diff --git a/csharp/App/BmsTunnel/BatteryConnection.cs b/csharp/App/BmsTunnel/BatteryTty.cs similarity index 58% rename from csharp/App/BmsTunnel/BatteryConnection.cs rename to csharp/App/BmsTunnel/BatteryTty.cs index a3cfc5cc2..b1ef964fd 100644 --- a/csharp/App/BmsTunnel/BatteryConnection.cs +++ b/csharp/App/BmsTunnel/BatteryTty.cs @@ -7,39 +7,26 @@ namespace InnovEnergy.App.BmsTunnel; using Nodes = IReadOnlyList; -public readonly struct BatteryConnection +public static class BatteryTty { - public String Tty { get; } - public Nodes Nodes { get; } - - private BatteryConnection(String tty, Nodes nodes) - { - Tty = tty; - Nodes = nodes; - } + const String DevDir = "/dev"; - private BatteryConnection(String tty, params Byte[] nodes) : this(tty, (Nodes) nodes) - { - } - - - public static async Task Connect(SshHost? host = null) + public static async Task GetTty() { Console.WriteLine("searching battery connection..."); - return await GetConnectionFromDBus(host) - ?? await GetConnectionFromDevTty(host); + return await GetTtyFromDBus() + ?? await GetTtyFromDev(); } - private static async Task GetConnectionFromDevTty(SshHost? host) + private static async Task GetTtyFromDev() { - var fs = FileSystem.OnHost(host); + var ttys = await FileSystem + .Local + .GetFiles(DevDir, FileType.CharacterDevice); - var ttys = await fs.GetFiles("/dev", FileType.CharacterDevice); - var candidateTtys = ttys - .Where(f => f.Contains("ttyUSB")) - .Select(t => t.Split("/").LastOrDefault()) + .Where(f => f.StartsWith(DevDir + "/ttyUSB")) .NotNull() .ToList(); @@ -49,17 +36,15 @@ public readonly struct BatteryConnection return null; } - var userSelectedTty = "Select TTY:".ChooseFrom(candidateTtys); + if (candidateTtys.Count == 1) + return candidateTtys[0]; - return userSelectedTty != null - ? new BatteryConnection(userSelectedTty) - : null; + return "Select TTY:".ChooseFrom(candidateTtys); } - private static async Task GetConnectionFromDBus(SshHost? host) + private static async Task GetTtyFromDBus() { var tty = await LsDBus - .OnHost(host) .ExecuteBufferedAsync() .Select(ParseBatteryTty); @@ -67,17 +52,15 @@ public readonly struct BatteryConnection return null; Console.WriteLine("found battery on DBus"); - - var availableNodes = await GetNodes(tty, host); - return new BatteryConnection(tty, availableNodes); + + return $"{DevDir}/{tty}"; } - private static CommandTask GetNodes(String tty, SshHost? host = null) + private static CommandTask GetNodes(String tty) { const String defaultArgs = "--system --print-reply --type=method_call / com.victronenergy.BusItem.GetValue"; return DBusSend - .OnHost(host) .AppendArgument($"--dest=com.victronenergy.battery.{tty}") .AppendArgument(defaultArgs) .ExecuteBufferedAsync() diff --git a/csharp/App/BmsTunnel/BmsTunnel.cs b/csharp/App/BmsTunnel/BmsTunnel.cs index bb3999280..d7f455996 100644 --- a/csharp/App/BmsTunnel/BmsTunnel.cs +++ b/csharp/App/BmsTunnel/BmsTunnel.cs @@ -23,12 +23,12 @@ public class BmsTunnel : IDisposable private const Byte TunnelCode = 0x41; private const String CrcError = "?? CRC FAILED"; - public BmsTunnel(String tty, Byte node, SshHost? host) + public BmsTunnel(String tty, Byte node) { Tty = tty; Node = node; - StopSerialStarter(host); + StopSerialStarter(); SerialPort = new SerialPort(Tty, BaudRate, Parity, DataBits, StopBits); SerialPort.ReadTimeout = 100; @@ -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; } @@ -165,11 +165,10 @@ public class BmsTunnel : IDisposable .Concat(NewLine); } - private void StopSerialStarter(SshHost? host) + private void StopSerialStarter() { CliPrograms.StopTty .WithArguments(Tty) - .OnHost(host) .ExecuteBufferedAsync() .Task .Wait(3000); diff --git a/csharp/App/BmsTunnel/Program.cs b/csharp/App/BmsTunnel/Program.cs index 538ff348b..eb23e57d8 100644 --- a/csharp/App/BmsTunnel/Program.cs +++ b/csharp/App/BmsTunnel/Program.cs @@ -13,41 +13,19 @@ public static class Program public static async Task Main(String[] args) { - var hostName = args.FirstOrDefault(); - - BatteryConnection? connection = hostName is not null ? await ConnectToBms(hostName, new SshHost(hostName)): new BatteryConnection(); + var tty = await BatteryTty.GetTty(); - if (connection is null) + if (tty is null) return 2; - - // connection.Tty; + Console.WriteLine("\nstarting BMS tunnel\n"); - var path = $"/dev/{connection.Value.Tty}"; - // if (hostName != null) - // path = $"root@{hostName}:" + path; - // - // var node = connection.Nodes.Any() - // ? connection.Nodes.First() - // : DefaultNode; - // - - // TODO: Fixme - // var path = ""; - Byte node = 2; - var nodes = new Byte[] { 1, 2, 3 }; - - // TODO: Fixme - - using var tunnel = new BmsTunnel(path, node, hostName is not null ? new SshHost(hostName): null); + using var tunnel = new BmsTunnel(tty, 2); ExplainNode(); - ExplainNodes(); ExplainExit(); Console.WriteLine(""); - - ListNodes(); while (true) { @@ -73,14 +51,11 @@ public static class Program Boolean ProcessLocalCommand(String cmd) { - cmd = cmd.TrimStart('/').Trim(); + cmd = cmd.TrimStart('/').Trim().ToUpper(); if (cmd == "EXIT") return true; - - if (cmd == "NODES") - ListNodes(); - else if (cmd.StartsWith("NODE ")) + if (cmd.StartsWith("NODE ")) ChangeNode(cmd); else Console.WriteLine("unrecognized command"); @@ -90,14 +65,6 @@ public static class Program return 0; - void ListNodes() - { - if(nodes.Length >= 250) - return; - - nodes.Aggregate("available nodes:", (a, b) => $"{a} {b}") - .Apply(Console.WriteLine); - } void ChangeNode(String cmd) { @@ -109,32 +76,10 @@ public static class Program return; } - if (!nodes.Contains(newNode)) - { - Console.WriteLine(newNode + " is not available"); - ListNodes(); - return; - } - tunnel.Node = newNode; } } - private static async Task ConnectToBms(String? hostName, SshHost host) - { - - if (await host.Ping()) - return await BatteryConnection.Connect(host); - - $"Cannot connect to {hostName}".WriteLine(ConsoleColor.Red); - - return null; - } - - private static void ExplainExit() => Console.WriteLine("/exit exit bms cli"); - private static void ExplainNodes() => Console.WriteLine("/nodes list available nodes"); private static void ExplainNode() => Console.WriteLine("/node change to node number "); - - } \ No newline at end of file diff --git a/csharp/App/Collector/Collector.sln b/csharp/App/Collector/Collector.sln deleted file mode 100644 index 8e142b535..000000000 --- a/csharp/App/Collector/Collector.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collector", "Collector.csproj", "{52BC5E5E-C6CE-4130-AD72-E16B793CE39A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "..\Utils\Utils.csproj", "{11568A17-A10B-4A38-849F-92D9A4E2AA35}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {52BC5E5E-C6CE-4130-AD72-E16B793CE39A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52BC5E5E-C6CE-4130-AD72-E16B793CE39A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52BC5E5E-C6CE-4130-AD72-E16B793CE39A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52BC5E5E-C6CE-4130-AD72-E16B793CE39A}.Release|Any CPU.Build.0 = Release|Any CPU - {11568A17-A10B-4A38-849F-92D9A4E2AA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {11568A17-A10B-4A38-849F-92D9A4E2AA35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {11568A17-A10B-4A38-849F-92D9A4E2AA35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {11568A17-A10B-4A38-849F-92D9A4E2AA35}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/csharp/App/EmuMeterDriver/Signal.cs b/csharp/App/EmuMeterDriver/Signal.cs index ef375bb91..d291d174c 100644 --- a/csharp/App/EmuMeterDriver/Signal.cs +++ b/csharp/App/EmuMeterDriver/Signal.cs @@ -4,6 +4,8 @@ using InnovEnergy.Lib.Victron.VeDBus; namespace InnovEnergy.App.EmuMeterDriver; + +// TODO: Does not compile public record Signal(Func Source, ObjectPath Path, String Format = "") { public VeProperty ToVeProperty(EmuMeterStatus status) @@ -11,5 +13,4 @@ public record Signal(Func Source, ObjectPath Path, Strin var value = Source(status); return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value)); } - } \ No newline at end of file diff --git a/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln b/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln deleted file mode 100644 index cb8340227..000000000 --- a/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenVpnCertificatesServer", "OpenVpnCertificatesServer.csproj", "{4C99C9B0-49F4-4DB7-8BC5-DA954EA68B5A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4C99C9B0-49F4-4DB7-8BC5-DA954EA68B5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C99C9B0-49F4-4DB7-8BC5-DA954EA68B5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C99C9B0-49F4-4DB7-8BC5-DA954EA68B5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C99C9B0-49F4-4DB7-8BC5-DA954EA68B5A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/csharp/App/SaliMax/Doc/Zustandsdiagramm_IBN_Standard.graphml b/csharp/App/SaliMax/Doc/Zustandsdiagramm_IBN_Standard.graphml new file mode 100644 index 000000000..1968f44f8 --- /dev/null +++ b/csharp/App/SaliMax/Doc/Zustandsdiagramm_IBN_Standard.graphml @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + 2 +Aus (mit Netz) + + + + + + + + + + + 3 + + + + + + + + + + + 4 + + + + + + + + + + + 5 + + + + + + + + + + + 6 + + + + + + + + + + + 7 + + + + + + + + + + + 8 + + + + + + + + + + + 9 + + + + + + + + + + + 4 + + + + + + + + + + + 11 + + + + + + + + + + + 12 + + + + + + + + + + + 13 + + + + + + + + + + + 14 + + + + + + + + + + + 15 + + + + + + + + + + + 16 +Netzparallel + + + + + + + + + + + 17 + + + + + + + + + + + 18 + + + + + + + + + + + 19 + + + + + + + + + + + 20 + + + + + + + + + + + 21 +Inselbetrieb + + + + + + + + + + + 22 + + + + + + + + + + + 23 + + + + + + + + + + + 24 + + + + + + + + + + + 1 + + + + + + + + + + + 9 + + + + + + + + + + + 13 + + + + + + + + + + + 15 + + + + + + + + + + + 17 + + + + + + + + + + + 1 +Aus (ohne Netz) + + + + + + + + + + + K1 schliesst + + + + + + + + + + + Uebergang nach Off + + + + + + + + + + + Uebergang nach Netzparallel + + + + + + + + + + + K3 öffnet + + + + + + + + + + + K2 schliesst + + + + + + + + + + + K3 schliesst + + + + + + + + + + + K1 öffnet + + + + + + + + + + + K2 öffnet + + + + + + + + + + + K3 öffnet + + + + + + + + + + + K3 schliesst + + + + + + + + + + + Uebergang nach Off + + + + + + + + + + + Uebergan nach Inselbetrieb + + + + + + + + + + + + K1 Schliesst + + + + + + + + + + + + + + + Turnoff The inverter + + + + + + + + + diff --git a/csharp/App/SaliMax/Doc/Zustandsmatrix_Salimax.xlsx b/csharp/App/SaliMax/Doc/Zustandsmatrix_Salimax.xlsx new file mode 100644 index 000000000..6cd1ceb13 Binary files /dev/null and b/csharp/App/SaliMax/Doc/Zustandsmatrix_Salimax.xlsx differ 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 (Salimax 0001).sh b/csharp/App/SaliMax/run (Salimax 0001).sh new file mode 100755 index 000000000..a138c3067 --- /dev/null +++ b/csharp/App/SaliMax/run (Salimax 0001).sh @@ -0,0 +1,33 @@ +#!/bin/bash + +dotnet_version='net6.0' +salimax_ip= '10.2.3.104' +username='ie-entwicklung@' + +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.104:~/salimax + +echo -e "\n============================ Restart Salimax sevice ============================\n" + +ssh -tt \ + ie-entwicklung@10.2.3.104 \ + sudo systemctl restart salimax.service + + +echo -e "\n============================ Print service output ============================\n" + +ssh -tt \ + ie-entwicklung@10.2.3.104 \ + journalctl -f -u salimax.service diff --git a/csharp/App/SaliMax/run (BeagleBone Meiringen).sh b/csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh similarity index 80% rename from csharp/App/SaliMax/run (BeagleBone Meiringen).sh rename to csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh index e98675532..7c2defe21 100755 --- a/csharp/App/SaliMax/run (BeagleBone Meiringen).sh +++ b/csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh @@ -2,6 +2,8 @@ dotnet_version='net6.0' +salimax_ip= '10.2.3.115' +username='ie-entwicklung@' set -e @@ -16,22 +18,17 @@ 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 - + ie-entwicklung@10.2.3.115:~/salimax + echo -e "\n============================ Restart Salimax sevice ============================\n" ssh -tt \ - ie-entwicklung@10.2.3.49 \ + ie-entwicklung@10.2.3.115 \ sudo systemctl restart salimax.service echo -e "\n============================ Print service output ============================\n" ssh -tt \ - ie-entwicklung@10.2.3.49 \ + ie-entwicklung@10.2.3.115 \ journalctl -f -u salimax.service - - diff --git a/csharp/App/SaliMax/src/AsciiArt.cs b/csharp/App/SaliMax/src/AsciiArt.cs deleted file mode 100644 index 9e0b47545..000000000 --- a/csharp/App/SaliMax/src/AsciiArt.cs +++ /dev/null @@ -1,79 +0,0 @@ -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 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/DeviceConfig.cs b/csharp/App/SaliMax/src/DeviceConfig.cs new file mode 100644 index 000000000..ca7982760 --- /dev/null +++ b/csharp/App/SaliMax/src/DeviceConfig.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; + +namespace InnovEnergy.App.SaliMax; + +public class DeviceConfig +{ + public String RelaysIp { get; set; } + public String TruConvertAcIp { get; set; } + public String TruConvertDcIp { get; set; } + public String GridMeterIp { get; set; } + public String InternalMeter { get; set; } + public String AmptIp { get; set; } + public byte[] BatteryNodes { get; set; } + public String BatteryTty { get; set; } + + [JsonConstructor] + public DeviceConfig(string relaysIp, string truConvertAcIp, string truConvertDcIp, string gridMeterIp, string internalMeter, string amptIp, byte[] batteryNodes, string batteryTty) + { + RelaysIp = relaysIp; + TruConvertAcIp = truConvertAcIp; + TruConvertDcIp = truConvertDcIp; + GridMeterIp = gridMeterIp; + InternalMeter = internalMeter; + AmptIp = amptIp; + BatteryNodes = batteryNodes; + BatteryTty = batteryTty; + } + + public static DeviceConfig? LoadDeviceConfig(String configFilePath) + { + try + { + var json = File.ReadAllText(configFilePath); + return JsonConvert.DeserializeObject(json); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } +} \ 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..063e80b10 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/Controller.cs @@ -0,0 +1,260 @@ +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 + + public static EssMode SelectControlMode(this StatusRecord s) + { + //return EssMode.OptimizeSelfConsumption; + + return s.StateMachine.State != 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 mode = s.SelectControlMode(); + + mode.WriteLine(); + + 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) + .LimitInverterPower(s); + + var currentPowerSetPoint = s.CurrentPowerSetPoint(); + + return limitedControl with { PowerSetpoint = currentPowerSetPoint + limitedControl.PowerCorrection }; + } + + private static EssControl LimitInverterPower(this EssControl control, StatusRecord s) + { + var powerDelta = control.PowerCorrection.Value; + + var acDcs = s.AcDc.Devices; + + var nInverters = acDcs.Count; + + if (nInverters < 2) + return control; // current loop cannot happen + + var nominalPower = acDcs.Average(d => d.Status.Nominal.Power); + var maxStep = nominalPower / 25; //TODO magic number to config + + var clampedPowerDelta = powerDelta.Clamp(-maxStep, maxStep); + + var dcLimited = acDcs.Any(d => d.Status.PowerLimitedBy == PowerLimit.DcLink); + + if (!dcLimited) + return control with { PowerCorrection = clampedPowerDelta }; + + var maxPower = acDcs.Max(d => d.Status.Ac.Power.Active.Value).WriteLine("Max"); + var minPower = acDcs.Min(d => d.Status.Ac.Power.Active.Value).WriteLine("Min"); + + var powerDifference = maxPower - minPower; + + if (powerDifference < maxStep) + return control with { PowerCorrection = clampedPowerDelta }; + + var correction = powerDifference / 4; //TODO magic number to config + + + // find out if we reach the lower or upper Dc limit by comparing the current Dc voltage to the reference voltage + return s.AcDc.Dc.Voltage > s.Config.ReferenceDcBusVoltage + ? control with { PowerCorrection = clampedPowerDelta.ClampMax(-correction), LimitedBy = EssLimit.ChargeLimitedByMaxDcBusVoltage } + : control with { PowerCorrection = clampedPowerDelta.ClampMin(correction), LimitedBy = EssLimit.DischargeLimitedByMinDcBusVoltage }; + } + + + private static EssControl LimitChargePower(this EssControl control, StatusRecord s) + { + + //var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower); + var maxBatteryChargePower = s.MaxBatteryChargePower(); + + return control + //.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower) + .LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower); + + } + + 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 Double ComputePowerDelta(this StatusRecord s, EssMode mode) + { + var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value); + + return mode switch + { + EssMode.HeatBatteries => s.ControlInverterPower(chargePower), + EssMode.ReachMinSoc => s.ControlInverterPower(chargePower), + EssMode.CalibrationCharge => s.ControlInverterPower(chargePower), + 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; + } + + + 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.BatterySelfDischargePower * 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..6d362b5c5 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/EssControl.cs @@ -0,0 +1,45 @@ +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.App.SaliMax.Ess; + +public record EssControl +( + EssMode Mode, + EssLimit LimitedBy, + ActivePower PowerCorrection, + ActivePower PowerSetpoint +) +{ + public EssControl LimitChargePower(Double controlDelta, EssLimit reason) + { + var overload = PowerCorrection - controlDelta; + + if (overload <= 0) + return this; + + return this with + { + LimitedBy = reason, + PowerCorrection = controlDelta, + PowerSetpoint = PowerSetpoint - overload + }; + } + + public EssControl LimitDischargePower(Double controlDelta, EssLimit reason) + { + var overload = PowerCorrection - controlDelta; + + if (overload >= 0) + return this; + + return this with + { + LimitedBy = reason, + PowerCorrection = controlDelta, + PowerSetpoint = PowerSetpoint - overload + }; + } +} + + + diff --git a/csharp/App/SaliMax/src/Ess/EssLimit.cs b/csharp/App/SaliMax/src/Ess/EssLimit.cs new file mode 100644 index 000000000..3eb95b07c --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/EssLimit.cs @@ -0,0 +1,20 @@ +namespace InnovEnergy.App.SaliMax.Ess; + +public enum EssLimit +{ + NoLimit, + DischargeLimitedByMinSoc, + DischargeLimitedByBatteryPower, + DischargeLimitedByInverterPower, + ChargeLimitedByInverterPower, + ChargeLimitedByBatteryPower, + ChargeLimitedByMaxDcBusVoltage, + DischargeLimitedByMinDcBusVoltage, +} + + +// 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..7a2b2a447 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/StatusRecord.cs @@ -0,0 +1,31 @@ +using InnovEnergy.App.SaliMax.SaliMaxRelays; +using InnovEnergy.App.SaliMax.System; +using InnovEnergy.App.SaliMax.SystemConfig; +using InnovEnergy.App.SaliMax.VirtualDevices; +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? LoadOnAcIsland { get; init; } + public AcPowerDevice? LoadOnAcGrid { get; init; } = null!; + public AcPowerDevice? PvOnAcGrid { get; init; } = null!; + public AcPowerDevice? PvOnAcIsland { get; init; } = null!; + public AcPowerDevice? AcGridToAcIsland { get; init; } = null!; + public DcPowerDevice? LoadOnDc { get; init; } = null!; + public RelaysRecord? Relays { get; init; } + public AmptStatus PvOnDc { get; init; } = null!; + public Config Config { get; init; } = null!; + public EssControl EssControl { get; set; } = null!; + public StateMachine StateMachine { get; } = new StateMachine(); + +} diff --git a/csharp/App/SaliMax/src/Flow.cs b/csharp/App/SaliMax/src/Flow.cs new file mode 100644 index 000000000..ddebc25fb --- /dev/null +++ b/csharp/App/SaliMax/src/Flow.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + + +namespace InnovEnergy.App.SaliMax; + +public static class Flow +{ + private static readonly String RightArrowChar = ">"; + private static readonly String LeftArrowChar = "<"; + private static readonly String DownArrowChar = "V"; + private static readonly String UpArrowChar = "^"; + + public static TextBlock Horizontal(Unit amount, Int32 width = 10) + { + var label = amount.ToStringRounded(); + var arrowChar = amount.Value < 0 ? LeftArrowChar : RightArrowChar; + var arrow = Enumerable.Repeat(arrowChar, width).Join(); + + // note : appending "fake label" below to make it vertically symmetric + return TextBlock.CenterHorizontal(label, arrow, ""); + } + + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + [SuppressMessage("ReSharper", "CoVariantArrayConversion")] + public static TextBlock Vertical(Unit amount, Int32 height = 4) + { + var label = amount.ToStringRounded(); + var arrowChar = amount.Value < 0 ? UpArrowChar : DownArrowChar; + var halfArrow = Enumerable.Repeat(arrowChar, height/2); + + var lines = halfArrow + .Append(label) + .Concat(halfArrow) + .ToArray(height / 2 * 2 + 1); + + return TextBlock.CenterHorizontal(lines); + } +} diff --git a/csharp/App/SaliMax/src/Logfile.cs b/csharp/App/SaliMax/src/Logfile.cs new file mode 100644 index 000000000..295d7729b --- /dev/null +++ b/csharp/App/SaliMax/src/Logfile.cs @@ -0,0 +1,67 @@ +using InnovEnergy.Lib.Time.Unix; +using Microsoft.Extensions.Logging; + +namespace InnovEnergy.App.SaliMax; + +public class CustomLogger : ILogger +{ + private readonly String _logFilePath; + private readonly Int64 _maxFileSizeBytes; + private readonly Int32 _maxLogFileCount; + private Int64 _currentFileSizeBytes; + + public CustomLogger(String logFilePath, Int64 maxFileSizeBytes, Int32 maxLogFileCount) + { + _logFilePath = logFilePath; + _maxFileSizeBytes = maxFileSizeBytes; + _maxLogFileCount = maxLogFileCount; + _currentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0; + } + + public IDisposable BeginScope(TState state) + { + throw new NotImplementedException(); + } + + public Boolean IsEnabled(LogLevel logLevel) + { + return true; // Enable logging for all levels + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + var logMessage = formatter(state, exception!); + + // Check the file size and rotate the log file if necessary + if (_currentFileSizeBytes + logMessage.Length >= _maxFileSizeBytes) + { + RotateLogFile(); + _currentFileSizeBytes = 0; + } + + // Write the log message to the file + File.AppendAllText(_logFilePath, logMessage + Environment.NewLine); + _currentFileSizeBytes += logMessage.Length; + } + + private void RotateLogFile() + { + // Check the log file count and delete the oldest file if necessary + var logFileDir = Path.GetDirectoryName(_logFilePath)!; + var logFileExt = Path.GetExtension(_logFilePath); + var logFileBaseName = Path.GetFileNameWithoutExtension(_logFilePath); + + var logFiles = Directory.GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}") + .OrderBy(file => file) + .ToList(); + + if (logFiles.Count >= _maxLogFileCount) + { + File.Delete(logFiles.First()); + } + + // Rename the current log file with a timestamp + var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{UnixTime.Now}{logFileExt}"); + File.Move(_logFilePath, logFileBackupPath); + } +} 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..368af1747 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -1,196 +1,410 @@ -using System.Diagnostics; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; +using System.Runtime.InteropServices; 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.App.SaliMax.VirtualDevices; 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.Power; using InnovEnergy.Lib.Utils; +using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig; +using AcPower = InnovEnergy.Lib.Units.Composite.AcPower; +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 static readonly TcpChannel RelaysChannel = new TcpChannel("10.0.1.1", 502); // "192.168.1.242"; + private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("10.0.2.1", 502); // "192.168.1.2"; + private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("10.0.3.1", 502); // "192.168.1.3"; + private static readonly TcpChannel GridMeterChannel = new TcpChannel("10.0.4.1", 502); // "192.168.1.241"; + private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("10.0.4.2", 502); // "192.168.1.241"; + private static readonly TcpChannel AmptChannel = new TcpChannel("10.0.5.1", 502); // "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(BatteryTty, n)) + .ToList(); + + var batteryDevices = new Battery48TlDevices(battery48TlDevices); + var acDcDevices = new TruConvertAcDcDevices(TruConvertAcChannel); + var dcDcDevices = new TruConvertDcDcDevices(TruConvertDcChannel); + var gridMeterDevice = new EmuMeterDevice(GridMeterChannel); + var acIslandLoadMeter = new EmuMeterDevice(AcOutLoadChannel); + var amptDevice = new AmptDevices(AmptChannel); + var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel); - var 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" - }; - -#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() { - var combinedBatteryStatus = batteries - .Select(b => b.ReadStatus()) - .NotNull() - .ToList() - .Combine(); + var acDc = acDcDevices.Read(); + var dcDc = dcDcDevices.Read(); + var battery = batteryDevices.Read(); + var relays = saliMaxRelaysDevice.Read(); + var loadOnAcIsland = acIslandLoadMeter.Read(); + var gridMeter = gridMeterDevice.Read(); + var pvOnDc = amptDevice.Read(); - // var dcDcStatusArray = dcDcDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); - // var inverterStatusArray = inverterDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); + var pvOnAcGrid = AcPowerDevice.Null; + var pvOnAcIsland = AcPowerDevice.Null; + var gridPower = gridMeter is null ? AcPower.Null : gridMeter.Ac.Power; + var islandLoadPower = loadOnAcIsland is null ? AcPower.Null : loadOnAcIsland.Ac.Power; + var inverterAcPower = acDc.Ac.Power; + + var loadOnAcGrid = gridPower + + pvOnAcGrid.Power + + pvOnAcIsland.Power + - islandLoadPower + - inverterAcPower; + var gridBusToIslandBusPower = gridPower + + pvOnAcGrid.Power + - loadOnAcGrid; + + // var dcPower = acDc.Dc.Power.Value + // + pvOnDc.Dc?.Power.Value ?? 0 + // - dcDc.Dc.Link.Power.Value; + + var dcPower = 0; + + var loadOnDc = new DcPowerDevice { Power = dcPower} ; + + 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, + AcDc = acDc ?? AcDcDevicesRecord.Null, + DcDc = dcDc ?? DcDcDevicesRecord.Null, + Battery = battery ?? Battery48TlRecords.Null, + Relays = relays, + GridMeter = gridMeter, + + PvOnAcGrid = pvOnAcGrid, + PvOnAcIsland = pvOnAcIsland, + PvOnDc = pvOnDc ?? AmptStatus.Null, + + AcGridToAcIsland = new AcPowerDevice { Power = gridBusToIslandBusPower }, + LoadOnAcGrid = new AcPowerDevice { Power = loadOnAcGrid }, + LoadOnAcIsland = loadOnAcIsland, + LoadOnDc = loadOnDc, + + Config = Config.Load() // load from disk every iteration, so config can be changed while running }; } + + void WriteControl(StatusRecord r) + { + if (r.Relays is not null) + saliMaxRelaysDevice.Write(r.Relays); + + acDcDevices.Write(r.AcDc); + dcDcDevices.Write(r.DcDc); + } - 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(); + + PrintTopology(record); - //JsonSerializer.Serialize(jsonLog, JsonOptions).WriteLine(ConsoleColor.DarkBlue); -#endif - Topology.Print(status); + if (record.Relays is not null) + record.Relays.ToCsv().WriteLine(); + + + var emuMeterRegisters = record.GridMeter; + if (emuMeterRegisters is not null) + { + emuMeterRegisters.Ac.Power.Active.WriteLine("Grid Active"); + //emuMeterRegisters.Ac.Power.Reactive.WriteLine("Grid Reactive"); + } + + + record.ControlConstants(); - while (UnixTime.Now == t) - await Task.Delay(delayTime); + record.ControlSystemState(); + + Console.WriteLine($"{record.StateMachine.State}: {record.StateMachine.Message}"); + + var essControl = record.ControlEss().WriteLine(); + + record.EssControl = essControl; + + record.AcDc.SystemControl.ApplyAcDcDefaultSettings(); + record.DcDc.SystemControl.ApplyDcDcDefaultSettings(); + + DistributePower(record, essControl); + + WriteControl(record); + + await UploadCsv(record, t); + + record.Config.Save(); + + "===========================================".WriteLine(); } // ReSharper disable once FunctionNeverReturns } - - - - // to delete not used anymore - [Conditional("RELEASE")] - private static void ReleaseWriteLog(JsonObject jsonLog, UnixTime timestamp) + private static void PrintTopology(StatusRecord s) { - // WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO + // Power Measurement Values + var gridPower = s.GridMeter!.Ac.Power.Active; + var inverterPower = s.AcDc.Ac.Power.Active; + var islandLoadPower = s.LoadOnAcIsland is null ? 0 : s.LoadOnAcIsland.Ac.Power.Active; + var dcBatteryPower = s.DcDc.Dc.Battery.Power; + var dcdcPower = s.DcDc.Dc.Link.Power; + var pvOnDcPower = s.PvOnDc.Dc!.Power.Value; + + // Power Calculated Values + var islandToGridBusPower = inverterPower + islandLoadPower; + var gridLoadPower = s.LoadOnAcGrid is null ? 0: s.LoadOnAcGrid.Power.Active; + + var gridPowerByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Power.Active.ToStringRounded(), + s.GridMeter.Ac.L2.Power.Active.ToStringRounded(), + s.GridMeter.Ac.L3.Power.Active.ToStringRounded()); + + var gridVoltageByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Voltage.ToStringRounded(), + s.GridMeter.Ac.L2.Voltage.ToStringRounded(), + s.GridMeter.Ac.L3.Voltage.ToStringRounded()); + + var inverterPowerByPhase = TextBlock.AlignLeft(s.AcDc.Ac.L1.Power.Active.ToStringRounded(), + s.AcDc.Ac.L2.Power.Active.ToStringRounded(), + s.AcDc.Ac.L3.Power.Active.ToStringRounded()); + + // ReSharper disable once CoVariantArrayConversion + var inverterPowerByAcDc = TextBlock.AlignLeft(s.AcDc.Devices + .Select(s1 => s1.Status.Ac.Power) + .ToArray()); + + var dcLinkVoltage = TextBlock.CenterHorizontal("", + s.DcDc.Dc.Link.Voltage.ToStringRounded(), + ""); + + //var inverterPowerByPhase = new ActivePower[(Int32)s.AcDc.Ac.L1.Power.Active, (Int32)s.AcDc.Ac.L2.Power.Active, (Int32)s.AcDc.Ac.L3.Power.Active]; + + // Voltage Measurement Values + //var inverterVoltage = new Voltage [(Int32)s.AcDc.Ac.L1.Voltage, (Int32)s.AcDc.Ac.L2.Voltage, (Int32)s.AcDc.Ac.L3.Voltage]; + //var dcLinkVoltage = s.DcDc.Dc.Link.Voltage; + var dc48Voltage = s.DcDc.Dc.Battery.Voltage; + var batteryVoltage = s.Battery.Dc.Voltage; + var batterySoc = s.Battery.Soc; + var batteryCurrent = s.Battery.Dc.Current; + var batteryTemp = s.Battery.Temperature; + + var gridBusColumn = ColumnBox("Pv", "Grid Bus", "Load" , gridVoltageByPhase , gridLoadPower); + var islandBusColumn = ColumnBox("Pv", "Island Bus", "Load" , inverterPowerByPhase, islandLoadPower); + var dcBusColumn = ColumnBox("Pv", "Dc Bus", "Load" , dcLinkVoltage, 0, pvOnDcPower); + var gridBusFlow = Flow.Horizontal(gridPower); + var flowGridBusToIslandBus = Flow.Horizontal((ActivePower)islandToGridBusPower); + var flowIslandBusToInverter = Flow.Horizontal(inverterPower); + var flowInverterToDcBus = Flow.Horizontal(inverterPower); + var flowDcBusToDcDc = Flow.Horizontal(dcdcPower); + var flowDcDcToBattery = Flow.Horizontal(dcBatteryPower); + + var gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid"); + var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("Inverter"); + var dcDcBox = TextBlock.AlignLeft(dc48Voltage).TitleBox("DC/DC"); + var batteryBox = TextBlock.AlignLeft(batteryVoltage.ToStringRounded(), batterySoc.ToStringRounded(), batteryCurrent.ToStringRounded(), batteryTemp.ToStringRounded()).TitleBox("Battery"); + + + + var totalBoxes = TextBlock.CenterVertical(gridBox, + gridBusFlow, + gridBusColumn, + flowGridBusToIslandBus, + islandBusColumn, + flowIslandBusToInverter, + inverterBox, + flowInverterToDcBus, + dcBusColumn, + flowDcBusToDcDc, + dcDcBox, + flowDcDcToBattery, + batteryBox); + + totalBoxes.WriteLine(); + } + + private static TextBlock ColumnBox(String pvTitle, String busTitle, String loadTitle, TextBlock dataBox) + { + return ColumnBox(pvTitle, busTitle, loadTitle, dataBox, 0); + } + + private static TextBlock ColumnBox(String pvTitle, String busTitle, String loadTitle, TextBlock dataBox, ActivePower loadPower) + { + return ColumnBox(pvTitle, busTitle, loadTitle, dataBox, loadPower, 0); + } + + private static TextBlock ColumnBox(String pvTitle, String busTitle, String loadTitle, TextBlock dataBox, ActivePower loadPower, ActivePower pvPower) + { + var pvBox = TextBlock.AlignLeft("").TitleBox(pvTitle); + var pvToBus = Flow.Vertical(pvPower); + var busBox = TextBlock.AlignLeft(dataBox).TitleBox(busTitle); + var busToLoad = Flow.Vertical(loadPower); + var loadBox = TextBlock.AlignLeft("").TitleBox(loadTitle); + + return TextBlock.CenterHorizontal(pvBox, pvToBus, busBox, busToLoad, loadBox); + } + + private static async Task ResultOrNull(this Task task) + { + if (task.Status == TaskStatus.RanToCompletion) + return await task; + + return default; + } + + private static void ControlConstants(this StatusRecord r) + { + var inverters = r.AcDc.Devices; + + inverters.ForEach(d => d.Control.Dc.MaxVoltage = r.Config.MaxDcBusVoltage); + inverters.ForEach(d => d.Control.Dc.MinVoltage = r.Config.MinDcBusVoltage); + inverters.ForEach(d => d.Control.Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage); } - [Conditional("DEBUG")] - private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp) + // why this is not in Controller? + private static void DistributePower(StatusRecord record, EssControl essControl) { - WriteToFile(jsonLog, "/home/atef/JsonData/" + timestamp); + var nInverters = record.AcDc.Devices.Count; + + var powerPerInverterPhase = nInverters > 0 + ? AcPower.FromActiveReactive(essControl.PowerSetpoint / nInverters / 3, 0) + : AcPower.Null; + + //var powerPerInverterPhase = AcPower.Null; + + record.AcDc.Devices.ForEach(d => + { + d.Control.Ac.PhaseControl = PhaseControl.Asymmetric; + d.Control.Ac.Power.L1 = powerPerInverterPhase; + d.Control.Ac.Power.L2 = powerPerInverterPhase; + d.Control.Ac.Power.L3 = powerPerInverterPhase; + }); } - private static void WriteToFile(Object obj, String fileName) + private static void ApplyAcDcDefaultSettings(this SystemControlRegisters? sc) { - var jsonString = JsonSerializer.Serialize(obj, JsonOptions); - File.WriteAllText(fileName, jsonString); + if (sc is null) + return; + + sc.ReferenceFrame = ReferenceFrame.Consumer; + sc.SystemConfig = AcDcAndDcDc; + + #if DEBUG + sc.CommunicationTimeout = TimeSpan.FromMinutes(2); + #else + sc.CommunicationTimeout = TimeSpan.FromSeconds(20); + #endif + + sc.PowerSetPointActivation = PowerSetPointActivation.Immediate; + sc.UseSlaveIdForAddressing = true; + sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; + sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; + sc.ResetAlarmsAndWarnings = true; + } + + private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc) + { + if (sc is null) + return; + + sc.SystemConfig = DcDcOnly; + #if DEBUG + sc.CommunicationTimeout = TimeSpan.FromMinutes(2); + #else + sc.CommunicationTimeout = TimeSpan.FromSeconds(20); + #endif + + sc.PowerSetPointActivation = PowerSetPointActivation.Immediate; + sc.UseSlaveIdForAddressing = true; + sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; + sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; + sc.ResetAlarmsAndWarnings = true; } - private static readonly JsonSerializerOptions JsonOptions = new() + private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp) { - WriteIndented = true, - IgnoreReadOnlyProperties = false, - Converters = { new JsonStringEnumConverter() }, - NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, - //TODO - }; - - - private static async Task UploadTimeSeries(S3Config config, JsonObject json, UnixTime unixTime) - { - var payload = JsonSerializer.Serialize(json, JsonOptions); - var s3Path = unixTime.Ticks + ".json"; - var request = config.CreatePutRequest(s3Path); - var response = await request.PutAsync(new StringContent(payload)); + timeStamp.WriteLine(); + + 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 +413,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..7e20ca8eb --- /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.StateMachine.State = s.GetSystemState(); + + return s.StateMachine.State 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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.Message = "ESS"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.ConnectIslandBusToGrid(); + + return true; + + // => 15 + } + + + private static Boolean State17(StatusRecord s) + { + s.StateMachine.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.StateMachine.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.StateMachine.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.StateMachine.Message = "Relay device is not available"; + return s.EnableSafeDefaults(); + } + + private static Boolean State102(StatusRecord s) + { + s.StateMachine.Message = "ACDCs not available"; + return s.EnableSafeDefaults(); + } + + + private static Boolean State103(StatusRecord s) + { + s.StateMachine.Message = "Panic: ACDCs have unequal grid types"; + return s.EnableSafeDefaults(); + } + + private static Boolean State104(StatusRecord s) + { + s.StateMachine.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/StateMachine.cs b/csharp/App/SaliMax/src/System/StateMachine.cs new file mode 100644 index 000000000..75abc1ae8 --- /dev/null +++ b/csharp/App/SaliMax/src/System/StateMachine.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.App.SaliMax.System; + +public class StateMachine +{ + public String Message { get; set; } = "Panic: Unknown State!"; + public Int32 State { 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..87febfdbd 100644 --- a/csharp/App/SaliMax/src/SystemConfig/Config.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Config.cs @@ -1,6 +1,90 @@ +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 GridSetPoint { get; set; } + public Double BatterySelfDischargePower { get; set; } + public Double HoldSocZone { get; set; } + + public Double MaxDcBusVoltage { get; set; } + public Double MinDcBusVoltage { get; set; } + public Double ReferenceDcBusVoltage { get; set; } + + public static Config Default => new() + { + MinSoc = 20, + LastEoc = UnixTime.Epoch, + PConstant = .5, + GridSetPoint = 0, + BatterySelfDischargePower = 200, // TODO: multiple batteries + HoldSocZone = 1, // TODO: find better name, + MinDcBusVoltage = 730, + ReferenceDcBusVoltage = 750, + MaxDcBusVoltage = 770, + }; + + + 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/src/VirtualDevices/AcPowerDevice.cs b/csharp/App/SaliMax/src/VirtualDevices/AcPowerDevice.cs new file mode 100644 index 000000000..0775a1018 --- /dev/null +++ b/csharp/App/SaliMax/src/VirtualDevices/AcPowerDevice.cs @@ -0,0 +1,10 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.App.SaliMax.VirtualDevices; + +public class AcPowerDevice +{ + public AcPower Power { get; init; } = AcPower.Null; + + public static AcPowerDevice Null => new AcPowerDevice(); +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/VirtualDevices/DcPowerDevice.cs b/csharp/App/SaliMax/src/VirtualDevices/DcPowerDevice.cs new file mode 100644 index 000000000..d8f612124 --- /dev/null +++ b/csharp/App/SaliMax/src/VirtualDevices/DcPowerDevice.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.App.SaliMax.VirtualDevices; + +public class DcPowerDevice +{ + public DcPower Power { get; init; } = DcPower.Null; +} \ 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/tunnelsToProto.sh old mode 100644 new mode 100755 similarity index 88% rename from csharp/App/SaliMax/tunneltoProto.sh rename to csharp/App/SaliMax/tunnelsToProto.sh index 215e43c1d..0a136e54b --- a/csharp/App/SaliMax/tunneltoProto.sh +++ b/csharp/App/SaliMax/tunnelsToProto.sh @@ -33,11 +33,12 @@ 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 - +tunnel "Adam " 10.0.1.1 502 5006 +tunnel "Batteries " 127.0.0.1 6855 5007 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 +echo "done" diff --git a/csharp/App/SaliMax/tunnelstoSalimax0001.sh b/csharp/App/SaliMax/tunnelstoSalimax0001.sh new file mode 100755 index 000000000..4dd7a96af --- /dev/null +++ b/csharp/App/SaliMax/tunnelstoSalimax0001.sh @@ -0,0 +1,45 @@ +j#!/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) " 10.0.2.1 80 7001 +tunnel "Trumpf DCDC (http) " 10.0.3.1 80 7002 +tunnel "Ext Emu Meter (http) " 10.0.4.1 80 7003 +tunnel "Int Emu Meter (http) " 10.0.4.2 80 7004 +tunnel "AMPT (http) " 10.0.5.1 8080 7005 + + +tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 4001 +tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 4002 +tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 4003 +tunnel "Int Emu Meter " 10.0.4.2 502 4004 +tunnel "AMPT (modbus) " 10.0.5.1 502 4005 +tunnel "Adam " 10.0.1.1 502 4006 + + + +echo +echo "press any key to close the tunnels ..." +read -r -n 1 -s +kill $(jobs -p) +echo "done" diff --git a/csharp/InnovEnergy.props b/csharp/InnovEnergy.props index b1aafb267..ad3d9ebef 100644 --- a/csharp/InnovEnergy.props +++ b/csharp/InnovEnergy.props @@ -9,12 +9,10 @@ net6.0 true false - Please.reload.the.project.Rider.is.stupid + $([System.Text.RegularExpressions.Regex]::Match($(MSBuildProjectExtensionsPath), "[/\\]csharp[/\\].*[/\\]obj").ToString().Replace("/",".").Replace("\\",".").Replace(".csharp", $(Company)).Replace(".obj", "")) $(Company) Team + - - - $(Company).$(MSBuildProjectDirectory.Replace($(SolutionDir), "").Replace("/",".").Replace("\",".")) - + diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index 640f8ab8f..f3837b3d4 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -73,7 +73,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{AED84693-C ../.gitignore = ../.gitignore EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VrmGrabber", "App\VrmGrabber\VrmGrabber.csproj", "{4F9BB20B-8030-48AB-A37B-23796459D516}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Lib\Logging\Logging.csproj", "{1A56992B-CB72-490F-99A4-DF1186BA3A18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SrcGen", "Lib\SrcGen\SrcGen.csproj", "{2E5409D6-59BD-446F-BB82-E7759DD8AADD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6360D", "Lib\Devices\Adam6360D\Adam6360D.csproj", "{A3C79247-4CAA-44BE-921E-7285AB39E71F}" EndProject Global @@ -186,10 +190,18 @@ Global {B816BB44-E97E-4E02-B80A-BEDB5B923A96}.Debug|Any CPU.Build.0 = Debug|Any CPU {B816BB44-E97E-4E02-B80A-BEDB5B923A96}.Release|Any CPU.ActiveCfg = Release|Any CPU {B816BB44-E97E-4E02-B80A-BEDB5B923A96}.Release|Any CPU.Build.0 = Release|Any CPU - {4F9BB20B-8030-48AB-A37B-23796459D516}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F9BB20B-8030-48AB-A37B-23796459D516}.Release|Any CPU.Build.0 = Release|Any CPU - {4F9BB20B-8030-48AB-A37B-23796459D516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F9BB20B-8030-48AB-A37B-23796459D516}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A56992B-CB72-490F-99A4-DF1186BA3A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A56992B-CB72-490F-99A4-DF1186BA3A18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A56992B-CB72-490F-99A4-DF1186BA3A18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A56992B-CB72-490F-99A4-DF1186BA3A18}.Release|Any CPU.Build.0 = Release|Any CPU + {2E5409D6-59BD-446F-BB82-E7759DD8AADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E5409D6-59BD-446F-BB82-E7759DD8AADD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E5409D6-59BD-446F-BB82-E7759DD8AADD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E5409D6-59BD-446F-BB82-E7759DD8AADD}.Release|Any CPU.Build.0 = Release|Any CPU + {A3C79247-4CAA-44BE-921E-7285AB39E71F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3C79247-4CAA-44BE-921E-7285AB39E71F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3C79247-4CAA-44BE-921E-7285AB39E71F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3C79247-4CAA-44BE-921E-7285AB39E71F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A} @@ -222,6 +234,8 @@ Global {C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {4A67D79F-F0C9-4BBC-9601-D5948E6C05D3} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {B816BB44-E97E-4E02-B80A-BEDB5B923A96} = {DDDBEFD0-5DEA-4C7C-A9F2-FDB4636CF092} - {4F9BB20B-8030-48AB-A37B-23796459D516} = {145597B4-3E30-45E6-9F72-4DD43194539A} + {1A56992B-CB72-490F-99A4-DF1186BA3A18} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} + {2E5409D6-59BD-446F-BB82-E7759DD8AADD} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} + {A3C79247-4CAA-44BE-921E-7285AB39E71F} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} EndGlobalSection EndGlobal diff --git a/csharp/InnovEnergy.sln.DotSettings b/csharp/InnovEnergy.sln.DotSettings index 61f3b696e..33953991f 100644 --- a/csharp/InnovEnergy.sln.DotSettings +++ b/csharp/InnovEnergy.sln.DotSettings @@ -11,12 +11,14 @@ True True True + True True True True True True True + True True True True @@ -35,8 +37,10 @@ True True True + True True True + True True True True diff --git a/csharp/Lib/Devices/AMPT/Ampt.csproj b/csharp/Lib/Devices/AMPT/Ampt.csproj index d5340e3ca..f2b0a1896 100644 --- a/csharp/Lib/Devices/AMPT/Ampt.csproj +++ b/csharp/Lib/Devices/AMPT/Ampt.csproj @@ -1,9 +1,7 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - + + + diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs index 6491f4e8b..2592c22d3 100644 --- a/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs @@ -1,118 +1,90 @@ -using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Units.Composite; -using static DecimalMath.DecimalEx; - -namespace InnovEnergy.Lib.Devices.AMPT; - -public class AmptCommunicationUnit -{ - private ModbusTcpClient? Modbus { get; set; } - - private const UInt16 RegistersPerDevice = 16; - private const UInt16 FirstDeviceOffset = 85; - - public String Hostname { get; } - public UInt16 Port { get; } - public Byte SlaveAddress { get; } - - public AmptCommunicationUnit(String hostname, UInt16 port = 502, Byte slaveAddress = 1) - { - Hostname = hostname; - Port = port; - SlaveAddress = slaveAddress; - } - - public AmptCommunicationUnitStatus? ReadStatus() - { - try - { - OpenConnection(); - return TryReadStatus(); - } - catch - { - CloseConnection(); - return null; - } - } - - private void CloseConnection() - { - try - { - Modbus?.CloseConnection(); - } - catch - { - // ignored - } - - Modbus = null; - } - - private void OpenConnection() - { - if (Modbus is null) - { - var connection = new ModbusTcpConnection(Hostname, Port); - Modbus = new ModbusTcpClient(connection, SlaveAddress); - } - } - - private AmptCommunicationUnitStatus TryReadStatus() - { - var r = Modbus!.ReadHoldingRegisters(1, 116); - - var currentFactor = Pow(10.0m, r.GetInt16(73)); - var voltageFactor = Pow(10.0m, r.GetInt16(74)); - var energyFactor = Pow(10.0m, r.GetInt16(76) + 3); // +3 => converted from Wh to kWh - var nbrOfDevices = r.GetUInt16(78); - - var devices = Enumerable - .Range(0, nbrOfDevices) - .Select(ReadDeviceStatus) - .ToList(); - - return new AmptCommunicationUnitStatus - { - Sid = r.GetUInt32(1), - IdSunSpec = r.GetUInt16(3), - Manufacturer = r.GetString(5, 16), - Model = r.GetString(21, 16), - Version = r.GetString(45, 8), - SerialNumber = r.GetString(53, 16), - DeviceAddress = r.GetInt16(69), - IdVendor = r.GetUInt16(71), - Devices = devices - }; - - AmptStatus ReadDeviceStatus(Int32 deviceNumber) - { - var baseAddress = (UInt16)(FirstDeviceOffset + deviceNumber * RegistersPerDevice); // base address - - return new AmptStatus - { - Dc = new DcBus - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 6)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 5)) * currentFactor - }, - Strings = new DcBus[] - { - new() - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 8)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 14)) * currentFactor - }, - new() - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 9)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 15)) * currentFactor - } - }, - ProductionToday = r.GetUInt32((UInt16)(baseAddress + 12)) * energyFactor, - }; - } - } -} \ No newline at end of file +// using InnovEnergy.Lib.Protocols.Modbus.Channels; +// using InnovEnergy.Lib.Protocols.Modbus.Clients; +// using InnovEnergy.Lib.Protocols.Modbus.Conversions; +// using InnovEnergy.Lib.Protocols.Modbus.Slaves; +// using InnovEnergy.Lib.Units.Composite; +// +// namespace InnovEnergy.Lib.Devices.AMPT; +// +// public class AmptCommunicationUnit : ModbusDevice +// { +// +// private const UInt16 RegistersPerDevice = 16; +// private const UInt16 FirstDeviceOffset = 85; +// +// +// public AmptCommunicationUnit(String hostname, UInt16 port = 502, Byte slaveAddress = 1) : this +// ( +// channel: new TcpChannel(hostname, port), +// slaveAddress +// ) +// {} +// +// +// public AmptCommunicationUnit(Channel channel, Byte slaveAddress) : this +// ( +// client: new ModbusTcpClient(channel, slaveAddress) +// ) +// {} +// +// public AmptCommunicationUnit(ModbusClient client) : base(client) +// { +// } +// +// +// private AmptCommunicationUnitStatus TryReadStatus() +// { +// var r = new ModbusRegisters(116, Modbus.ReadHoldingRegisters(1, 116).ToArray()) ; // TODO +// +// var currentFactor = Pow(10.0m, r.GetInt16(73)); +// var voltageFactor = Pow(10.0m, r.GetInt16(74)); +// var energyFactor = Pow(10.0m, r.GetInt16(76) + 3); // +3 => converted from Wh to kWh +// var nbrOfDevices = r.GetUInt16(78); +// +// var devices = Enumerable +// .Range(0, nbrOfDevices) +// .Select(ReadDeviceStatus) +// .ToList(); +// +// return new AmptCommunicationUnitStatus +// { +// Sid = r.GetUInt32(1), +// IdSunSpec = r.GetUInt16(3), +// Manufacturer = r.GetString(5, 16), +// Model = r.GetString(21, 16), +// Version = r.GetString(45, 8), +// SerialNumber = r.GetString(53, 16), +// DeviceAddress = r.GetInt16(69), +// IdVendor = r.GetUInt16(71), +// Devices = devices +// }; +// +// AmptStatus ReadDeviceStatus(Int32 deviceNumber) +// { +// var baseAddress = (UInt16)(FirstDeviceOffset + deviceNumber * RegistersPerDevice); // base address +// +// return new AmptStatus +// { +// Dc = new DcBus +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 6)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 5)) * currentFactor +// }, +// Strings = new DcBus[] +// { +// new() +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 8)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 14)) * currentFactor +// }, +// new() +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 9)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 15)) * currentFactor +// } +// }, +// ProductionToday = r.GetUInt32((UInt16)(baseAddress + 12)) * energyFactor, +// }; +// } +// } +// } \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs index 757d3592f..e9a95ea44 100644 --- a/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs @@ -1,6 +1,6 @@ namespace InnovEnergy.Lib.Devices.AMPT; -public record AmptCommunicationUnitStatus +public class AmptCommunicationUnitStatus { public UInt32 Sid { get; init; } // A well-known value 0x53756e53, uniquely identifies this as a SunSpec Modbus Map public UInt16 IdSunSpec { get; init; } // A well-known value 1, uniquely identifies this as a SunSpec Common Model diff --git a/csharp/Lib/Devices/AMPT/AmptDevices.cs b/csharp/Lib/Devices/AMPT/AmptDevices.cs new file mode 100644 index 000000000..1750d0029 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/AmptDevices.cs @@ -0,0 +1,100 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public class AmptDevices +{ + private readonly ModbusDevice _CommunicationUnit; + private readonly IEnumerable> _StringOptimizers; + + public AmptDevices(String hostname, UInt16 port = 502) : this(new TcpChannel(hostname, port)) + { + } + + public AmptDevices(Channel transport) : this(new ModbusTcpClient(transport, 2)) + { + } + + + public AmptDevices(ModbusClient modbusClient) + { + _CommunicationUnit = new ModbusDevice(modbusClient); + _StringOptimizers = StringOptimizers(modbusClient); + } + + public AmptStatus Read() + { + CommunicationUnitRegisters? cuStatus = null; + + try + { + cuStatus = _CommunicationUnit.Read(); + } + catch (Exception e) + { + Console.WriteLine(e); + // TODO: log + } + + // CommunicationUnit knows how many StringOptimizers are connected + var nStringOptimizers = cuStatus?.NumberOfStringOptimizers ?? 0; + + // hardcoded: every SO has 2 strings (produced like this by AMPT) + var nStrings = nStringOptimizers * 2; + + // read stati from optimizers + var soStati = _StringOptimizers + .Take(nStringOptimizers) + .Select(so => so.Read()) + .ToArray(nStringOptimizers); + + // every SO has 2 strings but ONE Dc Link Connection + // they are connected to a shared Dc Link, so Voltage seen by them should be approx the same. + // voltages are averaged, currents added + + // TODO: alarm when we see substantially different voltages + + var busVoltage = nStringOptimizers == 0 ? 0 : soStati.Average(r => r.Voltage); + var busCurrent = nStringOptimizers == 0 ? 0 : soStati.Sum (r => r.Current); + var dc = DcBus.FromVoltageCurrent(busVoltage, busCurrent); + + // flatten the 2 strings of each SO into one array + var strings = soStati.SelectMany(GetStrings).ToArray(nStrings); + + return new AmptStatus(dc, strings); + } + + + + private static IEnumerable GetStrings(StringOptimizerRegisters r) + { + // hardcoded: every SO has 2 strings (produced like this by AMPT) + + yield return DcBus.FromVoltageCurrent(r.String1Voltage, r.String1Current); + yield return DcBus.FromVoltageCurrent(r.String2Voltage, r.String2Current); + } + + + private static IEnumerable> StringOptimizers(ModbusClient modbusClient) + { + var cache = new List>(); + + ModbusDevice GetOptimizer(Int32 i) + { + if (i < cache.Count) + return cache[i]; + + var modbusDevice = new ModbusDevice(modbusClient, i * 16); + cache.Add(modbusDevice); + return modbusDevice; + } + + return Enumerable + .Range(0, Byte.MaxValue) + .Select(GetOptimizer); + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptStatus.cs b/csharp/Lib/Devices/AMPT/AmptStatus.cs index 8e2140bdc..19977095e 100644 --- a/csharp/Lib/Devices/AMPT/AmptStatus.cs +++ b/csharp/Lib/Devices/AMPT/AmptStatus.cs @@ -1,9 +1,38 @@ -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Devices.AMPT; -public record AmptStatus : MpptStatus +public class AmptStatus { - public Energy ProductionToday { get; init; } // converted to kW in AmptCU class + public AmptStatus(DcBus? dc, IReadOnlyList strings) + { + Dc = dc; + Strings = strings; + } + + public DcBus? Dc { get; } + public IReadOnlyList Strings { get; } + + public static AmptStatus Null => new AmptStatus(null, Array.Empty()); } + + +// public static AmptStatus Parallel(IReadOnlyList stati) + // { + // if (stati.Count == 0) + // { + // return new AmptStatus + // ( + // Dc: DcBus.FromVoltageCurrent(0, 0), + // Strings: Array.Empty() + // ); + // } + // + // var voltage = stati.Average(s => s.Dc.Voltage.Value); + // var current = stati.Sum(s => s.Dc.Current.Value); + // var dc = DcBus.FromVoltageCurrent(voltage, current); + // + // var strings = stati.SelectMany(s => s.Strings).ToList(); + // + // return new AmptStatus(dc, strings); + // } \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs b/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs new file mode 100644 index 000000000..5f9edcd41 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.AMPT; + +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] + + +[OneBasedAddressing] +public record CommunicationUnitRegisters +{ + [HoldingRegister(73)] public Int16 CurrentScaleFactor { get; private set; } + [HoldingRegister(74)] public Int16 VoltageScaleFactor { get; private set; } + [HoldingRegister(76)] public Int16 EnergyScaleFactor { get; private set; } + + [HoldingRegister(78)] public UInt16 NumberOfStringOptimizers { get; private set; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/Doc/Ampt_Communications_Unit_Installation_Manual_57070008-1C.pdf b/csharp/Lib/Devices/AMPT/Doc/Ampt_Communications_Unit_Installation_Manual_57070008-1C.pdf new file mode 100644 index 000000000..4dca09e96 Binary files /dev/null and b/csharp/Lib/Devices/AMPT/Doc/Ampt_Communications_Unit_Installation_Manual_57070008-1C.pdf differ diff --git a/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html b/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html new file mode 100644 index 000000000..1448776e4 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html @@ -0,0 +1,412 @@ + + + + + Modbus Map + + + +
+

+

Modbus Map

+
+

These Modbus maps are for your reference. The modbus service runs on port 502.

+

Important note: The Ampt ModBus register map uses big endian values.

+

SunSpec SMDX File

+
+
+

SunSpec Registers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Start OffsetSizeNameTypeR/WDescription
12SIDuint32RA well-known value 0x53756e53, uniquely identifies this as a SunSpec Modbus Map
31IDuint16RA well-known value 1, uniquely identifies this as a SunSpec Common Model
41Luint16RWell-known # of 16-bit registers to follow: 66
516ManufacturerstringRA well-known value registered with SunSpec for compliance: "Ampt"
2116ModelstringRManufacturer specific value "Communication Unit"
378Reserved--
458VersionstringRSoftware Version
5316Serial NumberstringRManufacturer specific value
691Device Addressint16R/WModbus Device ID
701Reserved--
711IDuint16RAmpt SunSpec Vendor Code 64050
721Luint16RVariable number of 16-bit registers to follow: 12 + N*16
731DCA_SFint16RCurrent scale factor
741DCV_SFint16RVoltage scale factor
751Reserved--
761DCkWh_SFint16REnergy Scale Factor
771Reserved--
781Nuint16RNumber of strings
796Reserved--
1
851String IDint16RThe string number
862Reserved--
882String data timestampuint32RThe UTC timestamp of the measurements
901OutDCAint16RString output current in mA
912OutDCVuint32RString output voltage in mV
932In1DCVuint32RString input 1 voltage in mV
952In2DCVuint32RString input 2 voltage in mV
972DCWhuint32RDaily integrated string output energy in Wh
991In1DCAint16RString input 1 current in mA
1001In2DCAint16RString input 2 current in mA
2
1011String IDint16RThe string number
1022Reserved--
1042String data timestampuint32RThe UTC timestamp of the measurements
1061OutDCAint16RString output current in mA
1072OutDCVuint32RString output voltage in mV
1092In1DCVuint32RString input 1 voltage in mV
1112In2DCVuint32RString input 2 voltage in mV
1132DCWhuint32RDaily integrated string output energy in Wh
1151In1DCAint16RString input 1 current in mA
1161In2DCAint16RString input 2 current in mA
+


+
+ + diff --git a/csharp/Lib/Devices/AMPT/Doc/smdx_64050.xml b/csharp/Lib/Devices/AMPT/Doc/smdx_64050.xml new file mode 100644 index 000000000..6e5e32266 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/Doc/smdx_64050.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ampt SunSpec Vendor Code 64050 + + + + + Variable number of 16-bit registers to follow: 12 + N*16 + + + + + Current Scale Factor + + + + + Voltage Scale Factor + + + + + Energy Scale Factor + + + + + Number of strings + + + + + The String Number + + + + + UTC timestamp of measurements + + + + + String output current in mA + + + + + String output current in mV + + + + + String input 1 in mV + + + + + String input 2 in mV + + + + + Daily integrated string output energy in watt-hours + + + + + String input 1 in mA + + + + + String input 1 in mA + + + + \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/Program.cs b/csharp/Lib/Devices/AMPT/Program.cs new file mode 100644 index 000000000..55fb5194d --- /dev/null +++ b/csharp/Lib/Devices/AMPT/Program.cs @@ -0,0 +1,28 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public static class Program +{ + public static Task Main(string[] args) + { + var ch = new TcpChannel("localhost", 5005); + var cl = new ModbusTcpClient(ch, 1); + var d = new AmptDevices(cl); + + while (true) + { + AmptStatus x = d.Read(); + + x.ToCsv().WriteLine(); + + //Console.WriteLine(x); + } + + + //Console.WriteLine(x); + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs b/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs new file mode 100644 index 000000000..6ae8327c7 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.AMPT; + + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + +[OneBasedAddressing][BigEndian] +public record StringOptimizerRegisters +{ + [HoldingRegister(88) ] public UInt32 Timestamp { get; private set; } + + [HoldingRegister (90, Scale = .001)] public Double Current { get; private set; } + [HoldingRegister(91, Scale = .001)] public Double Voltage { get; private set; } + + [HoldingRegister(93, Scale = .001)] public Double String1Voltage { get; private set; } + [HoldingRegister(95, Scale = .001)] public Double String2Voltage { get; private set; } + + [HoldingRegister(97)] public Double ProductionToday { get; private set; } + + [HoldingRegister(99, Scale = .001)] public Double String1Current { get; private set; } + [HoldingRegister(100, Scale = .001)] public Double String2Current { get; private set; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6060/Adam6060Control.cs b/csharp/Lib/Devices/Adam6060/Adam6060Control.cs deleted file mode 100644 index 10167564e..000000000 --- a/csharp/Lib/Devices/Adam6060/Adam6060Control.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -namespace InnovEnergy.Lib.Devices.Adam6060; - -public class Adam6060Control -{ - internal const UInt16 RelaysStartRegister = 17; - internal const UInt16 NbRelays = 6; - - public Boolean Relay0 { get; init; } // Address(0X) 00017 - public Boolean Relay1 { get; init; } // Address(0X) 00018 - public Boolean Relay2 { get; init; } // Address(0X) 00019 - public Boolean Relay3 { get; init; } // Address(0X) 00020 - public Boolean Relay4 { get; init; } // Address(0X) 00021 - public Boolean Relay5 { get; init; } // Address(0X) 00022 -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6060/Adam6060Device.cs b/csharp/Lib/Devices/Adam6060/Adam6060Device.cs index d910baf41..114aa3d32 100644 --- a/csharp/Lib/Devices/Adam6060/Adam6060Device.cs +++ b/csharp/Lib/Devices/Adam6060/Adam6060Device.cs @@ -1,103 +1,18 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using static InnovEnergy.Lib.Devices.Adam6060.Adam6060Status; -using static InnovEnergy.Lib.Devices.Adam6060.Adam6060Control; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; namespace InnovEnergy.Lib.Devices.Adam6060; -public class Adam6060Device +public class Adam6060Device : ModbusDevice { - public String Hostname { get; } - public UInt16 Port { get; } - public Byte SlaveAddress { get; } - private ModbusTcpClient? Modbus { get; set; } - - public Adam6060Device(String hostname, UInt16 port = 5004, Byte slaveAddress = 2) + public Adam6060Device(String hostname, Byte slaveId, UInt16 port = 502) : + this(new TcpChannel(hostname, port), slaveId) { - Hostname = hostname; - Port = port; - SlaveAddress = slaveAddress; } - private void OpenConnection() + public Adam6060Device(Channel channel, Byte slaveId) : base(new ModbusTcpClient(channel, slaveId)) { - if (Modbus is null) - { - var connection = new ModbusTcpConnection(Hostname, Port); - Modbus = new ModbusTcpClient(connection, SlaveAddress); - } - } - - public Adam6060Status? ReadStatus() - { - try - { - OpenConnection(); - return TryReadStatus(); - } - catch - { - CloseConnection(); - return null; - } - } - - private void CloseConnection() - { - try - { - Modbus?.CloseConnection(); - } - catch - { - // ignored - } - - Modbus = null; - } - - private Adam6060Status TryReadStatus() - { - var inputs = Modbus!.ReadDiscreteInputs(DigitalInputsStartRegister, NbDigitalInputs); - var relays = Modbus!.ReadDiscreteInputs(RelaysStartRegister, NbRelays); - - return new Adam6060Status - { - DigitalInput0 = inputs[0], - DigitalInput1 = inputs[1], - DigitalInput2 = inputs[2], - DigitalInput3 = inputs[3], - DigitalInput4 = inputs[4], - DigitalInput5 = inputs[5], - - Relay0 = relays[0], - Relay1 = relays[1], - Relay2 = relays[2], - Relay3 = relays[3], - Relay4 = relays[4], - Relay5 = relays[5], - }; - } - - public Boolean WriteControl(Adam6060Control control) - { - try - { - OpenConnection(); - - Modbus!.WriteMultipleCoils(RelaysStartRegister, control.Relay0, - control.Relay1, - control.Relay2, - control.Relay3, - control.Relay4, - control.Relay5); - return true; - } - catch - { - CloseConnection(); - return false; - } } } \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6060/Adam6060Registers.cs b/csharp/Lib/Devices/Adam6060/Adam6060Registers.cs new file mode 100644 index 000000000..58511cf8f --- /dev/null +++ b/csharp/Lib/Devices/Adam6060/Adam6060Registers.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.Adam6060; + +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public class Adam6060Registers +{ + [DiscreteInput(1)] public Boolean DigitalInput0 { get; private set; } + [DiscreteInput(2)] public Boolean DigitalInput1 { get; private set; } + [DiscreteInput(3)] public Boolean DigitalInput2 { get; private set; } + [DiscreteInput(4)] public Boolean DigitalInput3 { get; private set; } + [DiscreteInput(5)] public Boolean DigitalInput4 { get; private set; } + [DiscreteInput(6)] public Boolean DigitalInput5 { get; private set; } + + [Coil(17)] public Boolean Relay0 { get; set; } + [Coil(18)] public Boolean Relay1 { get; set; } + [Coil(19)] public Boolean Relay2 { get; set; } + [Coil(20)] public Boolean Relay3 { get; set; } + [Coil(21)] public Boolean Relay4 { get; set; } + [Coil(22)] public Boolean Relay5 { get; set; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6060/Adam6060Status.cs b/csharp/Lib/Devices/Adam6060/Adam6060Status.cs deleted file mode 100644 index 5036fd1c6..000000000 --- a/csharp/Lib/Devices/Adam6060/Adam6060Status.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Adam6060; - -public class Adam6060Status : Adam6060Control -{ - internal const UInt16 DigitalInputsStartRegister = 1; - internal const UInt16 NbDigitalInputs = 6; - - public Boolean DigitalInput0 { get; init; } //Address(0X) 00001 - public Boolean DigitalInput1 { get; init; } //Address(0X) 00002 - public Boolean DigitalInput2 { get; init; } //Address(0X) 00003 - public Boolean DigitalInput3 { get; init; } //Address(0X) 00004 - public Boolean DigitalInput4 { get; init; } //Address(0X) 00005 - public Boolean DigitalInput5 { get; init; } //Address(0X) 00006 -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6060/Doc/ADAM-6000-Series_eng_man.pdf b/csharp/Lib/Devices/Adam6060/Doc/ADAM-6000-Series_eng_man.pdf new file mode 100644 index 000000000..f8a05f525 Binary files /dev/null and b/csharp/Lib/Devices/Adam6060/Doc/ADAM-6000-Series_eng_man.pdf differ diff --git a/csharp/Lib/Devices/Adam6360D/Adam6360D.csproj b/csharp/Lib/Devices/Adam6360D/Adam6360D.csproj new file mode 100644 index 000000000..0b06d7c44 --- /dev/null +++ b/csharp/Lib/Devices/Adam6360D/Adam6360D.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/csharp/Lib/Devices/Adam6360D/Adam6360DDevice.cs b/csharp/Lib/Devices/Adam6360D/Adam6360DDevice.cs new file mode 100644 index 000000000..7a91d9e87 --- /dev/null +++ b/csharp/Lib/Devices/Adam6360D/Adam6360DDevice.cs @@ -0,0 +1,19 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; + +namespace InnovEnergy.Lib.Devices.Adam6360D; + +public class Adam6360DDevice : ModbusDevice +{ + + public Adam6360DDevice(String hostname, Byte slaveId, UInt16 port = 502) : + this(new TcpChannel(hostname, port), slaveId) + { + } + + + public Adam6360DDevice(Channel channel, Byte slaveId) : base(new ModbusTcpClient(channel, slaveId)) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6360D/Adam6360DRegisters.cs b/csharp/Lib/Devices/Adam6360D/Adam6360DRegisters.cs new file mode 100644 index 000000000..9717e5b48 --- /dev/null +++ b/csharp/Lib/Devices/Adam6360D/Adam6360DRegisters.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.Adam6360D; + +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[AddressOffset(-1)] +public class Adam6360DRegisters +{ + [DiscreteInput(1)] public Boolean DigitalInput0 { get; private set; } + [DiscreteInput(2)] public Boolean DigitalInput1 { get; private set; } + [DiscreteInput(3)] public Boolean DigitalInput2 { get; private set; } + [DiscreteInput(4)] public Boolean DigitalInput3 { get; private set; } + [DiscreteInput(5)] public Boolean DigitalInput4 { get; private set; } + [DiscreteInput(6)] public Boolean DigitalInput5 { get; private set; } + [DiscreteInput(7)] public Boolean DigitalInput6 { get; private set; } + [DiscreteInput(8)] public Boolean DigitalInput7 { get; private set; } + [DiscreteInput(9)] public Boolean DigitalInput8 { get; private set; } + [DiscreteInput(10)] public Boolean DigitalInput9 { get; private set; } + [DiscreteInput(11)] public Boolean DigitalInput10 { get; private set; } + [DiscreteInput(12)] public Boolean DigitalInput11 { get; private set; } + [DiscreteInput(13)] public Boolean DigitalInput12 { get; private set; } + [DiscreteInput(14)] public Boolean DigitalInput13 { get; private set; } + + [Coil(33)] public Boolean Relay0 { get; set; } + [Coil(34)] public Boolean Relay1 { get; set; } + [Coil(35)] public Boolean Relay2 { get; set; } + [Coil(36)] public Boolean Relay3 { get; set; } + [Coil(37)] public Boolean Relay4 { get; set; } + [Coil(38)] public Boolean Relay5 { get; set; } + [Coil(39)] public Boolean Relay6 { get; set; } + [Coil(40)] public Boolean Relay7 { get; set; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Adam6360D/Doc/ADAM-6300_User_Manaul_Ed.2-FINAL.pdf b/csharp/Lib/Devices/Adam6360D/Doc/ADAM-6300_User_Manaul_Ed.2-FINAL.pdf new file mode 100644 index 000000000..886e8d5f2 Binary files /dev/null and b/csharp/Lib/Devices/Adam6360D/Doc/ADAM-6300_User_Manaul_Ed.2-FINAL.pdf differ diff --git a/csharp/Lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf b/csharp/Lib/Devices/Adam6360D/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf similarity index 100% rename from csharp/Lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf rename to csharp/Lib/Devices/Adam6360D/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf diff --git a/csharp/Lib/Devices/Adam6360D/Program.cs b/csharp/Lib/Devices/Adam6360D/Program.cs new file mode 100644 index 000000000..f0b3681da --- /dev/null +++ b/csharp/Lib/Devices/Adam6360D/Program.cs @@ -0,0 +1,20 @@ +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Adam6360D; + +public static class Program +{ + public static Task Main(String[] args) + { + var d = new Adam6360DDevice("localhost", 2, 5006); + + while (true) + { + var x = d.Read(); + x.ToCsv().WriteLine(); + } + + + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj b/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj index 92dd034ad..6df76f3b3 100644 --- a/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj +++ b/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj @@ -1,14 +1,12 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - - - - - - - - + + + + + + + + + +
\ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs b/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs index 97462b720..b22f5d2e5 100644 --- a/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs +++ b/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs @@ -1,48 +1,46 @@ +using System.IO.Ports; +using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using static InnovEnergy.Lib.Devices.Battery48TL.Constants; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Utils; +using static System.IO.Ports.Parity; namespace InnovEnergy.Lib.Devices.Battery48TL; -public class Battery48TlDevice +public class Battery48TlDevice: ModbusDevice { - private ModbusClient Modbus { get; } + public const Parity Parity = Odd; + public const Int32 StopBits = 1; + public const Int32 BaudRate = 115200; + public const Int32 DataBits = 8; + + + public Battery48TlDevice(String tty, Byte slaveId, SshHost host) : this + ( + channel: new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits), + slaveId + ) + {} - public Battery48TlDevice(String device, Byte nodeId) + public Battery48TlDevice(String tty, Byte slaveId, String? host = null) : this + ( + channel: host switch + { + null => new SerialPortChannel ( tty, BaudRate, Parity, DataBits, StopBits), + _ => new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits) + }, + slaveId + ) + {} + + public Battery48TlDevice(Channel channel, Byte slaveId) : this + ( + client: new ModbusRtuClient(channel, slaveId) + ) + {} + + public Battery48TlDevice(ModbusClient client): base(client) { - var serialConnection = new ModbusSerialConnection(device, - BaudRate, - Parity, - DataBits, - StopBits, - Constants.Timeout); - - Modbus = new ModbusRtuClient(serialConnection, nodeId); } - private Battery48TlDevice(ModbusClient modbus) // TODO : remove nullable - { - Modbus = modbus; - } - - public static Battery48TlDevice Fake() // TODO : remove nullable - { - return new Battery48TlDevice(null!); - } - - public Battery48TLStatus? ReadStatus() //Already try catch is implemented - { - try - { - return Modbus - .ReadInputRegisters(BaseAddress, NoOfRegisters) - .ParseBatteryStatus(); - } - catch (Exception e) - { - Console.WriteLine(e.Message + " Battery "); - Modbus.CloseConnection(); - return null; - } - } } \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs b/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs deleted file mode 100644 index da885cb32..000000000 --- a/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; - -namespace InnovEnergy.Lib.Devices.Battery48TL; - -using T = Battery48TLStatus; - -[SuppressMessage("ReSharper", "InconsistentNaming")] -public record Battery48TLStatus : BatteryStatus -{ - public Voltage CellsVoltage { get; init; } - - public Power MaxChargingPower { get; init; } - public Power MaxDischargingPower { get; init; } - - public LedState GreenLed { get; init; } - public LedState AmberLed { get; init; } - public LedState BlueLed { get; init; } - public LedState RedLed { get; init; } - - public IReadOnlyList Warnings { get; init; } = Array.Empty(); - public IReadOnlyList Alarms { get; init; } = Array.Empty(); - - public Boolean EocReached { get; init; } - public Boolean ConnectedToDc { get; init; } - public Boolean Heating { get; init; } - public TemperatureState TemperatureState { get; init; } // cold | operating temperature | overheated - - public Current TotalCurrent { get; init; } - - - - // TODO: strings - // TODO - // public State LimitedBy { get; init; } - - // TODO - // public Boolean AlarmOutActive { get; init; } - // public Boolean InternalFanActive { get; init; } - // public Boolean VoltMeasurementAllowed { get; init; } - // public Boolean AuxRelay { get; init; } - // public Boolean RemoteState { get; init; } - -} - - - - diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlDevices.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlDevices.cs new file mode 100644 index 000000000..44108e6fa --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TlDevices.cs @@ -0,0 +1,31 @@ +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +public class Battery48TlDevices +{ + private readonly IReadOnlyList _Devices; + + public Battery48TlDevices(IReadOnlyList devices) => _Devices = devices; + + public Battery48TlRecords Read() + { + try + { + var records = _Devices + .Select(d => d.Read()) + .ToArray(_Devices.Count); + + return new Battery48TlRecords(records); + } + catch (Exception e) + { + Console.WriteLine(e); + // TODO: log + + return Battery48TlRecords.Null; + } + + + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Api.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Api.cs new file mode 100644 index 000000000..40d925310 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Api.cs @@ -0,0 +1,196 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Battery48TL.DataTypes; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Power; +using static InnovEnergy.Lib.Devices.Battery48TL.DataTypes.LedState; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +using Strings = IReadOnlyList; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "ConvertToAutoProperty")] +public partial class Battery48TlRecord +{ + public Dc_ Dc => new Dc_(this); + public Leds_ Leds => new Leds_(this); + public Temperatures_ Temperatures => new Temperatures_(this); + + public Boolean ConnectedToDcBus => (_IoStates & 1) == 0; + public Boolean Eoc => Leds is { Green: On, Amber: Off, Blue : Off }; + + public Strings Warnings => ParseWarnings().OrderBy(w => w).ToList(); + public Strings Alarms => ParseAlarms() .OrderBy(w => w).ToList(); + + public Percent Soc => _Soc; + + public readonly struct Leds_ + { + public LedState Blue => Self.ParseLed(LedColor.Blue); + public LedState Red => Self.ParseLed(LedColor.Red); + public LedState Green => Self.ParseLed(LedColor.Green); + public LedState Amber => Self.ParseLed(LedColor.Amber); + + private Battery48TlRecord Self { get; } + internal Leds_(Battery48TlRecord self) => this.Self = self; + } + + public readonly struct Temperatures_ + { + public Boolean Heating => (Self._IoStates & 64) != 0; + public Temperature Board => Self._TemperaturesBoard; + public Cells_ Cells => new Cells_(Self); + + public TemperatureState State => Self.Leds switch + { + { Green: >= Blinking, Blue: >= Blinking } => TemperatureState.Cold, + _ => TemperatureState.Operation, + // TODO: overheated + }; + + internal Temperatures_(Battery48TlRecord self) => Self = self; + private Battery48TlRecord Self { get; } + } + + public readonly struct Cells_ + { + public Temperature Center => Self._TemperaturesCellsCenter; + public Temperature Left => Self._TemperaturesCellsLeft; + public Temperature Right => Self._TemperaturesCellsRight; + public Temperature Average => Self._TemperaturesCellsAverage; + + internal Cells_(Battery48TlRecord self) => Self = self; + private Battery48TlRecord Self { get; } + } + + public readonly struct Dc_ + { + public Voltage Voltage => Self._DcVoltage; + public Current Current => Self._DcCurrent; + public ActivePower Power => Self._DcVoltage * Self._DcCurrent; + + internal Dc_(Battery48TlRecord self) => Self = self; + private Battery48TlRecord Self { get; } + } + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private IEnumerable ParseAlarms() + { + Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0; + + if (HasBit(0) ) yield return "Tam : BMS temperature too low"; + if (HasBit(2) ) yield return "TaM2 : BMS temperature too high"; + if (HasBit(3) ) yield return "Tbm : Battery temperature too low"; + if (HasBit(5) ) yield return "TbM2 : Battery temperature too high"; + if (HasBit(7) ) yield return "VBm2 : Bus voltage too low"; + if (HasBit(9) ) yield return "VBM2 : Bus voltage too high"; + if (HasBit(11)) yield return "IDM2 : Discharge current too high"; + if (HasBit(12)) yield return "ISOB : Electrical insulation failure"; + if (HasBit(13)) yield return "MSWE : Main switch failure"; + if (HasBit(14)) yield return "FUSE : Main fuse blown"; + if (HasBit(15)) yield return "HTRE : Battery failed to warm up"; + if (HasBit(16)) yield return "TCPE : Temperature sensor failure"; + if (HasBit(17)) yield return "STRE :"; + if (HasBit(18)) yield return "CME : Current sensor failure"; + if (HasBit(19)) yield return "HWFL : BMS hardware failure"; + if (HasBit(20)) yield return "HWEM : Hardware protection tripped"; + if (HasBit(21)) yield return "ThM : Heatsink temperature too high"; + if (HasBit(22)) yield return "vsm1 : String voltage too low"; + if (HasBit(23)) yield return "vsm2 : Low string voltage failure"; + if (HasBit(25)) yield return "vsM2 : String voltage too high"; + if (HasBit(27)) yield return "iCM2 : Charge current too high"; + if (HasBit(29)) yield return "iDM2 : Discharge current too high"; + if (HasBit(31)) yield return "MID2 : String voltage unbalance too high"; + if (HasBit(33)) yield return "CCBF : Internal charger hardware failure"; + if (HasBit(34)) yield return "AhFL :"; + if (HasBit(36)) yield return "TbCM :"; + if (HasBit(37)) yield return "BRNF :"; + if (HasBit(42)) yield return "HTFS : Heater Fuse Blown"; + if (HasBit(43)) yield return "DATA : Parameters out of range"; + if (HasBit(45)) yield return "CELL2:"; + } + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private IEnumerable ParseWarnings() + { + Boolean HasBit(Int16 bit) => (_WarningFlags & 1uL << bit) > 0; + + if (HasBit(1) ) yield return "TaM1: BMS temperature high"; + if (HasBit(4) ) yield return "TbM1: Battery temperature high"; + if (HasBit(6) ) yield return "VBm1: Bus voltage low"; + if (HasBit(8) ) yield return "VBM1: Bus voltage high"; + if (HasBit(10)) yield return "IDM1: Discharge current high"; + if (HasBit(24)) yield return "vsM1: String voltage high"; + if (HasBit(26)) yield return "iCM1: Charge current high"; + if (HasBit(28)) yield return "iDM1: Discharge current high"; + if (HasBit(30)) yield return "MID1: String voltages unbalanced"; + if (HasBit(32)) yield return "BLPW: Not enough charging power on bus"; + if (HasBit(35)) yield return "Ah_W: String SOC low"; + if (HasBit(38)) yield return "MPMM: Midpoint wiring problem"; + if (HasBit(39)) yield return "TCMM:"; + if (HasBit(40)) yield return "TCdi: Temperature difference between strings high"; + if (HasBit(41)) yield return "WMTO:"; + if (HasBit(44)) yield return "bit44:"; + if (HasBit(46)) yield return "CELL1:"; + } + + + private Double CalcPowerLimitImposedByVoltageLimit(Double vLimit, Double rInt) + { + var v = Dc.Voltage; + var i = Dc.Current; + + var dv = vLimit - v; + var di = dv / rInt; + + return vLimit * (i + di); + } + + private Double CalcPowerLimitImposedByCurrentLimit(Double iLimit, Double rInt) + { + var v = Dc.Voltage; + var i = Dc.Current; + + var di = iLimit - i; + var dv = di * rInt; + + return iLimit * (v + dv); + } + + public DcPower MaxChargePower + { + get + { + var pLimits = new[] + { + CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMin), + CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMax), + CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMin), + CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMax) + }; + + var pLimit = pLimits.Min(); + + return Math.Max(pLimit, 0); + } + } + + public DcPower MaxDischargePower + { + get + { + var pLimits = new[] + { + CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMin), + CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMax), + CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMin), + CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMax), + }; + + var pLimit = pLimits.Max(); + + return Math.Min(pLimit, 0); + } + } +} + diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs new file mode 100644 index 000000000..f595e95f8 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TlRecord.Modbus.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Battery48TL.DataTypes; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using InnovEnergy.Lib.SrcGen.Attributes; + +namespace InnovEnergy.Lib.Devices.Battery48TL; +#pragma warning disable CS0169, CS0649 + + +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[BigEndian] +public partial class Battery48TlRecord +{ + [InputRegister(1004)] private UInt16 _LedStates; + [InputRegister(1005)] private UInt64 _WarningFlags; + [InputRegister(1009)] private UInt64 _AlarmFlags; + [InputRegister(1013)] private UInt16 _IoStates; + + [InputRegister(999, Scale = 0.01)] private Double _DcVoltage; + [InputRegister(1000, Scale = 0.01, Offset = -10000)] private Double _DcCurrent; + + [InputRegister(1053, Scale = 0.1)] private Double _Soc; + + [InputRegister(1014, Scale = 0.1, Offset = -400)] private Double _TemperaturesBoard; + [InputRegister(1015, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsCenter; + [InputRegister(1016, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsLeft; + [InputRegister(1017, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsRight; + [InputRegister(1003, Scale = 0.1, Offset = -400)] private Double _TemperaturesCellsAverage; + + private LedState ParseLed(LedColor led) => (LedState)((_LedStates >> (Int32)led) & 3); + + // public Decimal CellsVoltage { get; init; } + // + // public Decimal MaxChargingPower { get; init; } + // public Decimal MaxDischargingPower { get; init; } +} + + diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs new file mode 100644 index 000000000..5d5db55d2 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs @@ -0,0 +1,38 @@ +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +public class Battery48TlRecords +{ + public Battery48TlRecords(IReadOnlyList records) + { + var empty = records.Count == 0; + + Devices = records; + Eoc = !empty && records.All(r => r.Eoc); + Warnings = records.SelectMany(r => r.Warnings).Distinct().ToList(); + Alarms = records.SelectMany(r => r.Alarms).Distinct().ToList(); + Soc = empty ? 0 : records.Min(r => r.Soc.Value); + Temperature = records.Any() ? records.Average(b => b.Temperatures.Cells.Average.Value) : 0; + + Dc = empty + ? DcBus.FromVoltageCurrent(0, 0) + : DcBus.FromVoltageCurrent + ( + records.Average(r => r.Dc.Voltage), + records.Sum(r => r.Dc.Current) + ); + } + + public DcBus Dc { get; init; } + public Boolean Eoc { get; init; } + public IReadOnlyList Warnings { get; init; } + public IReadOnlyList Alarms { get; init; } + public Percent Soc { get; init; } + public Temperature Temperature { get; init; } + + public IReadOnlyList Devices { get; init; } + + public static Battery48TlRecords Null => new Battery48TlRecords(Array.Empty()); +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Constants.cs b/csharp/Lib/Devices/Battery48TL/Constants.cs deleted file mode 100644 index ea2e7501e..000000000 --- a/csharp/Lib/Devices/Battery48TL/Constants.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.IO.Ports; -using static System.IO.Ports.Parity; -using static System.IO.Ports.StopBits; - -namespace InnovEnergy.Lib.Devices.Battery48TL; - -[SuppressMessage("ReSharper", "InconsistentNaming")] -public static class Constants -{ - public const Int32 BaseAddress = 1000; - public const Int32 NoOfRegisters = 56; - - public const Parity Parity = Odd; - public const StopBits StopBits = One; - public const Int32 BaudRate = 115200; - public const Int32 DataBits = 8; - public static TimeSpan Timeout { get; } = TimeSpan.FromMilliseconds(100); - - public const Decimal VMax = 59.0m; - public const Decimal VMin = 42.0m; - public const Decimal AhPerString = 40.0m; - - private const Decimal RStringMin = 0.125m; - private const Decimal RStringMax = 0.250m; - private const Decimal IMaxPerString = 20.0m; - private const UInt16 NumberOfStrings = 5; - - public const Decimal RIntMin = RStringMin / NumberOfStrings; - public const Decimal RIntMax = RStringMax / NumberOfStrings; - public const Decimal IMax = NumberOfStrings * IMaxPerString; -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/CellTemperatures.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/CellTemperatures.cs new file mode 100644 index 000000000..8fccf3583 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/CellTemperatures.cs @@ -0,0 +1,11 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +public readonly struct CellTemperatures +{ + public Temperature Center { get; internal init; } + public Temperature Left { get; internal init; } + public Temperature Right { get; internal init; } +} + diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/Constants.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/Constants.cs new file mode 100644 index 000000000..defd08f83 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/Constants.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Ports; +using static System.IO.Ports.Parity; + +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public static class Constants +{ + public const Int32 BaseAddress = 1000; + public const Int32 NoOfRegisters = 56; + + public const Parity Parity = Odd; + public const Int32 StopBits = 1; + public const Int32 BaudRate = 115200; + public const Int32 DataBits = 8; + public static TimeSpan Timeout { get; } = TimeSpan.FromMilliseconds(100); + + public const Double VMax = 59.0; + public const Double VMin = 42.0; + public const Double AhPerString = 40.0; + + private const Double RStringMin = 0.125; + private const Double RStringMax = 0.250; + private const Double IMaxPerString = 20.0; + private const UInt16 NumberOfStrings = 5; + + public const Double RIntMin = RStringMin / NumberOfStrings; + public const Double RIntMax = RStringMax / NumberOfStrings; + public const Double IMax = NumberOfStrings * IMaxPerString; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/LedColor.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/LedColor.cs new file mode 100644 index 000000000..595e67f38 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/LedColor.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +public enum LedColor +{ + Green = 0, // don't change: numbers are important + Amber = 2, + Blue = 4, + Red = 6, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/LedState.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/LedState.cs new file mode 100644 index 000000000..07ff0d01c --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/LedState.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +public enum LedState +{ + Off = 0, + On = 1, + Blinking = 2, + BlinkingFast = 3 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/Leds.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/Leds.cs new file mode 100644 index 000000000..a3edab38b --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/Leds.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +public readonly struct Leds +{ + public LedState Blue { get; internal init; } + public LedState Green { get; internal init; } + public LedState Amber { get; internal init; } + public LedState Red { get; internal init; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/DataTypes/TemperatureState.cs b/csharp/Lib/Devices/Battery48TL/DataTypes/TemperatureState.cs new file mode 100644 index 000000000..dd915a33f --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/DataTypes/TemperatureState.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.Devices.Battery48TL.DataTypes; + +public enum TemperatureState +{ + Cold = 0, + Operation = 1, + Overheated = 2, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Doc/48TL200 ModBus protocol_rev.11-1.pdf b/csharp/Lib/Devices/Battery48TL/Doc/48TL200 ModBus protocol_rev.11-1.pdf new file mode 100644 index 000000000..a08d9e1ef Binary files /dev/null and b/csharp/Lib/Devices/Battery48TL/Doc/48TL200 ModBus protocol_rev.11-1.pdf differ diff --git a/csharp/Lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf b/csharp/Lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf deleted file mode 100644 index 568e5d84f..000000000 Binary files a/csharp/Lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf and /dev/null differ diff --git a/csharp/Lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf b/csharp/Lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf deleted file mode 100644 index 71ae84f67..000000000 Binary files a/csharp/Lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf and /dev/null differ diff --git a/csharp/Lib/Devices/Battery48TL/Doc/configure_tty.txt b/csharp/Lib/Devices/Battery48TL/Doc/configure_tty.txt new file mode 100644 index 000000000..0c7e944ef --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Doc/configure_tty.txt @@ -0,0 +1,2 @@ + +stty -F /dev/ttyUSB0 <<< '0:0:1bb2:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0' diff --git a/csharp/Lib/Devices/Battery48TL/LedColor.cs b/csharp/Lib/Devices/Battery48TL/LedColor.cs deleted file mode 100644 index 5c91fa53d..000000000 --- a/csharp/Lib/Devices/Battery48TL/LedColor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Battery48TL; - -public enum LedColor -{ - Green = 0, - Amber = 1, - Blue = 2, - Red = 3, -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/LedState.cs b/csharp/Lib/Devices/Battery48TL/LedState.cs deleted file mode 100644 index 937c9e7e4..000000000 --- a/csharp/Lib/Devices/Battery48TL/LedState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Battery48TL; - -public enum LedState -{ - Off = 0, - On = 1, - BlinkingSlow = 2, - BlinkingFast = 3 -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/ModbusParser.cs b/csharp/Lib/Devices/Battery48TL/ModbusParser.cs deleted file mode 100644 index 6877c6a05..000000000 --- a/csharp/Lib/Devices/Battery48TL/ModbusParser.cs +++ /dev/null @@ -1,274 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; -using InnovEnergy.Lib.Units.Composite; -using InnovEnergy.Lib.Utils; -using static InnovEnergy.Lib.Devices.Battery48TL.LedState; - -namespace InnovEnergy.Lib.Devices.Battery48TL; - -public static class ModbusParser -{ - internal static Battery48TLStatus ParseBatteryStatus(this ModbusRegisters data) - { - var greenLed = data.ParseLedState(register: 1005, led: LedColor.Green); - var amberLed = data.ParseLedState(register: 1005, led: LedColor.Amber); - var blueLed = data.ParseLedState(register: 1005, led: LedColor.Blue); - var redLed = data.ParseLedState(register: 1005, led: LedColor.Red); - - var soc = data.ParseSoc(); - - // var eoc = greenLed is On - // && amberLed is Off - // && blueLed is Off; - - var eoc = data.ParseEocReached(); - - var maxSoc = eoc ? 100m : 99.9m; - - var batteryCold = greenLed >= BlinkingSlow - && blueLed >= BlinkingSlow; - - var temperatureState = batteryCold - ? TemperatureState.Cold - : TemperatureState.OperatingTemperature; // TODO: overheated - - - return new Battery48TLStatus - { - Dc = data.ParseDcBus(), - Alarms = data.ParseAlarms().ToList(), - Warnings = data.ParseWarnings().ToList(), - Soc = Math.Min(soc, maxSoc), - Temperature = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400), - GreenLed = greenLed, - AmberLed = amberLed, - BlueLed = blueLed, - RedLed = redLed, - Heating = data.ParseBool(baseRegister: 1014, bit: 6), - ConnectedToDc = data.ParseBool(baseRegister: 1014, bit: 0), - TemperatureState = temperatureState, - MaxChargingPower = data.CalcMaxChargePower(), - MaxDischargingPower = data.CalcMaxDischargePower(), - CellsVoltage = data.ParseDecimal(register: 1000, scaleFactor: 0.01m), - TotalCurrent = data.ReadTotalCurrent(), - EocReached = eoc - }; - } - - - public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0) - { - var value = data[register].ConvertTo(); // widen to 32bit signed - - if (value >= 0x8000) - value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&! - - return (Decimal)(value + offset) * scaleFactor; - } - - internal static Decimal ParseCurrent(this ModbusRegisters data) - { - return data.ParseDecimal(register: 1001, scaleFactor: 0.01m, offset: -10000); - } - - internal static Decimal ParseBusVoltage(this ModbusRegisters data) - { - return data.ParseDecimal(register: 1002, scaleFactor: 0.01m); - } - - internal static Decimal ReadTotalCurrent(this ModbusRegisters data) - { - try - { - return ParseDecimal(data, register: 1063, scaleFactor: 0.01m, offset: -100); - } - catch (Exception e) - { - Console.WriteLine(e + " Read Total current fail "); - throw; - } - } - - internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit) - { - var x = bit / 16; - var y = bit % 16; - - var value = (UInt32)data[baseRegister + x]; - - return (value & (1 << y)) > 0; - } - - internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led) - { - var lo = data.ParseBool(register, (led.ConvertTo() * 2 ).ConvertTo()); - var hi = data.ParseBool(register, (led.ConvertTo() * 2 + 1).ConvertTo()); - - return (hi, lo) switch - { - (false, false) => Off, - (false, true) => On, - (true, false) => BlinkingSlow, - (true, true) => BlinkingFast, - }; - } - - internal static String ParseString(this ModbusRegisters data, Int32 register, Int16 count) - { - return Enumerable - .Range(register, count) - .Select(i => data[i]) - .Select(BitConverter.GetBytes) - .Select(Encoding.ASCII.GetString) - .Aggregate("", (a, b) => a + b[1] + b[0]); // endian swap - } - - internal static Boolean ParseEocReached(this ModbusRegisters data) - { - var s = ParseString(data, 1061, 2); - return "EOC_" == s; - } - - internal static Decimal ParseSoc(this ModbusRegisters data) - { - return data.ParseDecimal(register: 1054, scaleFactor: 0.1m); - } - - private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt) - { - var dv = vLimit - v; - var di = dv / rInt; - var pLimit = vLimit * (i + di); - - return pLimit; - } - - private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt) - { - var di = iLimit - i; - var dv = di * rInt; - var pLimit = iLimit * (v + dv); - - return pLimit; - } - - - private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint) - { - // const Int32 holdZone = 300; - // const Int32 maxAllowedTemp = 315; - - var kp = 0.05m; - var error = setpoint - power; - var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m)); - - return controlOutput; - - // var a = holdZone - maxAllowedTemp; - // var b = -a * maxAllowedTemp; - } - - internal static Decimal CalcMaxChargePower(this ModbusRegisters data) - { - var v = data.ParseDecimal(register: 1000, scaleFactor: 0.01m); - var i = ParseCurrent(data); - - var pLimits = new[] - { - // TODO: review - CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin), - CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax), - CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin), - CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax) - }; - - var pLimit = pLimits.Min(); - - return Math.Max(pLimit, 0); - } - - internal static DcBus ParseDcBus(this ModbusRegisters data) => new() - { - Current = data.ParseCurrent(), - Voltage = data.ParseBusVoltage(), - }; - - internal static Decimal CalcMaxDischargePower(this ModbusRegisters data) - { - var v = data.ParseDecimal(register: 1000, scaleFactor: 0.01m); - var i = ParseCurrent(data); - - var pLimits = new[] - { - // TODO: review - CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin), - CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax), - CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin), - CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax), - // CalcPowerLimitImposedByTempLimit(t,315,300) - }; - - var pLimit = pLimits.Max(); - - return Math.Min(pLimit, 0); - } - - - [SuppressMessage("ReSharper", "StringLiteralTypo")] - internal static IEnumerable ParseAlarms(this ModbusRegisters data) - { - if (data.ParseBool(1010, 0)) yield return "Tam : BMS temperature too low"; - if (data.ParseBool(1010, 2)) yield return "TaM2 : BMS temperature too high"; - if (data.ParseBool(1010, 3)) yield return "Tbm : Battery temperature too low"; - if (data.ParseBool(1010, 5)) yield return "TbM2 : Battery temperature too high"; - if (data.ParseBool(1010, 7)) yield return "VBm2 : Bus voltage too low"; - if (data.ParseBool(1010, 9)) yield return "VBM2 : Bus voltage too high"; - if (data.ParseBool(1010, 11)) yield return "IDM2 : Discharge current too high"; - if (data.ParseBool(1010, 12)) yield return "ISOB : Electrical insulation failure"; - if (data.ParseBool(1010, 13)) yield return "MSWE : Main switch failure"; - if (data.ParseBool(1010, 14)) yield return "FUSE : Main fuse blown"; - if (data.ParseBool(1010, 15)) yield return "HTRE : Battery failed to warm up"; - if (data.ParseBool(1010, 16)) yield return "TCPE : Temperature sensor failure"; - if (data.ParseBool(1010, 17)) yield return "STRE :"; - if (data.ParseBool(1010, 18)) yield return "CME : Current sensor failure"; - if (data.ParseBool(1010, 19)) yield return "HWFL : BMS hardware failure"; - if (data.ParseBool(1010, 20)) yield return "HWEM : Hardware protection tripped"; - if (data.ParseBool(1010, 21)) yield return "ThM : Heatsink temperature too high"; - if (data.ParseBool(1010, 22)) yield return "vsm1 : String voltage too low"; - if (data.ParseBool(1010, 23)) yield return "vsm2 : Low string voltage failure"; - if (data.ParseBool(1010, 25)) yield return "vsM2 : String voltage too high"; - if (data.ParseBool(1010, 27)) yield return "iCM2 : Charge current too high"; - if (data.ParseBool(1010, 29)) yield return "iDM2 : Discharge current too high"; - if (data.ParseBool(1010, 31)) yield return "MID2 : String voltage unbalance too high"; - if (data.ParseBool(1010, 33)) yield return "CCBF : Internal charger hardware failure"; - if (data.ParseBool(1010, 34)) yield return "AhFL :"; - if (data.ParseBool(1010, 36)) yield return "TbCM :"; - if (data.ParseBool(1010, 37)) yield return "BRNF :"; - if (data.ParseBool(1010, 42)) yield return "HTFS : If Heaters Fuse Blown"; - if (data.ParseBool(1010, 43)) yield return "DATA : Parameters out of range"; - if (data.ParseBool(1010, 45)) yield return "CELL2:"; - } - - [SuppressMessage("ReSharper", "StringLiteralTypo")] - internal static IEnumerable ParseWarnings(this ModbusRegisters data) - { - if (data.ParseBool(1006, 1)) yield return "TaM1: BMS temperature high"; - if (data.ParseBool(1006, 4)) yield return "TbM1: Battery temperature high"; - if (data.ParseBool(1006, 6)) yield return "VBm1: Bus voltage low"; - if (data.ParseBool(1006, 8)) yield return "VBM1: Bus voltage high"; - if (data.ParseBool(1006, 10)) yield return "IDM1: Discharge current high"; - if (data.ParseBool(1006, 24)) yield return "vsM1: String voltage high"; - if (data.ParseBool(1006, 26)) yield return "iCM1: Charge current high"; - if (data.ParseBool(1006, 28)) yield return "iDM1: Discharge current high"; - if (data.ParseBool(1006, 30)) yield return "MID1: String voltages unbalanced"; - if (data.ParseBool(1006, 32)) yield return "BLPW: Not enough charging power on bus"; - if (data.ParseBool(1006, 35)) yield return "Ah_W: String SOC low"; - if (data.ParseBool(1006, 38)) yield return "MPMM: Midpoint wiring problem"; - if (data.ParseBool(1006, 39)) yield return "TCMM:"; - if (data.ParseBool(1006, 40)) yield return "TCdi: Temperature difference between strings high"; - if (data.ParseBool(1006, 41)) yield return "WMTO:"; - if (data.ParseBool(1006, 44)) yield return "bit44:"; - if (data.ParseBool(1006, 46)) yield return "CELL1:"; - } -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Program.cs b/csharp/Lib/Devices/Battery48TL/Program.cs new file mode 100644 index 000000000..c82568a96 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Program.cs @@ -0,0 +1,35 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; +using static InnovEnergy.Lib.Devices.Battery48TL.Battery48TlDevice; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +public static class Program +{ + public static Task Main(string[] args) + { + var host = new SshHost("10.2.3.115", "ie-entwicklung"); + var channel = new RemoteSerialChannel(host, "ttyUSB0", BaudRate, Parity, DataBits, StopBits); + + var nodes = new Byte[] { 2 }; + + var devices = nodes + .Select(n => new Battery48TlDevice(channel, n)) + .ToList(); + + var d = new Battery48TlDevices(devices); + + //var options = new JsonSerializerOptions { WriteIndented = true, Converters = { new JsonStringEnumConverter() }}; + + + while (true) + { + var x = d.Read(); + x.ToCsv().WriteLine(); + + //(x, options).Apply(JsonSerializer.Serialize).WriteLine(); + } + } + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/TemperatureState.cs b/csharp/Lib/Devices/Battery48TL/TemperatureState.cs deleted file mode 100644 index 6bb71b4e1..000000000 --- a/csharp/Lib/Devices/Battery48TL/TemperatureState.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Battery48TL; - -public enum TemperatureState -{ - Cold = 0, - OperatingTemperature = 1, - Overheated = 2, -} \ No newline at end of file diff --git a/csharp/Lib/Devices/EmuMeter/Conversions.cs b/csharp/Lib/Devices/EmuMeter/Conversions.cs deleted file mode 100644 index 9844875eb..000000000 --- a/csharp/Lib/Devices/EmuMeter/Conversions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using InnovEnergy.Lib.Protocols.Modbus.Conversions; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.Lib.Devices.EmuMeter; - -public static class Conversions -{ - - // TODO: integrate into ModbusRegisters - - public static IReadOnlyList ToSingles(this ModbusRegisters regs) - { - return regs - .Chunk(2) - .Select(c => c.Reverse().SelectMany(BitConverter.GetBytes).ToArray()) - .Select(d => BitConverter.ToSingle(d)) - .ToList(); - } - - public static IReadOnlyList ToDecimals(this ModbusRegisters regs) - { - return regs - .Chunk(2) - .Select(c => c.Reverse().SelectMany(BitConverter.GetBytes).ToArray()) - .Select(d => BitConverter.ToSingle(d)) - .Select(d => d.ConvertTo()) - .ToList(); - } - - // ReSharper disable once InconsistentNaming - public static IReadOnlyList ToUInt64s(this ModbusRegisters regs) - { - return regs - .SelectMany(d => BitConverter.GetBytes(d).Reverse()) - .Chunk(8) - .Select(c => c.Reverse().ToArray()) - .Select(d => BitConverter.ToUInt64(d)) - .ToList(); - } - -} \ No newline at end of file diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj b/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj index 92dd034ad..379b280a0 100644 --- a/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj +++ b/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj @@ -1,9 +1,7 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - + + + diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs index f26117c3f..9bf0ec45b 100644 --- a/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs @@ -1,97 +1,49 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Units.Composite; -using static DecimalMath.DecimalEx; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; namespace InnovEnergy.Lib.Devices.EmuMeter; -public class EmuMeterDevice +public class EmuMeterDevice: ModbusDevice { - private ModbusTcpClient Modbus { get; } - - public EmuMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) + public EmuMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) : this(new TcpChannel(hostname, port), slaveId) + { + } + + public EmuMeterDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId)) + { + } + + public EmuMeterDevice(ModbusClient client) : base(client) { - var connection = new ModbusTcpConnection(hostname, port); - Modbus = new ModbusTcpClient(connection, slaveId); } - public EmuMeterStatus? ReadStatus() + + public new EmuMeterRegisters? Read() { try { - return TryReadStatus(); + return base.Read(); } - catch (Exception) + catch (Exception e) { - Modbus.CloseConnection(); + // TODO: Log + Console.WriteLine(e); return null; } } - - - //private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos); - private EmuMeterStatus TryReadStatus() + + public new void Write(EmuMeterRegisters registers) { - // Console.WriteLine("Reading Emu Meter Data"); - - - // TODO: get SerialNb, depends on Little/Big Endian support in Modbus Lib - // var registers = Modbus.ReadHoldingRegisters(5001, 4); - // var id = registers.GetInt32(5001); - - var powerCurrent = Modbus.ReadHoldingRegisters(9000, 108).ToDecimals(); // TODO "ModbusRegisters" - var voltageFreq = Modbus.ReadHoldingRegisters(9200, 112).ToDecimals(); // To check with Ivo - - // var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s(); - - var activePowerL1 = powerCurrent[1]; - var activePowerL2 = powerCurrent[2]; - var activePowerL3 = powerCurrent[3]; - var reactivePowerL1 = powerCurrent[6]; - var reactivePowerL2 = powerCurrent[7]; - var reactivePowerL3 = powerCurrent[8]; - - var currentL1 = powerCurrent[51]; - var currentL2 = powerCurrent[52]; - var currentL3 = powerCurrent[53]; - - var voltageL1N = voltageFreq[0]; - var voltageL2N = voltageFreq[1]; - var voltageL3N = voltageFreq[2]; - var frequency = voltageFreq[55]; - - - var l1 = new AcPhase + try { - Current = currentL1, - Voltage = voltageL1N, - Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works - }; - var l2 = new AcPhase + base.Write(registers); + } + catch (Exception e) { - Current = currentL2, - Voltage = voltageL2N, - Phi = ATan2(reactivePowerL2, activePowerL2) - }; - var l3 = new AcPhase - { - Current = currentL3, - Voltage = voltageL3N, - Phi = ATan2(reactivePowerL3, activePowerL3) - }; - - return new EmuMeterStatus - { - Ac = new Ac3Bus - { - Frequency = frequency, - L1 = l1, - L2 = l2, - L3 = l3 - } - }; - + // TODO: Log + Console.WriteLine(e); + } } - } \ No newline at end of file diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterRegisters.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterRegisters.cs new file mode 100644 index 000000000..d27007bf9 --- /dev/null +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterRegisters.cs @@ -0,0 +1,68 @@ +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using InnovEnergy.Lib.StatusApi.DeviceTypes; +using InnovEnergy.Lib.Units.Composite; + +#pragma warning disable CS0649 + +namespace InnovEnergy.Lib.Devices.EmuMeter; + +using Float32 = Single; + + +[AddressOffset(-2)] // why? +public class EmuMeterRegisters : IAc3Meter +{ + [HoldingRegister(9002)] private Float32 _ActivePowerL1; + [HoldingRegister(9004)] private Float32 _ActivePowerL2; + [HoldingRegister(9006)] private Float32 _ActivePowerL3; + + [HoldingRegister(9012)] private Float32 _ReactivePowerL1; + [HoldingRegister(9014)] private Float32 _ReactivePowerL2; + [HoldingRegister(9016)] private Float32 _ReactivePowerL3; + + [HoldingRegister(9022)] private Float32 _ApparentPowerL1; + [HoldingRegister(9024)] private Float32 _ApparentPowerL2; + [HoldingRegister(9026)] private Float32 _ApparentPowerL3; + + [HoldingRegister(9102)] private Float32 _CurrentL1; + [HoldingRegister(9104)] private Float32 _CurrentL2; + [HoldingRegister(9106)] private Float32 _CurrentL3; + + [HoldingRegister(9200)] private Float32 _VoltageL1N; + [HoldingRegister(9202)] private Float32 _VoltageL2N; + [HoldingRegister(9204)] private Float32 _VoltageL3N; + + [HoldingRegister(9310)] private Float32 _Frequency; + + public Ac3Bus Ac => Ac3Bus.FromPhasesAndFrequency + ( + l1: AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + _VoltageL1N, + _CurrentL1, + _ActivePowerL1, + _ReactivePowerL1, + _ApparentPowerL1 + ), + l2: AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + _VoltageL2N, + _CurrentL2, + _ActivePowerL2, + _ReactivePowerL2, + _ApparentPowerL2 + ), + l3: AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + _VoltageL3N, + _CurrentL3, + _ActivePowerL3, + _ReactivePowerL3, + _ApparentPowerL3 + ), + frequency: _Frequency + ); +} + + + diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs deleted file mode 100644 index 1290843af..000000000 --- a/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs +++ /dev/null @@ -1,10 +0,0 @@ -using InnovEnergy.Lib.StatusApi; - -namespace InnovEnergy.Lib.Devices.EmuMeter; - -public record EmuMeterStatus : PowerMeterStatus -{ - // TODO: additional Measurements, device id -} - - diff --git a/csharp/Lib/Devices/EmuMeter/Program.cs b/csharp/Lib/Devices/EmuMeter/Program.cs new file mode 100644 index 000000000..604671b5d --- /dev/null +++ b/csharp/Lib/Devices/EmuMeter/Program.cs @@ -0,0 +1,24 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.EmuMeter; + +public static class Program +{ + public static Task Main(string[] args) + { + var ch = new TcpChannel("localhost", 5004); + var cl = new ModbusTcpClient(ch, 1); + var d = new EmuMeterDevice(cl); + + while (true) + { + var x = d.Read(); + x.ToCsv().WriteLine(); + } + + + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/ControlRecord.cs b/csharp/Lib/Devices/Trumpf/SystemControl/ControlRecord.cs deleted file mode 100644 index b5879384e..000000000 --- a/csharp/Lib/Devices/Trumpf/SystemControl/ControlRecord.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; - -public record ControlRecord -{ - private static readonly TimeSpan DefaultCommunicationTimeOut = TimeSpan.FromSeconds(10); - - // TODO - - // public UInt32 Date { get; init;} - // public UInt32 Time { get; init;} - // public UInt32 IpAddress { get; init;} //= 0x C0A80102; - // public UInt32 Subnet { get; init;} //= 0x FFFFFF00; - // public UInt32 Gateway { get; init;} //= 0x C0A80102; - // public Boolean ResetParamToDefault { get; init;} = false ; // Coil - // public TimeSpan CommunicationTimeout { get; init;} = DefaultCommunicationTimeOut; - // public Boolean FactoryResetParameters { get; init;} = false; - // public SystemConfig ConnectedSystemConfig { get; init;} = 0; - // public UInt16 UpdateSwTrigger { get; init;} = 0; - // public UInt16 AutomaticSwUpdate { get; init;} = 0; - // public UInt16 CustomerValuesSaveReset { get; init;} = 0; - // public UInt16 SerialNumberSystemControl { get; init;} = 0; - // public UInt16 SerialNumberAcDc { get; init;} = 0; - // public UInt16 IntegrationLevel { get; init;} = 0; - // public UInt16 IlBuildnumber { get; init;} = 0; - // public Boolean PowerStageEnable { get; init;} = true; - // public SymmetricAcOperationMode SetValueConfig { get; init;} = 0; - // public Boolean ResetsAlarmAndWarning { get; init;} = false; - // public PreChargeDcLinkConfig PreChargeDcLinkConfig { get; init;} = (PreChargeDcLinkConfig)0; - // public PowerFactorConvention PowerFactorConvention { get; init;} = 0; //0 = producer - // public UInt16 SlaveAddress { get; init;} = Slave.Broadcast; - // public ErrorPolicy ErrorHandlingPolicy { get; init;} = 0; - // public AcDcGridType GridType { get; init;} = 0; - // public UInt16 SubSlaveAddress { get; init;} = 0; - // public Boolean UseModbusSlaveIdForAddressing { get; init;} = false; - // public UInt16 SubSlaveErrorPolicy { get; init;} = 0; // must be an enum - // public Decimal SignedPowerNominalValue { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal SignedPowerSetValueL1 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal SignedPowerSetValueL2 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal SignedPowerSetValueL3 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal PowerSetValue { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal PowerSetValueL1 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal PowerSetValueL2 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal PowerSetValueL3 { get; init;} = 0; // resolution 0.001 and Unit kva, - // public Decimal MaximumGridCurrentRmsL1 { get; init;} = 0; // resolution : 0.01 - // public Decimal MaximumGridCurrentRmsL2 { get; init;} = 0; // resolution : 0.01 - // public Decimal MaximumGridCurrentRmsL3 { get; init;} = 0; // resolution : 0.01 - // public Decimal CosPhiSetValueL1 { get; init;} = 0; // resolution : 0.01 - // public Decimal CosPhiSetValueL2 { get; init;} = 0; // resolution : 0.01 - // public Decimal CosPhiSetValueL3 { get; init;} = 0; // resolution : 0.01 - // public Boolean PhaseL1IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - // public Boolean PhaseL2IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - // public Boolean PhaseL3IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - // public Boolean PhasesAreCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - // public Double SetPointCosPhi { get; init;} = 0; // resolution 0.01 - // public Double SetPointSinPhi { get; init;} = 0; // resolution 0.01 - // public Double SetPointSinPhiL1 { get; init;} = 0; // resolution 0.01 - // public Double SetPointSinPhiL2 { get; init;} = 0; // resolution 0.01 - // public Double SetPointSinPhiL3 { get; init;} = 0; // resolution 0.01 - // public Decimal FrequencyOffsetIm { get; init;} = 0; // resolution 0.01 - // public UInt16 VoltageAdjustmentFactorIm { get; init;} = 0; - // public UInt16 PreChargeDcLinkVoltage { get; init;} = 0; - // public Decimal MaxPeakCurrentVoltageControlL1 { get; init;} = 0; // resolution 0.01 - // public Decimal MaxPeakCurrentVoltageControlL2 { get; init;} = 0; // resolution 0.01 - // public Decimal MaxPeakCurrentVoltageControlL3 { get; init;} = 0; // resolution 0.01 - // public UInt16 GridFormingMode { get; init;} = 1; // 0 = not grid-forming (grid-tied) ,1 = grid-forming - // public UInt16 DcLinkRefVoltage { get; init;} = 800; - // public UInt16 DcLinkMinVoltage { get; init;} = 780; - // public UInt16 DcLinkMaxVoltage { get; init;} = 820; - // public UInt16 DcVoltageRefUs { get; init;} = 900; - // public UInt16 DcMinVoltageUs { get; init;} = 880; - // public UInt16 DcMaxVoltageUs { get; init;} = 920; - // // Need to discuss this with Ivo - // // public UInt16 FrequencySlopeIslandMode { get; init;} = 200; // resolution 0.01 - // // public UInt16 VoltageSlopeIslandMode { get; init;} = 500; // resolution 0.01 - // public UInt16 AcDcGcBypassMode { get; init;} = 0; - // public UInt16 AcDcGcPMaxThresholdPercent { get; init;} = 0; // resolution 0.01 - // public UInt16 AcDcGcStartupRampEnable { get; init;} = 0; - // public DcStageConfiguration DcConfigModule { get; init;} = 0; // this must be an enum - // public UInt16 DcDcPowerDistribution { get; init;} = 0; // 0.1 resolution - // public AcDcDistributionMode AcDcDistributionMode { get; init;} = 0; - - - -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/AlarmMessage.cs similarity index 88% rename from csharp/Lib/Devices/Trumpf/SystemControl/AlarmMessage.cs rename to csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/AlarmMessage.cs index d63b5dfb8..7ec586580 100644 --- a/csharp/Lib/Devices/Trumpf/SystemControl/AlarmMessage.cs +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/AlarmMessage.cs @@ -1,18 +1,18 @@ using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; [SuppressMessage("ReSharper", "UnusedMember.Global")] -public enum AlarmMessage +public enum AlarmMessage : UInt16 { NoAlarm = 0, BmsCommunicationTimeoutHasOccured = 40302, // BMS communication timeout has occured. Rs485CommunicationAlarm = 40303, // RS-485 communication alarm. + NoSlaveModuleWasFoundPleaseCheckRs485Connection = 40304, // No slave module was found, please check RS-485 connection(s). + NumberOfOrCombinationOfConnectedSlaveTypesNotSupported = 40305, // Number of or combination of connected slave types not supported. SoftwareVersionsOfSystemControlAndModulesDoNotMatch1 = 40412, // Software versions of system control and module(s) do not match. SoftwareVersionsOfSystemControlAndModulesDoNotMatch2 = 40413, // Software versions of system control and module(s) do not match. SoftwareVersionsOfSystemControlAndModulesDoNotMatch3 = 40414, // Software versions of system control and module(s) do not match. SoftwareVersionsOfSystemControlAndModulesDoNotMatch4 = 40415, // Software versions of system control and module(s) do not match. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch5 = 40416, // Software versions of system control and module(s) do not match. - NoSlaveModuleWasFoundPleaseCheckRs485Connection = 40304, // No slave module was found, please check RS-485 connection(s). - NumberOfOrCombinationOfConnectedSlaveTypesNotSupported = 40305, // Number of or combination of connected slave types not supported. + SoftwareVersionsOfSystemControlAndModulesDoNotMatch5 = 40416, // Software versions of system control and module(s) do not match. } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/MainState.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/DeviceState.cs similarity index 52% rename from csharp/Lib/Devices/Trumpf/TruConvert/MainState.cs rename to csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/DeviceState.cs index a4cfa89e5..630853c59 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvert/MainState.cs +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/DeviceState.cs @@ -1,6 +1,6 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; -public enum MainState : UInt16 +public enum DeviceState : UInt16 { PowerUp = 0, Alarm = 1, diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcGridType.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/GridType.cs similarity index 73% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcGridType.cs rename to csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/GridType.cs index cbb766adf..edcc5f036 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcGridType.cs +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/GridType.cs @@ -1,6 +1,6 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; -public enum AcDcGridType : UInt16 +public enum GridType : UInt16 { GridTied400V50Hz = 0, // grid-tied, 400V, 50Hz GridTied480V60Hz = 1, // grid-tied, 480V, 60Hz diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointActivation.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointActivation.cs new file mode 100644 index 000000000..88de61135 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointActivation.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +public enum PowerSetPointActivation : UInt16 +{ + Immediate = 0, + Trigger = 1, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointTrigger.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointTrigger.cs new file mode 100644 index 000000000..d1604bf4f --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/PowerSetPointTrigger.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +public enum PowerSetPointTrigger : UInt16 +{ + Wait = 0, + Trigger = 1, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/ReferenceFrame.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/ReferenceFrame.cs new file mode 100644 index 000000000..fb78475c6 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/ReferenceFrame.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +public enum ReferenceFrame : UInt16 +{ + Producer = 0, + Consumer = 1, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SlaveErrorHandling.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SlaveErrorHandling.cs new file mode 100644 index 000000000..c076b0b1f --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SlaveErrorHandling.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +public enum SlaveErrorHandling : UInt16 +{ + Relaxed = 0, // System keeps running even if some slaves are in error state. + Strict = 1, // System shuts down as soon as one component is in error state. +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SubSlaveErrorHandling.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SubSlaveErrorHandling.cs new file mode 100644 index 000000000..7897fae09 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SubSlaveErrorHandling.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +public enum SubSlaveErrorHandling : UInt16 +{ + Strict = 0, // SystemControl switches to error state if at least one submodules is in error state + Relaxed = 1, // SystemControl switches to error state if all submodules are in error state. + Off = 2, // If possible AC-DC module continues operation even if all submodules are in error state. +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/SystemConfig.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SystemConfig.cs similarity index 53% rename from csharp/Lib/Devices/Trumpf/TruConvert/SystemConfig.cs rename to csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SystemConfig.cs index 4967389e0..c380a851f 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvert/SystemConfig.cs +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/SystemConfig.cs @@ -1,9 +1,9 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; public enum SystemConfig : UInt16 { NoConfig = 0, Simulator = 1, DcDcOnly = 2, - AcDcAndDcDc = 3, + AcDcAndDcDc = 3 } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/WarningMessage.cs new file mode 100644 index 000000000..4566d665e --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/DataTypes/WarningMessage.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public enum WarningMessage : UInt16 +{ + NoWarning = 0, + // TODO: ??? +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/Doc/TRUMPF_Grid-Codes_TruConvert_AC_3025.pdf b/csharp/Lib/Devices/Trumpf/SystemControl/Doc/TRUMPF_Grid-Codes_TruConvert_AC_3025.pdf new file mode 100644 index 000000000..755be78c4 Binary files /dev/null and b/csharp/Lib/Devices/Trumpf/SystemControl/Doc/TRUMPF_Grid-Codes_TruConvert_AC_3025.pdf differ diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/ErrorPolicy.cs b/csharp/Lib/Devices/Trumpf/SystemControl/ErrorPolicy.cs deleted file mode 100644 index 9a57a9f3d..000000000 --- a/csharp/Lib/Devices/Trumpf/SystemControl/ErrorPolicy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; - -public enum ErrorPolicy : UInt16 -{ - Relaxed = 0, // 0 = relaxed (System keeps running even if some slaves are in error state.) - Strict = 1, // 1 = strict (System shuts down as soon as one component is in error state.) -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/Program.cs b/csharp/Lib/Devices/Trumpf/SystemControl/Program.cs new file mode 100644 index 000000000..aacd72a30 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/Program.cs @@ -0,0 +1,46 @@ +// using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +// using InnovEnergy.Lib.Utils; +// +// namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; +// +// // TODO: remove +// +// public static class Program +// { +// public static void Main(String[] args) +// { +// var d = new SystemControlDevice("localhost", 5001); +// +// while (true) +// { +// var r = d.Read(); +// +// Console.WriteLine(DateTime.Now); +// r.ToString().Replace(",", "\n").Replace(" {", "\n").WriteLine(); +// +// "Alarms".WriteLine(); +// r.Alarms.Count.WriteLine(); +// r.Alarms.Select(a => a.ToString()).JoinLines().WriteLine(); +// +// "Warnings".WriteLine(); +// r.NumberOfWarnings.WriteLine(); +// r.Warnings.Select(a => a.ToString()).JoinLines().WriteLine(); +// +// var c = r with +// { +// UseSlaveIdForAddressing = true, +// ReferenceFrame = ReferenceFrame.Consumer, +// GridType = GridType.GridTied400V50Hz, +// SystemConfig = SystemConfig.AcDcAndDcDc, +// CommunicationTimeout = null, +// SlaveErrorHandling = SlaveErrorHandling.Relaxed, +// SubSlaveErrorHandling = SubSlaveErrorHandling.Off +// }; +// +// +// +// d.Write(c); +// +// } +// } +// } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/StatusRecord.cs b/csharp/Lib/Devices/Trumpf/SystemControl/StatusRecord.cs deleted file mode 100644 index 511019b83..000000000 --- a/csharp/Lib/Devices/Trumpf/SystemControl/StatusRecord.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; - -using AlarmMessages = IReadOnlyList; -using WarningMessages = IReadOnlyList; - - -// ReSharper disable UnusedAutoPropertyAccessor.Global -#pragma warning disable CS8618 - - -public record StatusRecord -{ - - // TODO - - // public MainState MainState { get; init; } - // public String SerialNumber { get; init; } - // public AcDcGridType GridType { get; init; } - // public WarningMessages Warnings { get; init; } - // public AlarmMessages Alarms { get; init; } - // public Decimal NumberOfConnectedSlaves { get; init; } - // public Decimal NumberOfConnectedSubSlaves { get; init; } - // public Frequency AcDcNominalGridFrequency { get; init; } - // public Voltage AcDcNominalGridVoltage { get; init; } - // public Power AcDcActNominalPower { get; init; } - // public Decimal AcDcPowerLimitingStatusAct { get; init; } // TODO: enum - // public Voltage AcDcDcVoltageReference { get; init; } - // public Voltage AcDcDcLinkVoltageMinAct { get; init; } - // public Voltage AcDcDcLinkVoltageMaxAct { get; init; } - // public Voltage AcDcDcLinkChargedMinVoltage { get; init; } - // public Decimal AcDcStmActCustomer { get; init; } - // public Decimal AcDcOverloadIntegratorStatusL1 { get; init; } - // public Decimal AcDcOverloadIntegratorStatusL2 { get; init; } - // public Decimal AcDcOverloadIntegratorStatusL3 { get; init; } - // public Power AcSignedPowerValue { get; init; } - // public Voltage ActualDcLinkVoltageUpperHalf { get; init; } - // public Voltage ActualDcLinkVoltageLowerHalf { get; init; } - // public Voltage ActualDcLinkVoltageUpperHalfExt { get; init; } - // public Voltage ActualDcLinkVoltageLowerHalfExt { get; init; } - // public Voltage VoltageIntNtoPe { get; init; } - // public Voltage VoltageExtNtoPe { get; init; } - // public Temperature InletAirTemperature { get; init; } -} diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SubSlavesErrorPolicy.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SubSlavesErrorPolicy.cs deleted file mode 100644 index 107b6b42b..000000000 --- a/csharp/Lib/Devices/Trumpf/SystemControl/SubSlavesErrorPolicy.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; - -public enum SubSlavesErrorPolicy : UInt16 -{ - Strict = 0, // (AC-DC module switches to error state if at least one submodules is in error state - Relaxed = 1, // (AC-DC module switches to error state if all submodules are in error state.) - Off = 2, // (If possible AC-DC module continues operation even if all submodules are in error state. -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControl.csproj b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControl.csproj index 2197231aa..098c470f3 100644 --- a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControl.csproj +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControl.csproj @@ -1,11 +1,12 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - + + + + + - + + diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlDevice.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlDevice.cs index cc3e94af0..6d088f4b2 100644 --- a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlDevice.cs +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlDevice.cs @@ -1,8 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Utils; -using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.SystemControlRegisters; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; @@ -10,315 +9,21 @@ namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public class SystemControlDevice { - private ModbusTcpClient ModbusTcpClient { get; } + private readonly ModbusDevice _SystemControlSlave; + public Channel Channel { get; } - public SystemControlDevice(String hostname, UInt16 port = ModbusTcpClient.DefaultPort, Byte slaveAddress = 0) + public SystemControlDevice(String hostname, Int32 port = 502) : this(new TcpChannel(hostname, port)) { - var connection = new ModbusTcpConnection(hostname, port); - ModbusTcpClient = new ModbusTcpClient(connection, slaveAddress); } - - - public void WriteControl(ControlRecord c) - { - /* - WriteRegs(AcControlRegisters.Date, new List { c.Date.ConvertTo()}); - WriteRegs(AcControlRegisters.Time, new List { c.Time.ConvertTo()}); - WriteRegs(AcControlRegisters.IpAddress, new List { c.IpAddress.ConvertTo()}); - WriteRegs(AcControlRegisters.Subnet, new List { c.Subnet.ConvertTo()}); - WriteRegs(AcControlRegisters.Gateway, new List { c.Gateway.ConvertTo()}); - WriteCoils(AcControlRegisters.ResetParamToDefault, c.ResetParamToDefault); - WriteCoils(AcControlRegisters.FactoryResetParameters, c.FactoryResetParameters); - ModbusTcpClient.WriteRegisters(AcControlRegisters.UpdateSwTrigger, c.UpdateSwTrigger, c.AutomaticSwUpdate, c.CustomerValuesSaveReset); - */ - - - WriteRegs(CommunicationTimeout, c.CommunicationTimeout.TotalSeconds.ConvertTo()); - - WriteRegs(ConnectedSystemConfig, c.ConnectedSystemConfig); - - - WriteCoils(PowerStageConfig, c.PowerStageEnable, - c.SetValueConfig.ConvertTo(), - c.ResetsAlarmAndWarning); - WriteRegs(PreChargeDcLinkConfigR, c.PreChargeDcLinkConfig, - c.PowerFactorConvention, c.SlaveAddress, - c.ErrorHandlingPolicy, - c.GridType, c.SubSlaveAddress); - WriteCoils(ModbusSlaveId, c.UseModbusSlaveIdForAddressing); - WriteRegs(SubSlaveErrorPolicy, c.SubSlaveErrorPolicy); - - WriteRegs(SignedPowerNominalValue, -1.0m, c.SignedPowerNominalValue);/*, c.SignedPowerSetValueL1, - c.SignedPowerSetValueL2, c.SignedPowerSetValueL3, - c.PowerSetValue, c.PowerSetValueL1, - c.PowerSetValueL2, c.PowerSetValuesL3);*/ - - - WriteRegs(MaximumGridCurrentRmsL1, 0.01m, c.MaximumGridCurrentRmsL1, c.MaximumGridCurrentRmsL2, - c.MaximumGridCurrentRmsL3, c.CosPhiSetValueL1, - c.CosPhiSetValueL2, c.CosPhiSetValueL3); - - WriteCoils(PhaseL1IsCapacitive, c.PhaseL1IsCapacitive, - c.PhaseL2IsCapacitive, - c.PhaseL3IsCapacitive, - c.PhasesAreCapacitive); - - /* WriteRegs(SetPointCosPhi, 0.01m, c.SetPointCosPhi.ConvertTo(), - c.SetPointSinPhi.ConvertTo(), - c.SetPointSinPhiL1.ConvertTo(), - c.SetPointSinPhiL2.ConvertTo(), - c.SetPointSinPhiL3.ConvertTo(), - c.FrequencyOffsetIm);*/ - - WriteRegs(VoltageAdjustmentFactorIm, c.VoltageAdjustmentFactorIm); - WriteRegs(PreChargeDcLinkVoltage, c.PreChargeDcLinkVoltage); - WriteRegs(MaxPeakCurrentVoltageControlL1, 0.01m, c.MaxPeakCurrentVoltageControlL1, - c.MaxPeakCurrentVoltageControlL2, - c.MaxPeakCurrentVoltageControlL3); - WriteRegs(GridFormingMode, c.GridFormingMode, c.DcLinkRefVoltage, - c.DcLinkMinVoltage, c.DcLinkMaxVoltage, - c.DcVoltageRefUs, c.DcMinVoltageUs, c.DcMaxVoltageUs); - WriteRegs(AcDcGcBypassMode, c.AcDcGcBypassMode); - WriteRegs(AcDcGcPMaxThresholdPercent, 0.01m, c.AcDcGcPMaxThresholdPercent); - WriteRegs(AcDcGcStartupRampEnable, c.AcDcGcStartupRampEnable); - WriteRegs(DcConfigModule, c.DcConfigModule); - WriteRegs(DcDcPowerDistribution, 0.1m, c.DcDcPowerDistribution); - WriteRegs(SystemControlRegisters.AcDcDistributionMode, c.AcDcDistributionMode); - } - - private void WriteRegs (UInt16 a, Decimal res = 1.0m, params Decimal[] regs) => ModbusTcpClient.WriteRegisters(a, regs.ToUInt16(res)); - private void WriteRegs (UInt16 a, params IConvertible[] regs) => ModbusTcpClient.WriteRegisters(a, regs.Select(v => v.ConvertTo()).ToArray()); - private void WriteRegs (UInt16 a, params UInt16[] regs) => ModbusTcpClient.WriteRegisters(a, regs); - private void WriteCoils(UInt16 a, params Boolean[] coils) => ModbusTcpClient.WriteMultipleCoils(a, coils); - - private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos); - public StatusRecord? ReadStatus() + public SystemControlDevice(Channel channel) { - try - { - return TryReadStatus(); - } - catch (Exception e) - { - ModbusTcpClient.CloseConnection(); - Console.WriteLine("Failed to read inverter status"); - e.Message.WriteLine(); - - return null; - } + Channel = channel; + + var tcpClient = new ModbusTcpClient(channel, 0); + _SystemControlSlave = new ModbusDevice(tcpClient); } - private StatusRecord TryReadStatus() - { - // Console.WriteLine("Reading Ac Device"); - - var acSerialNumber = ModbusTcpClient.ReadInputRegisters(2009, 2); - var acActualMain = ModbusTcpClient.ReadInputRegisters(5001, 3); - var acActualAcDc = ModbusTcpClient.ReadInputRegisters(5021, 9); - var acActualAcDc2 = ModbusTcpClient.ReadInputRegisters(5031, 1); - var acActualAcDc3 = ModbusTcpClient.ReadInputRegisters(5131, 6); - var acActualMeasurement = ModbusTcpClient.ReadInputRegisters(5141, 3); - var acActualMeasurement1 = ModbusTcpClient.ReadInputRegisters(5151, 3); - var acActualMeasurement2 = ModbusTcpClient.ReadInputRegisters(5161, 3); - var acActualMeasurement3 = ModbusTcpClient.ReadInputRegisters(5171, 3); - var acActualMeasurement4 = ModbusTcpClient.ReadInputRegisters(5187, 2); - var acActualMeasurement5 = ModbusTcpClient.ReadInputRegisters(5189, 2); - var acActualMeasurement6 = ModbusTcpClient.ReadInputRegisters(5191, 2); - var acActualMeasurement7 = ModbusTcpClient.ReadInputRegisters(5201, 1); - var acActualMeasurement8 = ModbusTcpClient.ReadInputRegisters(5211, 4); - var acActualMeasurement9 = ModbusTcpClient.ReadInputRegisters(5221, 2); - var acActualTemp = ModbusTcpClient.ReadInputRegisters(5501, 1); - var acWarningValues = ModbusTcpClient.ReadInputRegisters(2402, 22); - var acAlarmValues = ModbusTcpClient.ReadInputRegisters(2809, 22); - var acSetValues = ModbusTcpClient.ReadInputRegisters(4196, 1); - - var warnings = Enumerable - .Range(2404, 20) - .Select(n => acWarningValues.GetUInt16((UInt16)n).ConvertTo()) - .ToArray(); - - var alarms = Enumerable - .Range(2811, 20) - .Select(n => acAlarmValues.GetUInt16((UInt16)n).ConvertTo()) - .Where(m => m != AlarmMessage.NoAlarm) - .ToArray(); - - - var dcPower = acActualMeasurement.GetInt16(5141) * 1m + acActualMeasurement.GetInt16(5142) * 1m + acActualMeasurement.GetInt16(5143) * 1m; - var dcVoltage = acActualMeasurement8.GetUInt16(5214) + acActualMeasurement8.GetUInt16(5213); - var dcCurrent = dcVoltage != 0m - ? dcPower / dcVoltage - : 0m; - - - // //acActualMeasurement - // PowerAcL1 = acActualMeasurement.GetInt16(5141) * 1m, // in Watt - // PowerAcL2 = acActualMeasurement.GetInt16(5142) * 1m, // in Watt - // PowerAcL3 = acActualMeasurement.GetInt16(5143) * 1m, // in Watt - // - //acActualMeasurement1 - // PhaseCurrentL1 = acActualMeasurement1.GetUInt16(5151) * 0.01m, - // PhaseCurrentL2 = acActualMeasurement1.GetUInt16(5152) * 0.01m, - // PhaseCurrentL3 = acActualMeasurement1.GetUInt16(5153) * 0.01m, - - //acActualMeasurement2 - // GridVoltageL1 = acActualMeasurement2.GetUInt16(5161) * 0.1m, - // GridVoltageL2 = acActualMeasurement2.GetUInt16(5162) * 0.1m, - // GridVoltageL3 = acActualMeasurement2.GetUInt16(5163) * 0.1m, - - //acActualMeasurement3 - // CosPhiL1 = acActualMeasurement3.GetInt16(5171) * 0.01m, - // CosPhiL2 = acActualMeasurement3.GetInt16(5172) * 0.01m, - // CosPhiL3 = acActualMeasurement3.GetInt16(5173) * 0.01m, - - // //acActualMeasurement4 - // SumPowerL1 = acActualMeasurement4.GetUInt32(5187) * 1m, // in Watt - // //acActualMeasurement5 - // SumPowerL2 = acActualMeasurement5.GetUInt32(5189) * 1m, // in Watt - // //acActualMeasurement6 - // SumPowerL3 = acActualMeasurement6.GetUInt32(5191) * 1m, // in Watt - // //acActualMeasurement9 - // GridFrequency = acActualMeasurement7.GetInt16(5201) * 0.01m, - - //acActualMeasurement11 - // VoltageIntNtoPE = acActualMeasurement9.GetInt16(5221) * 0.1m, - // VoltageExtNtoPE = acActualMeasurement9.GetInt16(5222) * 0.1m, - - // - // ApparentPowerAcL1 = acActualAcDc3.GetUInt16(5131) * 1m, // in VA - // ApparentPowerAcL2 = acActualAcDc3.GetUInt16(5132) * 1m, // in VA - // ApparentPowerAcL3 = acActualAcDc3.GetUInt16(5133) * 1m, // in VA - - var apparentPowerAcL1 = acActualAcDc3.GetUInt16(5131) * 1m; - var apparentPowerAcL2 = acActualAcDc3.GetUInt16(5132) * 1m; - var apparentPowerAcL3 = acActualAcDc3.GetUInt16(5133) * 1m; - - var powerAcL1 = acActualMeasurement.GetInt16(5141) * 1m; // in Watt - var powerAcL2 = acActualMeasurement.GetInt16(5142) * 1m; // in Watt - var powerAcL3 = acActualMeasurement.GetInt16(5143) * 1m; // in Watt - - var phaseCurrentL1 = acActualMeasurement1.GetUInt16(5151) * 0.01m; - var phaseCurrentL2 = acActualMeasurement1.GetUInt16(5152) * 0.01m; - var phaseCurrentL3 = acActualMeasurement1.GetUInt16(5153) * 0.01m; - - var gridVoltageL1 = acActualMeasurement2.GetUInt16(5161) * 0.1m; - var gridVoltageL2 = acActualMeasurement2.GetUInt16(5162) * 0.1m; - var gridVoltageL3 = acActualMeasurement2.GetUInt16(5163) * 0.1m; - - var gridFrequency = acActualMeasurement7.GetInt16(5201) * 0.01m; - - return new StatusRecord - { - Ac = new Ac3Bus - { - Frequency = gridFrequency, - - L1 = new AcPhase - { - Voltage = gridVoltageL1, - Current = phaseCurrentL1, - Phi = ACos(powerAcL1 / apparentPowerAcL1), // TODO: 2pi - }, - L2 = new AcPhase - { - Voltage = gridVoltageL2, - Current = phaseCurrentL2, - Phi = ACos(powerAcL2 / apparentPowerAcL2), // TODO: 2pi - }, - L3 = new AcPhase - { - Voltage = gridVoltageL3, - Current = phaseCurrentL3, - Phi = ACos(powerAcL3 / apparentPowerAcL3), // TODO: 2pi - } - }, - Dc = new DcBus - { - Current = dcCurrent, - Voltage = dcVoltage, - }, - - MainState = acActualMain.GetInt16(5001).ConvertTo(), - Alarms = alarms, - Warnings = warnings, - GridType = acActualAcDc.GetUInt16(5024).ConvertTo(), - SerialNumber = acSerialNumber.GetInt32(2009).ToString(), // TODO: why tostring ? - NumberOfConnectedSlaves = acActualMain.GetUInt16(5002), - NumberOfConnectedSubSlaves = acActualMain.GetUInt16(5003), - AcDcNominalGridFrequency = acActualAcDc.GetUInt16(5021) * 0.1m, - AcDcNominalGridVoltage = acActualAcDc.GetUInt16(5022), - AcDcActNominalPower = acActualAcDc.GetUInt16(5023), - AcDcPowerLimitingStatusAct = acActualAcDc.GetUInt16(5025), - AcDcDcVoltageReference = acActualAcDc.GetUInt16(5026), // DC link reference - AcDcDcLinkVoltageMinAct = acActualAcDc.GetUInt16(5027), // DC link min voltage - AcDcDcLinkVoltageMaxAct = acActualAcDc.GetUInt16(5028), // DC link max voltage - AcDcDcLinkChargedMinVoltage = acActualAcDc.GetUInt16(5029) * 0.01m, - AcDcStmActCustomer = acActualAcDc2.GetUInt16(5031), //need to check - AcDcOverloadIntegratorStatusL1 = acActualAcDc3.GetUInt16(5134) * 0.1m, - AcDcOverloadIntegratorStatusL2 = acActualAcDc3.GetUInt16(5135) * 0.1m, - AcDcOverloadIntegratorStatusL3 = acActualAcDc3.GetUInt16(5136) * 0.1m, - AcSignedPowerValue = acSetValues.GetInt16(4196) * -1.0m, // this is also used for control - ActualDcLinkVoltageUpperHalf = acActualMeasurement8.GetUInt16(5211), - ActualDcLinkVoltageLowerHalf = acActualMeasurement8.GetUInt16(5212), - ActualDcLinkVoltageUpperHalfExt = acActualMeasurement8.GetUInt16(5213), - ActualDcLinkVoltageLowerHalfExt = acActualMeasurement8.GetUInt16(5214), - VoltageIntNtoPe = acActualMeasurement9.GetInt16(5221) * 0.1m, - VoltageExtNtoPe = acActualMeasurement9.GetInt16(5222) * 0.1m, - InletAirTemperature = acActualTemp.GetInt16(5501) * 0.1m, - - }; - - - - // ( - // Ac: new Ac3Bus - // ( - // new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)), - // new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)), - // new AcPhase(gridVoltageL3,phaseCurrentL3, ACos(powerAcL3/apparentPowerAcL3)), - // gridFrequency // Gird Frequency - // ), - // Dc: new DcConnection(dcVoltage, dcCurrent), - // - // SerialNumber : acSerialNumber.GetInt32(2009).ToString(), - // - // // acActualMainValues - // MainState : acActualMain.GetInt16(5001).ConvertTo(), - // NumberOfConnectedSlaves : acActualMain.GetUInt16(5002), - // NumberOfConnectedSubSlaves : acActualMain.GetUInt16(5003), - // - // //acActualAcDc - // AcDcNominalGridFrequency : acActualAcDc.GetUInt16(5021) * 0.1m, - // AcDcNominalGridVoltage : acActualAcDc.GetUInt16(5022), - // AcDcActNominalPower : acActualAcDc.GetUInt16(5023), - // AcDcActiveGridType : acActualAcDc.GetUInt16(5024).ConvertTo(), - // AcDcPowerLimitingStatusAct : acActualAcDc.GetUInt16(5025), - // AcDcDcVoltageReference : acActualAcDc.GetUInt16(5026), // DC link reference - // AcDcDcLinkVoltageMinAct : acActualAcDc.GetUInt16(5027), // DC link min voltage - // AcDcDcLinkVoltageMaxAct : acActualAcDc.GetUInt16(5028), // DC link max voltage - // AcDcDcLinkChargedMinVoltage : acActualAcDc.GetUInt16(5029) * 0.01m, - // - // //ac Actual AcDc 2 - // AcDcStmActCustomer : acActualAcDc2.GetUInt16(5031), //need to check - // AcDcOverloadIntegratorStatusL1 : acActualAcDc3.GetUInt16(5134) * 0.1m, - // AcDcOverloadIntegratorStatusL2 : acActualAcDc3.GetUInt16(5135) * 0.1m, - // AcDcOverloadIntegratorStatusL3 : acActualAcDc3.GetUInt16(5136) * 0.1m, - // AcSignedPowerValue : acSetValues.GetInt16(4196) * -1.0m, // this is also used for control - // - // //acActualMeasurement10 - // ActualDcLinkVoltageUpperHalf : acActualMeasurement8.GetUInt16(5211), - // ActualDcLinkVoltageLowerHalf : acActualMeasurement8.GetUInt16(5212), - // ActualDcLinkVoltageUpperHalfExt : acActualMeasurement8.GetUInt16(5213), - // ActualDcLinkVoltageLowerHalfExt : acActualMeasurement8.GetUInt16(5214), - // - // VoltageIntNtoPe : acActualMeasurement9.GetInt16(5221) * 0.1m, - // VoltageExtNtoPe : acActualMeasurement9.GetInt16(5222) * 0.1m, - // //acActualTemp - // InletAirTemperature : acActualTemp.GetInt16(5501) * 0.1m, - // - // Warnings : warnings, - // Alarms : alarms - // ); - } + public void Write(SystemControlRegisters c) => _SystemControlSlave.Write(c); + public SystemControlRegisters Read() => _SystemControlSlave.Read(); } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Alarms.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Alarms.cs new file mode 100644 index 000000000..7b0ff8640 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Alarms.cs @@ -0,0 +1,41 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; + +#pragma warning disable CS0649 + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public partial record SystemControlRegisters +{ + private IEnumerable GetAlarms() + { + yield return Alarm1; + yield return Alarm2; + yield return Alarm3; + yield return Alarm4; + yield return Alarm5; + yield return Alarm6; + yield return Alarm7; + yield return Alarm8; + yield return Alarm9; + yield return Alarm10; + yield return Alarm11; + yield return Alarm12; + yield return Alarm13; + yield return Alarm14; + yield return Alarm15; + yield return Alarm16; + yield return Alarm17; + yield return Alarm18; + yield return Alarm19; + yield return Alarm20; + } + + public IReadOnlyList Alarms => GetAlarms() + .Take(NumberOfAlarms) + .Where(IsSystemControlAlarm) + .ToList(); + + private static Boolean IsSystemControlAlarm(AlarmMessage alarm) => (UInt16)alarm is >= 40000 and < 50000; +} diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.CommunicationTimeout.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.CommunicationTimeout.cs new file mode 100644 index 000000000..65804eec3 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.CommunicationTimeout.cs @@ -0,0 +1,17 @@ +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; + +public partial record SystemControlRegisters +{ + public TimeSpan? CommunicationTimeout + { + get => _CommunicationTimeoutSeconds != NoTimeout + ? TimeSpan.FromSeconds(_CommunicationTimeoutSeconds) + : null; + + set => _CommunicationTimeoutSeconds = value is not null + ? value.Value.TotalSeconds.ConvertTo() + : NoTimeout; + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Modbus.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Modbus.cs new file mode 100644 index 000000000..4ea2187dc --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Modbus.cs @@ -0,0 +1,90 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using SystemConfig = InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig; + + +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; + +#pragma warning disable CS0169 +#pragma warning disable CS0649 +// using the same record for status & control + + + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[SuppressMessage("ReSharper", "InconsistentNaming")] +public partial record SystemControlRegisters +{ + private const UInt16 NoTimeout = UInt16.MaxValue; + + [HoldingRegister(1016)] private UInt16 _CommunicationTimeoutSeconds; + + [HoldingRegister(1018)] public SystemConfig SystemConfig { get; set; } + + //[Coil(4002)] + [HoldingRegister(4002)] public Boolean ResetAlarmsAndWarnings { get; set; } + + [HoldingRegister(4007)] public UInt16 TargetSlave { get; set; } + + //[Coil(4011)] + [HoldingRegister(4011)] public Boolean UseSlaveIdForAddressing { get; set; } + [HoldingRegister(4006)] public ReferenceFrame ReferenceFrame { get; set; } + + [HoldingRegister(4008)] public SlaveErrorHandling SlaveErrorHandling { get; set; } + [HoldingRegister(4012)] public SubSlaveErrorHandling SubSlaveErrorHandling { get; set; } + + + [HoldingRegister(4182)] public PowerSetPointActivation PowerSetPointActivation { get; set; } + [HoldingRegister(4183)] public PowerSetPointTrigger PowerSetPointTrigger { get; set; } + + [InputRegister(5000)] public DeviceState DeviceState { get; private set; } + [InputRegister(5001)] public UInt16 NumberOfConnectedSlaves { get; private set; } + [InputRegister(5002)] public UInt16 NumberOfConnectedSubSlaves { get; private set; } + + [InputRegister(2402)] public UInt16 NumberOfWarnings; + [InputRegister(2403)] private WarningMessage Warning1; + [InputRegister(2404)] private WarningMessage Warning2; + [InputRegister(2405)] private WarningMessage Warning3; + [InputRegister(2406)] private WarningMessage Warning4; + [InputRegister(2407)] private WarningMessage Warning5; + [InputRegister(2408)] private WarningMessage Warning6; + [InputRegister(2409)] private WarningMessage Warning7; + [InputRegister(2410)] private WarningMessage Warning8; + [InputRegister(2411)] private WarningMessage Warning9; + [InputRegister(2412)] private WarningMessage Warning10; + [InputRegister(2413)] private WarningMessage Warning11; + [InputRegister(2414)] private WarningMessage Warning12; + [InputRegister(2415)] private WarningMessage Warning13; + [InputRegister(2416)] private WarningMessage Warning14; + [InputRegister(2417)] private WarningMessage Warning15; + [InputRegister(2418)] private WarningMessage Warning16; + [InputRegister(2419)] private WarningMessage Warning17; + [InputRegister(2420)] private WarningMessage Warning18; + [InputRegister(2421)] private WarningMessage Warning19; + [InputRegister(2422)] private WarningMessage Warning20; + + + [InputRegister(2809)] private UInt16 NumberOfAlarms; + [InputRegister(2810)] private AlarmMessage Alarm1; + [InputRegister(2811)] private AlarmMessage Alarm2; + [InputRegister(2812)] private AlarmMessage Alarm3; + [InputRegister(2813)] private AlarmMessage Alarm4; + [InputRegister(2814)] private AlarmMessage Alarm5; + [InputRegister(2815)] private AlarmMessage Alarm6; + [InputRegister(2816)] private AlarmMessage Alarm7; + [InputRegister(2817)] private AlarmMessage Alarm8; + [InputRegister(2818)] private AlarmMessage Alarm9; + [InputRegister(2819)] private AlarmMessage Alarm10; + [InputRegister(2820)] private AlarmMessage Alarm11; + [InputRegister(2821)] private AlarmMessage Alarm12; + [InputRegister(2822)] private AlarmMessage Alarm13; + [InputRegister(2823)] private AlarmMessage Alarm14; + [InputRegister(2824)] private AlarmMessage Alarm15; + [InputRegister(2825)] private AlarmMessage Alarm16; + [InputRegister(2826)] private AlarmMessage Alarm17; + [InputRegister(2827)] private AlarmMessage Alarm18; + [InputRegister(2828)] private AlarmMessage Alarm19; + [InputRegister(2829)] private AlarmMessage Alarm20; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Warnings.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Warnings.cs new file mode 100644 index 000000000..42106bb00 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.Warnings.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; + +namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; + +#pragma warning disable CS0649 + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public partial record SystemControlRegisters +{ + private IEnumerable GetWarnings() + { + yield return Warning1; + yield return Warning2; + yield return Warning3; + yield return Warning4; + yield return Warning5; + yield return Warning6; + yield return Warning7; + yield return Warning8; + yield return Warning9; + yield return Warning10; + yield return Warning11; + yield return Warning12; + yield return Warning13; + yield return Warning14; + yield return Warning15; + yield return Warning16; + yield return Warning17; + yield return Warning18; + yield return Warning19; + yield return Warning20; + } + + public IReadOnlyList Warnings => GetWarnings() + .Take(NumberOfWarnings) + .Where(w => w != WarningMessage.NoWarning) + .ToList(); +} diff --git a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.cs b/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.cs deleted file mode 100644 index 6ab3817f2..000000000 --- a/csharp/Lib/Devices/Trumpf/SystemControl/SystemControlRegisters.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.SystemControl; - -public static class SystemControlRegisters -{ - - // TODO - - // public const UInt16 Date = 1001; - // public const UInt16 Time = 1003; - // public const UInt16 IpAddress = 1005; - // public const UInt16 Subnet = 1007; - // public const UInt16 Gateway = 1009; - // public const UInt16 ResetParamToDefault = 1011; - // public const UInt16 CommunicationTimeout = 1017; - // public const UInt16 RestartFlag = 1018; - // public const UInt16 ConnectedSystemConfig = 1019; - // public const UInt16 UpdateSwTrigger = 1027; - // public const UInt16 AutomaticSwUpdate = 1028; - // public const UInt16 CustomerValuesSaveReset = 1029; - // public const UInt16 SerialNumberSystemControl = 2001; - // public const UInt16 SerialNumberAcDc = 2009; - // public const UInt16 IntegrationLevel = 2051; - // public const UInt16 IlBuildnumber = 2052; - // public const UInt16 PowerStageConfig = 4001; - // public const UInt16 SetValueConfig = 4002; - // public const UInt16 ResetsAlarmAndWarning = 4003; - // public const UInt16 PreChargeDcLinkConfigR = 4006; - // public const UInt16 ReferenceFrameConvention = 4007; - // public const UInt16 SlaveAddress = 4008; - // public const UInt16 ErrorHandlingPolicy = 4009; - // public const UInt16 GridType = 4010; - // public const UInt16 SubSlaveAddress = 4011; - // public const UInt16 ModbusSlaveId = 4012; - // public const UInt16 SubSlaveErrorPolicy = 4013; - // public const UInt16 SignedPowerNominalValue = 4196; - // public const UInt16 SignedPowerSetValueL1 = 4197; - // public const UInt16 SignedPowerSetValueL2 = 4198; - // public const UInt16 SignedPowerSetValueL3 = 4199; - // public const UInt16 PowerSetValue = 4200; - // public const UInt16 PowerSetValueL1 = 4201; - // public const UInt16 PowerSetValueL2 = 4202; - // public const UInt16 PowerSetValueL3 = 4203; - // public const UInt16 MaximumGridCurrentRmsL1 = 4204; - // public const UInt16 MaximumGridCurrentRmsL2 = 4205; - // public const UInt16 MaximumGridCurrentRmsL3 = 4206; - // public const UInt16 CosPhiSetValueL1 = 4207; - // public const UInt16 CosPhiSetValueL2 = 4208; - // public const UInt16 CosPhiSetValueL3 = 4209; - // public const UInt16 PhaseL1IsCapacitive = 4214; // True = Capacitive, false = Inductive - // public const UInt16 PhaseL2IsCapacitive = 4215; // True = Capacitive, false = Inductive - // public const UInt16 PhaseL3IsCapacitive = 4216; // True = Capacitive, false = Inductive - // public const UInt16 PhasesAreCapacitive = 4217; // True = Capacitive, false = Inductive - // public const UInt16 SetPointCosPhi = 4218; - // public const UInt16 SetPointSinPhi = 4219; - // public const UInt16 SetPointSinPhiL1 = 4220; - // public const UInt16 SetPointSinPhiL2 = 4221; - // public const UInt16 SetPointSinPhiL3 = 4222; - // public const UInt16 FrequencyOffsetIm = 4223; //Im: Island mode - // public const UInt16 VoltageAdjustmentFactorIm = 4224; //Im: Island mode - // public const UInt16 PreChargeDcLinkVoltage = 4226; - // public const UInt16 MaxPeakCurrentVoltageControlL1 = 4227; - // public const UInt16 MaxPeakCurrentVoltageControlL2 = 4228; - // public const UInt16 MaxPeakCurrentVoltageControlL3 = 4229; - // public const UInt16 GridFormingMode = 4230; - // public const UInt16 DcLinkReferenceVoltage = 4231; - // public const UInt16 DcLinkMinVoltage = 4232; - // public const UInt16 DcLinkMaxVoltage = 4233; - // public const UInt16 AcDcDcVoltageRefUs = 4234; - // public const UInt16 AcDcMinDcLinkVoltageUs = 4235; - // public const UInt16 AcDcMaxDcLinkVoltageUs = 4236; - // // public const UInt16 FrequencySlopeIslandMode = 4237, // Function fN = f(active grid-power) of droop control - // // public const UInt16 VoltageSlopeIslandMode = 4238, // VN = f(reactive grid power) of droop control in island operation. - // public const UInt16 AcDcGcBypassMode = 4281; - // public const UInt16 AcDcGcPMaxThresholdPercent = 4282; // res - // public const UInt16 AcDcGcStartupRampEnable = 4283; - // public const UInt16 DcConfigModule = 4301; // 0 = DC module is off, battery voltage can be measured - // // 1 = DC module is active (DC link voltage control) - // // 2 = DC module is active(current source mode orin DC droop mode) - // public const UInt16 DcDcPowerDistribution = 4304; - // public const UInt16 AcDcDistributionMode = 4307; -} - - diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/AlarmState.cs b/csharp/Lib/Devices/Trumpf/TruConvert/AlarmState.cs deleted file mode 100644 index 7417d9af3..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvert/AlarmState.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; - -public enum InverterAlarmState : UInt16 -{ - AlarmOff = 0, - ResetAlarmAc = 1, -} - -public enum DcAlarmState : UInt16 -{ - AlarmOff = 0, - ResetAlarmDc = 1, -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/Slave.cs b/csharp/Lib/Devices/Trumpf/TruConvert/Slave.cs deleted file mode 100644 index 702fb1f62..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvert/Slave.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; - -public static class Slave -{ - public const UInt16 Broadcast = 0; -} - - diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj b/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj index a4f729801..517a59172 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj +++ b/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj @@ -7,6 +7,7 @@ + diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/Utils.cs b/csharp/Lib/Devices/Trumpf/TruConvert/Utils.cs deleted file mode 100644 index 91ba7993f..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvert/Utils.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; - -public static class Utils -{ - public static UInt16[] ToUInt16(this IEnumerable regs, Decimal resolution) - { - // LINQ - return regs - .Select(v => (Int16)(v / resolution)) - .Select(v => unchecked((UInt16)v)) - .ToArray(); - } -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs deleted file mode 100644 index 61205ca7c..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; - -public static class AcControlRegisters -{ - public const UInt16 Date = 1001; - public const UInt16 Time = 1003; - public const UInt16 IpAddress = 1005; - public const UInt16 Subnet = 1007; - public const UInt16 Gateway = 1009; - public const UInt16 ResetParamToDefault = 1011; - public const UInt16 CommunicationTimeout = 1017; - public const UInt16 RestartFlag = 1018; - public const UInt16 ConnectedSystemConfig = 1019; - public const UInt16 UpdateSwTrigger = 1027; - public const UInt16 AutomaticSwUpdate = 1028; - public const UInt16 CustomerValuesSaveReset = 1029; - public const UInt16 SerialNumberSystemControl = 2001; - public const UInt16 SerialNumberAcDc = 2009; - public const UInt16 IntegrationLevel = 2051; - public const UInt16 IlBuildnumber = 2052; - public const UInt16 PowerStageConfig = 4001; - public const UInt16 SetValueConfig = 4002; - public const UInt16 ResetsAlarmAndWarning = 4003; - public const UInt16 PreChargeDcLinkConfigR = 4006; - public const UInt16 ReferenceFrameConvention = 4007; - public const UInt16 SlaveAddress = 4008; - public const UInt16 ErrorHandlingPolicy = 4009; - public const UInt16 GridType = 4010; - public const UInt16 SubSlaveAddress = 4011; - public const UInt16 ModbusSlaveId = 4012; - public const UInt16 SubSlaveErrorPolicy = 4013; - public const UInt16 SignedPowerNominalValue = 4196; - public const UInt16 SignedPowerSetValueL1 = 4197; - public const UInt16 SignedPowerSetValueL2 = 4198; - public const UInt16 SignedPowerSetValueL3 = 4199; - public const UInt16 PowerSetValue = 4200; - public const UInt16 PowerSetValueL1 = 4201; - public const UInt16 PowerSetValueL2 = 4202; - public const UInt16 PowerSetValueL3 = 4203; - public const UInt16 MaximumGridCurrentRmsL1 = 4204; - public const UInt16 MaximumGridCurrentRmsL2 = 4205; - public const UInt16 MaximumGridCurrentRmsL3 = 4206; - public const UInt16 CosPhiSetValueL1 = 4207; - public const UInt16 CosPhiSetValueL2 = 4208; - public const UInt16 CosPhiSetValueL3 = 4209; - public const UInt16 PhaseL1IsCapacitive = 4214; // True = Capacitive, false = Inductive - public const UInt16 PhaseL2IsCapacitive = 4215; // True = Capacitive, false = Inductive - public const UInt16 PhaseL3IsCapacitive = 4216; // True = Capacitive, false = Inductive - public const UInt16 PhasesAreCapacitive = 4217; // True = Capacitive, false = Inductive - public const UInt16 SetPointCosPhi = 4218; - public const UInt16 SetPointSinPhi = 4219; - public const UInt16 SetPointSinPhiL1 = 4220; - public const UInt16 SetPointSinPhiL2 = 4221; - public const UInt16 SetPointSinPhiL3 = 4222; - public const UInt16 FrequencyOffsetIm = 4223; //Im: Island mode - public const UInt16 VoltageAdjustmentFactorIm = 4224; //Im: Island mode - public const UInt16 PreChargeDcLinkVoltage = 4226; - public const UInt16 MaxPeakCurrentVoltageControlL1 = 4227; - public const UInt16 MaxPeakCurrentVoltageControlL2 = 4228; - public const UInt16 MaxPeakCurrentVoltageControlL3 = 4229; - public const UInt16 GridFormingMode = 4230; - public const UInt16 DcLinkReferenceVoltage = 4231; - public const UInt16 DcLinkMinVoltage = 4232; - public const UInt16 DcLinkMaxVoltage = 4233; - public const UInt16 AcDcDcVoltageRefUs = 4234; - public const UInt16 AcDcMinDcLinkVoltageUs = 4235; - public const UInt16 AcDcMaxDcLinkVoltageUs = 4236; - // public const UInt16 FrequencySlopeIslandMode = 4237, // Function fN = f(active grid-power) of droop control - // public const UInt16 VoltageSlopeIslandMode = 4238, // VN = f(reactive grid power) of droop control in island operation. - public const UInt16 AcDcGcBypassMode = 4281; - public const UInt16 AcDcGcPMaxThresholdPercent = 4282; // res - public const UInt16 AcDcGcStartupRampEnable = 4283; - public const UInt16 DcConfigModule = 4301; // 0 = DC module is off, battery voltage can be measured - // 1 = DC module is active (DC link voltage control) - // 2 = DC module is active(current source mode orin DC droop mode) - public const UInt16 DcDcPowerDistribution = 4304; - public const UInt16 AcDcDistributionMode = 4307; -} - - diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs new file mode 100644 index 000000000..577b485d2 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs @@ -0,0 +1,82 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; + +public class AcDcDevicesRecord +{ + public static AcDcDevicesRecord Null => new AcDcDevicesRecord(null, Array.Empty()); + + public AcDcDevicesRecord(SystemControlRegisters? systemControl, IReadOnlyList devices) + { + SystemControl = systemControl; + Devices = devices; + } + + public SystemControlRegisters? SystemControl { get; } + public IReadOnlyList Devices { get; init; } + + public IEnumerable Alarms => Devices.SelectMany(d => d.Status.Alarms).Distinct(); + public IEnumerable Warnings => Devices.SelectMany(d => d.Status.Warnings).Distinct(); + + public Ac3Bus Ac + { + get + { + if (Devices.Count == 0) + return Ac3Bus.Null; + + var l1 = AcPhase.FromVoltageCurrentActiveReactive + ( + voltageRms : Devices.Average(d => d.Status.Ac.L1.Voltage), + currentRms : Devices.Sum(d => d.Status.Ac.L1.Current), + activePower : Devices.Sum(d => d.Status.Ac.L1.Power.Active), + reactivePower: Devices.Sum(d => d.Status.Ac.L1.Power.Reactive) + ); + + var l2 = AcPhase.FromVoltageCurrentActiveReactive + ( + voltageRms : Devices.Average(d => d.Status.Ac.L2.Voltage), + currentRms : Devices.Sum(d => d.Status.Ac.L2.Current), + activePower : Devices.Sum(d => d.Status.Ac.L2.Power.Active), + reactivePower: Devices.Sum(d => d.Status.Ac.L2.Power.Reactive) + ); + + var l3 = AcPhase.FromVoltageCurrentActiveReactive + ( + voltageRms : Devices.Average(d => d.Status.Ac.L3.Voltage), + currentRms : Devices.Sum(d => d.Status.Ac.L3.Current), + activePower : Devices.Sum(d => d.Status.Ac.L3.Power.Active), + reactivePower: Devices.Sum(d => d.Status.Ac.L3.Power.Reactive) + ); + + var f = Devices.Average(d => d.Status.Ac.Frequency); + + return Ac3Bus.FromPhasesAndFrequency(l1, l2, l3, f); + } + } + + public DcBus Dc + { + get + { + if (Devices.Count == 0) + return DcBus.Null; + + var u = Devices + .Select(d => d.Status.DcVoltages.Extern) + .Select(vs => vs.LowerHalf + vs.UpperHalf) + .Average(); + + var p = Ac.Power.Active.Value; + var i = u == 0 ? 0 : p / u; + + return DcBus.FromVoltageCurrent + ( + voltage: u, + current: i + ); + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.Modbus.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.Modbus.cs new file mode 100644 index 000000000..e4d6756a7 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.Modbus.cs @@ -0,0 +1,156 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using AlarmMessage = InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes.AlarmMessage; +using WarningMessage = InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes.WarningMessage; + +#pragma warning disable CS0649 + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; + + +public partial class AcDcRecord +{ + [HoldingRegister(4000)] internal Boolean PowerStageEnable; + [HoldingRegister(4001)] internal PhaseControl PhaseControl; + [HoldingRegister(4002)] internal Boolean ResetAlarmsAndWarnings; + + [HoldingRegister(4005)] internal DcPrechargeConfig DcPrechargeConfig; + + [HoldingRegister(4009)] internal GridType GridType; + [HoldingRegister(4200)] internal Double PowerSetpointL1; // VA + [HoldingRegister(4201)] internal Double PowerSetpointL2; // VA + [HoldingRegister(4202)] internal Double PowerSetpointL3; // VA + + [HoldingRegister(4206)] internal Double CosPhiSetpointL1; + [HoldingRegister(4207)] internal Double CosPhiSetpointL2; + [HoldingRegister(4208)] internal Double CosPhiSetpointL3; + + [HoldingRegister(4219)] internal Double SinPhiSetpointL1; + [HoldingRegister(4220)] internal Double SinPhiSetpointL2; + [HoldingRegister(4221)] internal Double SinPhiSetpointL3; + + [HoldingRegister(4213)] internal ReactivePowerKind ReactivePowerKindL1; + [HoldingRegister(4214)] internal ReactivePowerKind ReactivePowerKindL2; + [HoldingRegister(4215)] internal ReactivePowerKind ReactivePowerKindL3; + + [HoldingRegister(4222, Scale = .01)] internal Double IslandModeFrequencyOffset; + [HoldingRegister(4223)] internal Double IslandModeVoltageAdjustmentFactor; + + [HoldingRegister(4229)] internal GridMode GridMode; + + [HoldingRegister(4230)] internal Double DcLinkReferenceVoltage380400VGrid; + [HoldingRegister(4231)] internal Double DcLinkMinVoltage380400VGrid; + [HoldingRegister(4232)] internal Double DcLinkMaxVoltage380400VGrid; + + [HoldingRegister(4233)] internal Double DcLinkReferenceVoltage480VGrid; + [HoldingRegister(4234)] internal Double DcLinkMinVoltage480VGrid; + [HoldingRegister(4235)] internal Double DcLinkMaxVoltage480VGrid; + + + [InputRegister(5023)] internal readonly GridType ActiveGridType; + [InputRegister(5024)] internal readonly PowerLimit PowerLimitedBy; + + [InputRegister(5030)] internal readonly InverterState InverterState; + [InputRegister(5031)] internal readonly InverterState StateOnLastAlarm; + + [InputRegister(5025)] internal readonly Double DcReferenceVoltage; + [InputRegister(5026)] internal readonly Double DcMinVoltage; + [InputRegister(5027)] internal readonly Double DcMaxVoltage; + [InputRegister(5028, Scale = .01)] internal readonly Double DcHalfVoltageThreshold; + + [InputRegister(5020, Scale = .1)] internal readonly Double NominalAcFrequency; + [InputRegister(5021)] internal readonly Double NominalAcVoltage; + [InputRegister(5022)] internal readonly Double NominalPower; + + [InputRegister(5130)] internal readonly Double ApparentPowerL1; + [InputRegister(5131)] internal readonly Double ApparentPowerL2; + [InputRegister(5132)] internal readonly Double ApparentPowerL3; + + [InputRegister(5133, Scale = .1)] internal readonly Double OverloadCapacityL1; + [InputRegister(5134, Scale = .1)] internal readonly Double OverloadCapacityL2; + [InputRegister(5135, Scale = .1)] internal readonly Double OverloadCapacityL3; + + [InputRegister(5140)] internal readonly Double ActivePowerL1; + [InputRegister(5141)] internal readonly Double ActivePowerL2; + [InputRegister(5142)] internal readonly Double ActivePowerL3; + + [InputRegister(5231)] internal readonly Double ReactivePowerL1; + [InputRegister(5232)] internal readonly Double ReactivePowerL2; + [InputRegister(5233)] internal readonly Double ReactivePowerL3; + + [InputRegister(5150, Scale = .01)] internal readonly Double GridCurrentL1; + [InputRegister(5151, Scale = .01)] internal readonly Double GridCurrentL2; + [InputRegister(5152, Scale = .01)] internal readonly Double GridCurrentL3; + + [InputRegister(5160, Scale = .1)] internal readonly Double GridVoltageL1; + [InputRegister(5161, Scale = .1)] internal readonly Double GridVoltageL2; + [InputRegister(5162, Scale = .1)] internal readonly Double GridVoltageL3; + + [InputRegister(5170, Scale = .01)] internal readonly Double CosPhiL1;// TODO: check that those agree with computed ones in AC + [InputRegister(5171, Scale = .01)] internal readonly Double CosPhiL2;// TODO: remove + [InputRegister(5172, Scale = .01)] internal readonly Double CosPhiL3; + + [InputRegister(5200, Scale = .01)] internal readonly Double GridFrequency;// Grid freq in doc + + [InputRegister(5210)] internal readonly Double InternDcVoltageUpperHalf; + [InputRegister(5211)] internal readonly Double InternDcVoltageLowerHalf; + [InputRegister(5220, Scale = .1)] internal readonly Double InternVoltageNToPe; + + [InputRegister(5212)] internal readonly Double ExternDcVoltageUpperHalf; + [InputRegister(5213)] internal readonly Double ExternDcVoltageLowerHalf; + [InputRegister(5221, Scale = .1)] internal readonly Double ExternVoltageNToPe; + + [InputRegister(5500, Scale = .1)] internal readonly Double InletAirTemperature; + [InputRegister(5501, Scale = .1)] internal readonly Double IgbtL1Temperature; + [InputRegister(5502, Scale = .1)] internal readonly Double IgbtL2Temperature; + [InputRegister(5503, Scale = .1)] internal readonly Double IgbtL3Temperature; + [InputRegister(5504, Scale = .1)] internal readonly Double IgbtBalancerTemperature; + + [InputRegister(2809)] internal readonly UInt16 NumberOfAlarms; + + [InputRegister(2810)] internal readonly AlarmMessage Alarm1; + [InputRegister(2811)] internal readonly AlarmMessage Alarm2; + [InputRegister(2812)] internal readonly AlarmMessage Alarm3; + [InputRegister(2813)] internal readonly AlarmMessage Alarm4; + [InputRegister(2814)] internal readonly AlarmMessage Alarm5; + [InputRegister(2815)] internal readonly AlarmMessage Alarm6; + [InputRegister(2816)] internal readonly AlarmMessage Alarm7; + [InputRegister(2817)] internal readonly AlarmMessage Alarm8; + [InputRegister(2818)] internal readonly AlarmMessage Alarm9; + [InputRegister(2819)] internal readonly AlarmMessage Alarm10; + [InputRegister(2820)] internal readonly AlarmMessage Alarm11; + [InputRegister(2821)] internal readonly AlarmMessage Alarm12; + [InputRegister(2822)] internal readonly AlarmMessage Alarm13; + [InputRegister(2823)] internal readonly AlarmMessage Alarm14; + [InputRegister(2824)] internal readonly AlarmMessage Alarm15; + [InputRegister(2825)] internal readonly AlarmMessage Alarm16; + [InputRegister(2826)] internal readonly AlarmMessage Alarm17; + [InputRegister(2827)] internal readonly AlarmMessage Alarm18; + [InputRegister(2828)] internal readonly AlarmMessage Alarm19; + [InputRegister(2829)] internal readonly AlarmMessage Alarm20; + + + [InputRegister(2402)] internal readonly UInt16 NumberOfWarnings; + + [InputRegister(2403)] internal readonly WarningMessage Warning1; + [InputRegister(2404)] internal readonly WarningMessage Warning2; + [InputRegister(2405)] internal readonly WarningMessage Warning3; + [InputRegister(2406)] internal readonly WarningMessage Warning4; + [InputRegister(2407)] internal readonly WarningMessage Warning5; + [InputRegister(2408)] internal readonly WarningMessage Warning6; + [InputRegister(2409)] internal readonly WarningMessage Warning7; + [InputRegister(2410)] internal readonly WarningMessage Warning8; + [InputRegister(2411)] internal readonly WarningMessage Warning9; + [InputRegister(2412)] internal readonly WarningMessage Warning10; + [InputRegister(2413)] internal readonly WarningMessage Warning11; + [InputRegister(2414)] internal readonly WarningMessage Warning12; + [InputRegister(2415)] internal readonly WarningMessage Warning13; + [InputRegister(2416)] internal readonly WarningMessage Warning14; + [InputRegister(2417)] internal readonly WarningMessage Warning15; + [InputRegister(2418)] internal readonly WarningMessage Warning16; + [InputRegister(2419)] internal readonly WarningMessage Warning17; + [InputRegister(2420)] internal readonly WarningMessage Warning18; + [InputRegister(2421)] internal readonly WarningMessage Warning19; + [InputRegister(2422)] internal readonly WarningMessage Warning20; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.cs new file mode 100644 index 000000000..9d5f452f7 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcRecord.cs @@ -0,0 +1,10 @@ +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; + +public partial class AcDcRecord +{ + public AcDcStatus Status => new(this); + public AcDcControl Control => new(this); +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcControl.cs new file mode 100644 index 000000000..07c2f0056 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcControl.cs @@ -0,0 +1,35 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; + +public class AcControl +{ + public AcPowerControl Power => new (_Self); + + public PhaseControl PhaseControl + { + get => _Self.PhaseControl; + set => _Self.PhaseControl = value; + } + + public GridType GridType + { + get => _Self.GridType; + set + { + _Self.GridType = value; + _Self.GridMode = value switch + { + GridType.Island400V50Hz => GridMode.Island, + GridType.Island480V60Hz => GridMode.Island, + _ => GridMode.GridTied + }; + } + } + + public IslandMode IslandMode => new(_Self); + + internal AcControl(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcDcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcDcControl.cs new file mode 100644 index 000000000..0ffae612c --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcDcControl.cs @@ -0,0 +1,23 @@ + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; + +public class AcDcControl +{ + public AcControl Ac => new (_Self); + public DcControl Dc => new (_Self); + + public Boolean PowerStageEnable + { + get => _Self.PowerStageEnable; + set => _Self.PowerStageEnable = value; + } + + public Boolean ResetAlarmsAndWarnings + { + get => _Self.ResetAlarmsAndWarnings; + set => _Self.ResetAlarmsAndWarnings = value; + } + + internal AcDcControl(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcPowerControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcPowerControl.cs new file mode 100644 index 000000000..a23b522ee --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/AcPowerControl.cs @@ -0,0 +1,97 @@ +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; + +public class AcPowerControl +{ + public AcPower L1 + { + get + { + var s = _Self.PowerSetpointL1; + var cosPhi = _Self.CosPhiSetpointL1.Clamp(-1, 1); + var rpk = _Self.ReactivePowerKindL1; + var phi = cosPhi.Apply(Math.Acos) * (rpk == ReactivePowerKind.Inductive ? 1 : -1); + var sinPhi = Math.Sin(phi); + + return AcPower.FromActiveReactive(s * cosPhi, s * sinPhi); + } + + set + { + _Self.PowerSetpointL1 = value.Apparent.Value; + _Self.CosPhiSetpointL1 = value.CosPhi; + _Self.SinPhiSetpointL1 = Math.Sin(value.Phi); + _Self.ReactivePowerKindL1 = value.Reactive >= 0 + ? ReactivePowerKind.Inductive + : ReactivePowerKind.Capacitive; + } + } + + + public AcPower L2 + { + get + { + var s = _Self.PowerSetpointL2; + var cosPhi = _Self.CosPhiSetpointL2.Clamp(-1, 1); + var rpk = _Self.ReactivePowerKindL2; + var phi = cosPhi.Apply(Math.Acos) * (rpk == ReactivePowerKind.Inductive ? 1 : -1); + var sinPhi = Math.Sin(phi); + + return AcPower.FromActiveReactive(s * cosPhi, s * sinPhi); + } + + set + { + _Self.PowerSetpointL2 = value.Apparent.Value; + _Self.CosPhiSetpointL2 = value.CosPhi; + _Self.SinPhiSetpointL2 = Math.Sin(value.Phi); + _Self.ReactivePowerKindL2 = value.Reactive >= 0 + ? ReactivePowerKind.Inductive + : ReactivePowerKind.Capacitive; + } + } + + public AcPower L3 + { + get + { + var s = _Self.PowerSetpointL3; + var cosPhi = _Self.CosPhiSetpointL3.Clamp(-1, 1); + var rpk = _Self.ReactivePowerKindL3; + var phi = cosPhi.Apply(Math.Acos) * (rpk == ReactivePowerKind.Inductive ? 1 : -1); + var sinPhi = Math.Sin(phi); + + return AcPower.FromActiveReactive(s * cosPhi, s * sinPhi); + } + + set + { + _Self.PowerSetpointL3 = value.Apparent.Value; + _Self.CosPhiSetpointL3 = value.CosPhi; + _Self.SinPhiSetpointL3 = Math.Sin(value.Phi); + _Self.ReactivePowerKindL3 = value.Reactive >= 0 + ? ReactivePowerKind.Inductive + : ReactivePowerKind.Capacitive; + } + } + + + internal AcPowerControl(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; + + // public IEnumerator GetEnumerator() + // { + // yield return L1; + // yield return L2; + // yield return L3; + // } + // + // IEnumerator IEnumerable.GetEnumerator() + // { + // return GetEnumerator(); + // } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/DcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/DcControl.cs new file mode 100644 index 000000000..baac43651 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/DcControl.cs @@ -0,0 +1,45 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; + +public class DcControl +{ + public Voltage ReferenceVoltage + { + get => Is480V ? _Self.DcLinkReferenceVoltage480VGrid : _Self.DcLinkReferenceVoltage380400VGrid; + set + { + _Self.DcLinkReferenceVoltage480VGrid = value; + _Self.DcLinkReferenceVoltage380400VGrid = value; + } + } + + public Voltage MinVoltage + { + get => Is480V ? _Self.DcLinkMinVoltage480VGrid : _Self.DcLinkMinVoltage380400VGrid; + set + { + _Self.DcLinkMinVoltage480VGrid = value; + _Self.DcLinkMinVoltage380400VGrid = value; + } + } + + public Voltage MaxVoltage + { + get => Is480V ? _Self.DcLinkMaxVoltage480VGrid : _Self.DcLinkMaxVoltage380400VGrid; + set + { + _Self.DcLinkMaxVoltage480VGrid = value; + _Self.DcLinkMaxVoltage380400VGrid = value; + } + } + + public DcPrechargeConfig PrechargeConfig => _Self.DcPrechargeConfig; + + private Boolean Is480V => _Self.GridType is GridType.GridTied480V60Hz or GridType.Island480V60Hz; + + internal DcControl(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/IslandMode.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/IslandMode.cs new file mode 100644 index 000000000..694977730 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Control/IslandMode.cs @@ -0,0 +1,21 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Control; + +public class IslandMode +{ + public Frequency FrequencyOffset + { + get => _Self.IslandModeFrequencyOffset; + set => _Self.IslandModeFrequencyOffset = value; + } + + public Percent VoltageAdjustmentFactor + { + get => _Self.IslandModeVoltageAdjustmentFactor; + set => _Self.IslandModeVoltageAdjustmentFactor = value; + } + + internal IslandMode(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcControlRegisters.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcControlRegisters.cs new file mode 100644 index 000000000..4d6e1fc1c --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcControlRegisters.cs @@ -0,0 +1,80 @@ +// namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +// +// public static class AcControlRegisters +// { +// public const UInt16 Date = 1001; +// public const UInt16 Time = 1003; +// public const UInt16 IpAddress = 1005; +// public const UInt16 Subnet = 1007; +// public const UInt16 Gateway = 1009; +// public const UInt16 ResetParamToDefault = 1011; +// public const UInt16 CommunicationTimeout = 1017; +// public const UInt16 RestartFlag = 1018; +// public const UInt16 ConnectedSystemConfig = 1019; +// public const UInt16 UpdateSwTrigger = 1027; +// public const UInt16 AutomaticSwUpdate = 1028; +// public const UInt16 CustomerValuesSaveReset = 1029; +// public const UInt16 SerialNumberSystemControl = 2001; +// public const UInt16 SerialNumberAcDc = 2009; +// public const UInt16 IntegrationLevel = 2051; +// public const UInt16 IlBuildnumber = 2052; +// public const UInt16 PowerStageConfig = 4001; +// public const UInt16 SetValueConfig = 4002; +// public const UInt16 ResetsAlarmAndWarning = 4003; +// public const UInt16 PreChargeDcLinkConfigR = 4006; +// public const UInt16 ReferenceFrameConvention = 4007; +// public const UInt16 SlaveAddress = 4008; +// public const UInt16 ErrorHandlingPolicy = 4009; +// public const UInt16 GridType = 4010; +// public const UInt16 SubSlaveAddress = 4011; +// public const UInt16 ModbusSlaveId = 4012; +// public const UInt16 SubSlaveErrorPolicy = 4013; +// public const UInt16 SignedPowerNominalValue = 4196; +// public const UInt16 SignedPowerSetValueL1 = 4197; +// public const UInt16 SignedPowerSetValueL2 = 4198; +// public const UInt16 SignedPowerSetValueL3 = 4199; +// public const UInt16 Powe0rSetValue = 4200; +// public const UInt16 PowerSetValueL1 = 4201; +// public const UInt16 PowerSetValueL2 = 4202; +// public const UInt16 PowerSetValueL3 = 4203; +// public const UInt16 MaximumGridCurrentRmsL1 = 4204; +// public const UInt16 MaximumGridCurrentRmsL2 = 4205; +// public const UInt16 MaximumGridCurrentRmsL3 = 4206; +// public const UInt16 CosPhiSetValueL1 = 4207; +// public const UInt16 CosPhiSetValueL2 = 4208; +// public const UInt16 CosPhiSetValueL3 = 4209; +// public const UInt16 PhaseL1IsCapacitive = 4214; // True = Capacitive, false = Inductive +// public const UInt16 PhaseL2IsCapacitive = 4215; // True = Capacitive, false = Inductive +// public const UInt16 PhaseL3IsCapacitive = 4216; // True = Capacitive, false = Inductive +// public const UInt16 PhasesAreCapacitive = 4217; // True = Capacitive, false = Inductive +// public const UInt16 SetPointCosPhi = 4218; +// public const UInt16 SetPointSinPhi = 4219; +// public const UInt16 SetPointSinPhiL1 = 4220; +// public const UInt16 SetPointSinPhiL2 = 4221; +// public const UInt16 SetPointSinPhiL3 = 4222; +// public const UInt16 FrequencyOffsetIm = 4223; //Im: Island mode +// public const UInt16 VoltageAdjustmentFactorIm = 4224; //Im: Island mode +// public const UInt16 PreChargeDcLinkVoltage = 4226; +// public const UInt16 MaxPeakCurrentVoltageControlL1 = 4227; +// public const UInt16 MaxPeakCurrentVoltageControlL2 = 4228; +// public const UInt16 MaxPeakCurrentVoltageControlL3 = 4229; +// public const UInt16 GridFormingMode = 4230; +// public const UInt16 DcLinkReferenceVoltage = 4231; +// public const UInt16 DcLinkMinVoltage = 4232; +// public const UInt16 DcLinkMaxVoltage = 4233; +// public const UInt16 AcDcDcVoltageRefUs = 4234; +// public const UInt16 AcDcMinDcLinkVoltageUs = 4235; +// public const UInt16 AcDcMaxDcLinkVoltageUs = 4236; +// // public const UInt16 FrequencySlopeIslandMode = 4237, // Function fN = f(active grid-power) of droop control +// // public const UInt16 VoltageSlopeIslandMode = 4238, // VN = f(reactive grid power) of droop control in island operation. +// public const UInt16 AcDcGcBypassMode = 4281; +// public const UInt16 AcDcGcPMaxThresholdPercent = 4282; // res +// public const UInt16 AcDcGcStartupRampEnable = 4283; +// public const UInt16 DcConfigModule = 4301; // 0 = DC module is off, battery voltage can be measured +// // 1 = DC module is active (DC link voltage control) +// // 2 = DC module is active(current source mode orin DC droop mode) +// public const UInt16 DcDcPowerDistribution = 4304; +// public const UInt16 AcDcDistributionMode = 4307; +// } +// +// diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcDistributionMode.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcDcDistributionMode.cs similarity index 69% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcDistributionMode.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcDcDistributionMode.cs index 80f6f625c..64a029118 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcDcDistributionMode.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcDcDistributionMode.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum AcDcDistributionMode : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcErrorPolicy.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcErrorPolicy.cs similarity index 77% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcErrorPolicy.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcErrorPolicy.cs index 8dd96d128..9033c779f 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/AcErrorPolicy.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AcErrorPolicy.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum AcErrorPolicy : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AlarmMessage.cs similarity index 86% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AlarmMessage.cs index df5535008..4d9476fd2 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/AlarmMessage.cs @@ -1,22 +1,12 @@ using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "CommentTypo")] -public enum AlarmMessage +public enum AlarmMessage : UInt16 { - NoAlarm = 0, - BmsCommunicationTimeoutHasOccured = 40302, // BMS communication timeout has occured. - Rs485CommunicationAlarm1 = 40303, // RS-485 communication alarm. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch1 = 40412, // Software versions of system control and module(s) do not match. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch2 = 40413, // Software versions of system control and module(s) do not match. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch3 = 40414, // Software versions of system control and module(s) do not match. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch4 = 40415, // Software versions of system control and module(s) do not match. - SoftwareVersionsOfSystemControlAndModulesDoNotMatch5 = 40416, // Software versions of system control and module(s) do not match. - NoSlaveModuleWasFoundPleaseCheckRs485Connection = 40304, // No slave module was found, please check RS-485 connection(s). - NumberOfOrCombinationOfConnectedSlaveTypesNotSupported = 40305, // Number of or combination of connected slave types not supported. OvertemperatureIgbtBridge1 = 50000, // Overtemperature IGBT bridge 1. OvertemperatureIgbtBridge2 = 50001, // Overtemperature IGBT bridge 2. OvertemperatureIgbtBridge3 = 50002, // Overtemperature IGBT bridge 3. diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcPrechargeConfig.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcPrechargeConfig.cs new file mode 100644 index 000000000..7eddb6be1 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcPrechargeConfig.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum DcPrechargeConfig : UInt16 +{ + WaitForExternalPrecharge = 0, + PrechargeDcWithSystemCtl = 1, + PrechargeDcWithDcDcs = 2, + PrechargeDcWithSystemCtlAndWait = 3, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/DcStageConfiguration.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcStageConfiguration.cs similarity index 89% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/DcStageConfiguration.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcStageConfiguration.cs index 32ee16596..58a6e58e9 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/DcStageConfiguration.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/DcStageConfiguration.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum DcStageConfiguration : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/GridMode.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/GridMode.cs new file mode 100644 index 000000000..6ac2cca49 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/GridMode.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum GridMode +{ + GridTied = 0, + Island = 1 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/InverterState.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/InverterState.cs new file mode 100644 index 000000000..f1adab68b --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/InverterState.cs @@ -0,0 +1,13 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum InverterState : UInt16 +{ + Idle = 0, + DcLinkChargeDischargeTest = 1, + DcLinkSyncToExt = 2, + DcLinkCharge = 3, + AcSyncToGrid = 4, + AcCloseContactor = 5, + AcConnected = 6, + Alarm = 99 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PhaseControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PhaseControl.cs new file mode 100644 index 000000000..5c433899e --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PhaseControl.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum PhaseControl : Byte +{ + Asymmetric = 0, + Symmetric = 1, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PowerFactorConvention.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerFactorConvention.cs similarity index 69% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PowerFactorConvention.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerFactorConvention.cs index cfdaec558..7134db9a8 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PowerFactorConvention.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerFactorConvention.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum PowerFactorConvention : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerLimit.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerLimit.cs new file mode 100644 index 000000000..39f6be32f --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PowerLimit.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum PowerLimit : UInt16 +{ + Nothing = 0, + DcLink = 1, + GridCode = 2, + Overload = 3, + Temperature = 4, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PreChargeDcLinkConfig.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PreChargeDcLinkConfig.cs similarity index 88% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PreChargeDcLinkConfig.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PreChargeDcLinkConfig.cs index 206c656fd..1f53d038e 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/PreChargeDcLinkConfig.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/PreChargeDcLinkConfig.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum PreChargeDcLinkConfig : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/ReactivePowerKind.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/ReactivePowerKind.cs new file mode 100644 index 000000000..52a0ac452 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/ReactivePowerKind.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +public enum ReactivePowerKind +{ + Inductive = 0, + Capacitive = 1 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SymmetricAcOperationMode.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/SymmetricAcOperationMode.cs similarity index 68% rename from csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SymmetricAcOperationMode.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/SymmetricAcOperationMode.cs index df9d01eca..ce1767c40 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SymmetricAcOperationMode.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/SymmetricAcOperationMode.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; public enum SymmetricAcOperationMode : UInt16 { diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/WarningMessage.cs new file mode 100644 index 000000000..11ec4cdd2 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/DataTypes/WarningMessage.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +[SuppressMessage("ReSharper", "IdentifierTypo")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "CommentTypo")] + +public enum WarningMessage : UInt16 +{ + NoWarning = 00000, + + /* + + // these warnings are not official (not in the manual), and they seem to collide with the DCDC warnings + // so I commented them + + Fan = 10500, //AC-DC module warning + IOffset = 10503, //AC-DC module warning + VgOffset = 10504, //AC-DC module warning + VcOffset = 10505, //AC-DC module warning + DcOffset = 10506, //AC-DC module warning + NtcProtect = 10507, //DC-link circuit need more time for cool down + AirTemp = 10508, //Overtemperature inlet air: power is derated + SurgeDetected = 10509, //Temporary overvoltage in grid measurement detected (surge) + TempDerating = 11021, //Temperature derating active + Overload = 11022, //Overload handling is active + RuntimeEeprom = 11023, //AC-DC module warning + Overcurrent = 11024 //Overcurrent handling is active + + */ +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SubSlavesErrorPolicy.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SubSlavesErrorPolicy.cs deleted file mode 100644 index 190072c0c..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/Enums/SubSlavesErrorPolicy.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; - -public enum SubSlavesErrorPolicy : UInt16 -{ - Strict = 0, // (AC-DC module switches to error state if at least one submodules is in error state - Relaxed = 1, // (AC-DC module switches to error state if all submodules are in error state.) - Off = 2, // (If possible AC-DC module continues operation even if all submodules are in error state. -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Program.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Program.cs new file mode 100644 index 000000000..6af219c56 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Program.cs @@ -0,0 +1,61 @@ +// using InnovEnergy.Lib.Devices.Trumpf.SystemControl; +// using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +// using InnovEnergy.Lib.Units; +// using InnovEnergy.Lib.Utils; +// +// namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; +// +// // TODO :remove +// +// public static class Program +// { +// public static void Main(String[] args) +// { +// var sc = new SystemControlDevice("localhost", 5001); +// var acDc1 = sc.AcDcSlave(1); +// var acDc2 = sc.AcDcSlave(2); +// +// +// while (true) +// { +// "================================================".WriteLine(); +// +// var r = sc.Read(); +// +// Console.WriteLine(DateTime.Now); +// r.ToString().Replace(",", "\n").Replace(" {", "\n").WriteLine("\n"); +// +// var c = r with +// { +// UseSlaveIdForAddressing = true, +// ReferenceFrame = ReferenceFrame.Consumer, +// GridType = GridType.GridTied400V50Hz, +// SystemConfig = SystemConfig.AcDcAndDcDc, +// CommunicationTimeout = null, +// SlaveErrorHandling = SlaveErrorHandling.Relaxed, +// SubSlaveErrorHandling = SubSlaveErrorHandling.Off +// }; +// +// sc.Write(c); +// +// var s1 = acDc1.Read(); +// +// s1.ToCsv().Replace(",", "\n").Replace(" {", "\n").WriteLine("\n"); +// +// +// s1.ResetAlarmsAndWarnings = true; +// s1.PowerStageEnable = true; +// +// acDc1.Write(s1); +// +// var s2 = acDc2.Read(); +// +// +// s2.ToString().Replace(",", "\n").Replace(" {", "\n").WriteLine("\n"); +// +// acDc2.Write(s2 with { ResetAlarmsAndWarnings = true, PowerStageEnable = true }); +// } +// +// +// } +// } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/AcDcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/AcDcStatus.cs new file mode 100644 index 000000000..12d4061c7 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/AcDcStatus.cs @@ -0,0 +1,109 @@ + +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Units.Composite; +using AlarmMessage = InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes.AlarmMessage; +using WarningMessage = InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes.WarningMessage; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class AcDcStatus +{ + public Ac3Bus Ac => Ac3Bus.FromPhasesAndFrequency(L1, L2, L3, _Self.GridFrequency); + public PowerLimit PowerLimitedBy => _Self.PowerLimitedBy; + public InverterStates InverterState => new(_Self); + public GridType ActiveGridType => _Self.ActiveGridType; + public Voltages DcVoltages => new(_Self); + public Temperatures Temperature => new(_Self); + public OverloadCapacity OverloadCapacity => new(_Self); + public Nominals Nominal => new(_Self); + + public IReadOnlyList Alarms => GetAlarms ().Take(_Self.NumberOfAlarms ).Where(IsAcDcAlarm ).ToList(); + public IReadOnlyList Warnings => GetWarnings().Take(_Self.NumberOfWarnings).Where(IsAcDcWarning).ToList(); + + private IEnumerable GetAlarms() + { + yield return _Self.Alarm1; + yield return _Self.Alarm2; + yield return _Self.Alarm3; + yield return _Self.Alarm4; + yield return _Self.Alarm5; + yield return _Self.Alarm6; + yield return _Self.Alarm7; + yield return _Self.Alarm8; + yield return _Self.Alarm9; + yield return _Self.Alarm10; + yield return _Self.Alarm11; + yield return _Self.Alarm12; + yield return _Self.Alarm13; + yield return _Self.Alarm14; + yield return _Self.Alarm15; + yield return _Self.Alarm16; + yield return _Self.Alarm17; + yield return _Self.Alarm18; + yield return _Self.Alarm19; + yield return _Self.Alarm20; + } + + private static Boolean IsAcDcAlarm(AlarmMessage alarm) => (UInt16)alarm is >= 50000 and < 60000; + + private IEnumerable GetWarnings() + { + yield return _Self.Warning1; + yield return _Self.Warning2; + yield return _Self.Warning3; + yield return _Self.Warning4; + yield return _Self.Warning5; + yield return _Self.Warning6; + yield return _Self.Warning7; + yield return _Self.Warning8; + yield return _Self.Warning9; + yield return _Self.Warning10; + yield return _Self.Warning11; + yield return _Self.Warning12; + yield return _Self.Warning13; + yield return _Self.Warning14; + yield return _Self.Warning15; + yield return _Self.Warning16; + yield return _Self.Warning17; + yield return _Self.Warning18; + yield return _Self.Warning19; + yield return _Self.Warning20; + } + + // TODO: there are no AcDc Warnings defined in doc + private static Boolean IsAcDcWarning(WarningMessage warning) => warning != WarningMessage.NoWarning; + + private AcPhase L1 => AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + voltageRms : _Self.GridVoltageL1, + currentRms : _Self.GridCurrentL1, + activePower : _Self.ActivePowerL1, + reactivePower: _Self.ReactivePowerL1, + apparentPower: _Self.ApparentPowerL1 + ); + + private AcPhase L2 => AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + voltageRms : _Self.GridVoltageL2, + currentRms : _Self.GridCurrentL2, + activePower : _Self.ActivePowerL2, + reactivePower: _Self.ReactivePowerL2, + apparentPower: _Self.ApparentPowerL2 + ); + + private AcPhase L3 => AcPhase.FromVoltageCurrentActiveReactiveApparent + ( + voltageRms : _Self.GridVoltageL3, + currentRms : _Self.GridCurrentL3, + activePower : _Self.ActivePowerL3, + reactivePower: _Self.ReactivePowerL3, + apparentPower: _Self.ApparentPowerL3 + ); + + + internal AcDcStatus(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; + +} + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/ExternVoltages.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/ExternVoltages.cs new file mode 100644 index 000000000..d349302c2 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/ExternVoltages.cs @@ -0,0 +1,13 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class ExternVoltages +{ + public Voltage UpperHalf => _Self.ExternDcVoltageUpperHalf; + public Voltage LowerHalf => _Self.ExternDcVoltageLowerHalf; + public Voltage NToPe => _Self.ExternVoltageNToPe; + + internal ExternVoltages(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InternVoltages.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InternVoltages.cs new file mode 100644 index 000000000..634e29c4b --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InternVoltages.cs @@ -0,0 +1,13 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class InternVoltages +{ + public Voltage DcUpperHalf => _Self.InternDcVoltageUpperHalf; + public Voltage DcLowerHalf => _Self.InternDcVoltageLowerHalf; + public Voltage NToPe => _Self.InternVoltageNToPe; + + private readonly AcDcRecord _Self; + internal InternVoltages(AcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InverterStates.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InverterStates.cs new file mode 100644 index 000000000..39bd1482c --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/InverterStates.cs @@ -0,0 +1,12 @@ +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class InverterStates +{ + public InverterState Current => _Self.InverterState; + public InverterState OnLastAlarm => _Self.StateOnLastAlarm; + + internal InverterStates(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Nominals.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Nominals.cs new file mode 100644 index 000000000..6959419d9 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Nominals.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class Nominals +{ + public Frequency AcFrequency => _Self.NominalAcFrequency; + public Voltage AcVoltage => _Self.NominalAcVoltage; + public DcPower Power => _Self.NominalPower; + + private readonly AcDcRecord _Self; + internal Nominals(AcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/OverloadCapacity.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/OverloadCapacity.cs new file mode 100644 index 000000000..a49a9d2e3 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/OverloadCapacity.cs @@ -0,0 +1,13 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class OverloadCapacity +{ + public Percent L1 => _Self.OverloadCapacityL1; + public Percent L2 => _Self.OverloadCapacityL2; + public Percent L3 => _Self.OverloadCapacityL3; + + internal OverloadCapacity(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Temperatures.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Temperatures.cs new file mode 100644 index 000000000..06845ef5a --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Temperatures.cs @@ -0,0 +1,15 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + +public class Temperatures +{ + public Temperature InletAir => _Self.InletAirTemperature; + public Temperature IgbtL1 => _Self.IgbtL1Temperature; + public Temperature IgbtL2 => _Self.IgbtL2Temperature; + public Temperature IgbtL3 => _Self.IgbtL3Temperature; + public Temperature IgbtBalancer => _Self.IgbtBalancerTemperature; + + private readonly AcDcRecord _Self; + internal Temperatures(AcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Voltages.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Voltages.cs new file mode 100644 index 000000000..feb1ce640 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/Status/Voltages.cs @@ -0,0 +1,11 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Status; + + +public class Voltages +{ + public InternVoltages Intern => new(_Self); + public ExternVoltages Extern => new(_Self); + + internal Voltages(AcDcRecord self) => _Self = self; + private readonly AcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj index 34e08f495..85ad936c7 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj @@ -5,10 +5,12 @@ + + - + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs deleted file mode 100644 index 9844f5f5b..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs +++ /dev/null @@ -1,85 +0,0 @@ -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; - -public record TruConvertAcControl -{ - private static readonly TimeSpan DefaultCommunicationTimeOut = TimeSpan.FromSeconds(10); - - public UInt32 Date { get; init;} - public UInt32 Time { get; init;} - public UInt32 IpAddress { get; init;} //= 0x C0A80102; - public UInt32 Subnet { get; init;} //= 0x FFFFFF00; - public UInt32 Gateway { get; init;} //= 0x C0A80102; - public Boolean ResetParamToDefault { get; init;} = false ; // Coil - public TimeSpan CommunicationTimeout { get; init;} = DefaultCommunicationTimeOut; - public Boolean FactoryResetParameters { get; init;} = false; - public SystemConfig ConnectedSystemConfig { get; init;} = 0; - public UInt16 UpdateSwTrigger { get; init;} = 0; - public UInt16 AutomaticSwUpdate { get; init;} = 0; - public UInt16 CustomerValuesSaveReset { get; init;} = 0; - public UInt16 SerialNumberSystemControl { get; init;} = 0; - public UInt16 SerialNumberAcDc { get; init;} = 0; - public UInt16 IntegrationLevel { get; init;} = 0; - public UInt16 IlBuildnumber { get; init;} = 0; - public Boolean PowerStageEnable { get; init;} = true; - public SymmetricAcOperationMode SetValueConfig { get; init;} = 0; - public Boolean ResetsAlarmAndWarning { get; init;} = false; - public PreChargeDcLinkConfig PreChargeDcLinkConfig { get; init;} = (PreChargeDcLinkConfig)0; - public PowerFactorConvention PowerFactorConvention { get; init;} = 0; //0 = producer - public UInt16 SlaveAddress { get; init;} = Slave.Broadcast; - public AcErrorPolicy ErrorHandlingPolicy { get; init;} = 0; - public AcDcGridType GridType { get; init;} = 0; - public UInt16 SubSlaveAddress { get; init;} = 0; - public Boolean UseModbusSlaveIdForAddressing { get; init;} = false; - public UInt16 SubSlaveErrorPolicy { get; init;} = 0; // must be an enum - public Decimal SignedPowerNominalValue { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal SignedPowerSetValueL1 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal SignedPowerSetValueL2 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal SignedPowerSetValueL3 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal PowerSetValue { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal PowerSetValueL1 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal PowerSetValueL2 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal PowerSetValueL3 { get; init;} = 0; // resolution 0.001 and Unit kva, - public Decimal MaximumGridCurrentRmsL1 { get; init;} = 0; // resolution : 0.01 - public Decimal MaximumGridCurrentRmsL2 { get; init;} = 0; // resolution : 0.01 - public Decimal MaximumGridCurrentRmsL3 { get; init;} = 0; // resolution : 0.01 - public Decimal CosPhiSetValueL1 { get; init;} = 0; // resolution : 0.01 - public Decimal CosPhiSetValueL2 { get; init;} = 0; // resolution : 0.01 - public Decimal CosPhiSetValueL3 { get; init;} = 0; // resolution : 0.01 - public Boolean PhaseL1IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - public Boolean PhaseL2IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - public Boolean PhaseL3IsCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - public Boolean PhasesAreCapacitive { get; init;} = true; // True = Capacitive, false = Inductive - public Double SetPointCosPhi { get; init;} = 0; // resolution 0.01 - public Double SetPointSinPhi { get; init;} = 0; // resolution 0.01 - public Double SetPointSinPhiL1 { get; init;} = 0; // resolution 0.01 - public Double SetPointSinPhiL2 { get; init;} = 0; // resolution 0.01 - public Double SetPointSinPhiL3 { get; init;} = 0; // resolution 0.01 - public Decimal FrequencyOffsetIm { get; init;} = 0; // resolution 0.01 - public UInt16 VoltageAdjustmentFactorIm { get; init;} = 0; - public UInt16 PreChargeDcLinkVoltage { get; init;} = 0; - public Decimal MaxPeakCurrentVoltageControlL1 { get; init;} = 0; // resolution 0.01 - public Decimal MaxPeakCurrentVoltageControlL2 { get; init;} = 0; // resolution 0.01 - public Decimal MaxPeakCurrentVoltageControlL3 { get; init;} = 0; // resolution 0.01 - public UInt16 GridFormingMode { get; init;} = 1; // 0 = not grid-forming (grid-tied) ,1 = grid-forming - public UInt16 DcLinkRefVoltage { get; init;} = 800; - public UInt16 DcLinkMinVoltage { get; init;} = 780; - public UInt16 DcLinkMaxVoltage { get; init;} = 820; - public UInt16 DcVoltageRefUs { get; init;} = 900; - public UInt16 DcMinVoltageUs { get; init;} = 880; - public UInt16 DcMaxVoltageUs { get; init;} = 920; - // Need to discuss this with Ivo - // public UInt16 FrequencySlopeIslandMode { get; init;} = 200; // resolution 0.01 - // public UInt16 VoltageSlopeIslandMode { get; init;} = 500; // resolution 0.01 - public UInt16 AcDcGcBypassMode { get; init;} = 0; - public UInt16 AcDcGcPMaxThresholdPercent { get; init;} = 0; // resolution 0.01 - public UInt16 AcDcGcStartupRampEnable { get; init;} = 0; - public DcStageConfiguration DcConfigModule { get; init;} = 0; // this must be an enum - public UInt16 DcDcPowerDistribution { get; init;} = 0; // 0.1 resolution - public AcDcDistributionMode AcDcDistributionMode { get; init;} = 0; - - - -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDcDevices.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDcDevices.cs new file mode 100644 index 000000000..8a049cbb8 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDcDevices.cs @@ -0,0 +1,75 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; + +public class TruConvertAcDcDevices +{ + private readonly ModbusDevice _SystemControl; + private readonly IEnumerable> _AcDcs; + + public TruConvertAcDcDevices(String hostname, UInt16 port) : this(new TcpChannel(hostname, port)) + { + } + + public TruConvertAcDcDevices(Channel transport) + { + var modbusClient = new ModbusTcpClient(transport, 0); + + _SystemControl = new ModbusDevice(modbusClient); + + _AcDcs = Enumerable + .Range(1, Byte.MaxValue - 1) + .Memoize(CreateAcDc); + + ModbusDevice CreateAcDc(Int32 i) + { + var mb = new ModbusTcpClient(transport, (Byte)i); + return new ModbusDevice(mb); + } + } + + + public AcDcDevicesRecord Read() + { + try + { + var scStatus = _SystemControl.Read(); + var n = scStatus.NumberOfConnectedSlaves; + + var acDcRecords = _AcDcs + .Take(n) + .Select(acDc => acDc.Read()) + .ToArray(n); + + return new AcDcDevicesRecord(scStatus, acDcRecords); + } + catch + { + return new AcDcDevicesRecord(null, Array.Empty()); + } + } + + public void Write(AcDcDevicesRecord r) + { + if (r.SystemControl is not null) + _SystemControl.Write(r.SystemControl); // must run BEFORE the attached devices + + foreach (var (ctrl, device) in r.Devices.Zip(_AcDcs)) + { + try + { + device.Write(ctrl); + } + catch (Exception e) + { + Console.WriteLine(e); + // TODO: log + } + } + } + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs deleted file mode 100644 index f964f0441..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs +++ /dev/null @@ -1,330 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; -using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Units.Composite; -using InnovEnergy.Lib.Utils; -using static DecimalMath.DecimalEx; -using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters; - - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; - -using UInt16s = IReadOnlyList; - -[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] -public class TruConvertAcDevice -{ - private ModbusTcpClient ModbusTcpClient { get; } - - public TruConvertAcDevice(String hostname, UInt16 port = ModbusTcpClient.DefaultPort, Byte slaveAddress = 0) - { - var connection = new ModbusTcpConnection(hostname, port); - ModbusTcpClient = new ModbusTcpClient(connection, slaveAddress); - } - - - public void WriteControl(TruConvertAcControl c) - { - /* - WriteRegs(AcControlRegisters.Date, new List { c.Date.ConvertTo()}); - WriteRegs(AcControlRegisters.Time, new List { c.Time.ConvertTo()}); - WriteRegs(AcControlRegisters.IpAddress, new List { c.IpAddress.ConvertTo()}); - WriteRegs(AcControlRegisters.Subnet, new List { c.Subnet.ConvertTo()}); - WriteRegs(AcControlRegisters.Gateway, new List { c.Gateway.ConvertTo()}); - WriteCoils(AcControlRegisters.ResetParamToDefault, c.ResetParamToDefault); - WriteCoils(AcControlRegisters.FactoryResetParameters, c.FactoryResetParameters); - ModbusTcpClient.WriteRegisters(AcControlRegisters.UpdateSwTrigger, c.UpdateSwTrigger, c.AutomaticSwUpdate, c.CustomerValuesSaveReset); - */ - - - WriteRegs(CommunicationTimeout, c.CommunicationTimeout.TotalSeconds.ConvertTo()); - - WriteRegs(ConnectedSystemConfig, c.ConnectedSystemConfig); - - - WriteCoils(PowerStageConfig, c.PowerStageEnable, - c.SetValueConfig.ConvertTo(), - c.ResetsAlarmAndWarning); - WriteRegs(PreChargeDcLinkConfigR, c.PreChargeDcLinkConfig, - c.PowerFactorConvention, c.SlaveAddress, - c.ErrorHandlingPolicy, - c.GridType, c.SubSlaveAddress); - WriteCoils(ModbusSlaveId, c.UseModbusSlaveIdForAddressing); - WriteRegs(SubSlaveErrorPolicy, c.SubSlaveErrorPolicy); - - WriteRegs(SignedPowerNominalValue, -1.0m, c.SignedPowerNominalValue);/*, c.SignedPowerSetValueL1, - c.SignedPowerSetValueL2, c.SignedPowerSetValueL3, - c.PowerSetValue, c.PowerSetValueL1, - c.PowerSetValueL2, c.PowerSetValuesL3);*/ - - - WriteRegs(MaximumGridCurrentRmsL1, 0.01m, c.MaximumGridCurrentRmsL1, c.MaximumGridCurrentRmsL2, - c.MaximumGridCurrentRmsL3, c.CosPhiSetValueL1, - c.CosPhiSetValueL2, c.CosPhiSetValueL3); - - WriteCoils(PhaseL1IsCapacitive, c.PhaseL1IsCapacitive, - c.PhaseL2IsCapacitive, - c.PhaseL3IsCapacitive, - c.PhasesAreCapacitive); - - /* WriteRegs(SetPointCosPhi, 0.01m, c.SetPointCosPhi.ConvertTo(), - c.SetPointSinPhi.ConvertTo(), - c.SetPointSinPhiL1.ConvertTo(), - c.SetPointSinPhiL2.ConvertTo(), - c.SetPointSinPhiL3.ConvertTo(), - c.FrequencyOffsetIm);*/ - - WriteRegs(VoltageAdjustmentFactorIm, c.VoltageAdjustmentFactorIm); - WriteRegs(PreChargeDcLinkVoltage, c.PreChargeDcLinkVoltage); - WriteRegs(MaxPeakCurrentVoltageControlL1, 0.01m, c.MaxPeakCurrentVoltageControlL1, - c.MaxPeakCurrentVoltageControlL2, - c.MaxPeakCurrentVoltageControlL3); - WriteRegs(GridFormingMode, c.GridFormingMode, c.DcLinkRefVoltage, - c.DcLinkMinVoltage, c.DcLinkMaxVoltage, - c.DcVoltageRefUs, c.DcMinVoltageUs, c.DcMaxVoltageUs); - WriteRegs(AcDcGcBypassMode, c.AcDcGcBypassMode); - WriteRegs(AcDcGcPMaxThresholdPercent, 0.01m, c.AcDcGcPMaxThresholdPercent); - WriteRegs(AcDcGcStartupRampEnable, c.AcDcGcStartupRampEnable); - WriteRegs(DcConfigModule, c.DcConfigModule); - WriteRegs(DcDcPowerDistribution, 0.1m, c.DcDcPowerDistribution); - WriteRegs(AcControlRegisters.AcDcDistributionMode, c.AcDcDistributionMode); - } - - private void WriteRegs (UInt16 a, Decimal res = 1.0m, params Decimal[] regs) => ModbusTcpClient.WriteRegisters(a, regs.ToUInt16(res)); - private void WriteRegs (UInt16 a, params IConvertible[] regs) => ModbusTcpClient.WriteRegisters(a, regs.Select(v => v.ConvertTo()).ToArray()); - private void WriteRegs (UInt16 a, params UInt16[] regs) => ModbusTcpClient.WriteRegisters(a, regs); - private void WriteCoils(UInt16 a, params Boolean[] coils) => ModbusTcpClient.WriteMultipleCoils(a, coils); - - private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos); - - public TruConvertAcStatus? ReadStatus() - { - try - { - return TryReadStatus(); - } - catch (Exception e) - { - ModbusTcpClient.CloseConnection(); - Console.WriteLine("Failed to read inverter status"); - e.Message.WriteLine(); - - return null; - } - } - - private TruConvertAcStatus TryReadStatus() - { - // Console.WriteLine("Reading Ac Device"); - - var acSerialNumber = ModbusTcpClient.ReadInputRegisters(2009, 2); - var acActualMain = ModbusTcpClient.ReadInputRegisters(5001, 3); - var acActualAcDc = ModbusTcpClient.ReadInputRegisters(5021, 9); - var acActualAcDc2 = ModbusTcpClient.ReadInputRegisters(5031, 1); - var acActualAcDc3 = ModbusTcpClient.ReadInputRegisters(5131, 6); - var acActualMeasurement = ModbusTcpClient.ReadInputRegisters(5141, 3); - var acActualMeasurement1 = ModbusTcpClient.ReadInputRegisters(5151, 3); - var acActualMeasurement2 = ModbusTcpClient.ReadInputRegisters(5161, 3); - var acActualMeasurement3 = ModbusTcpClient.ReadInputRegisters(5171, 3); - var acActualMeasurement4 = ModbusTcpClient.ReadInputRegisters(5187, 2); - var acActualMeasurement5 = ModbusTcpClient.ReadInputRegisters(5189, 2); - var acActualMeasurement6 = ModbusTcpClient.ReadInputRegisters(5191, 2); - var acActualMeasurement7 = ModbusTcpClient.ReadInputRegisters(5201, 1); - var acActualMeasurement8 = ModbusTcpClient.ReadInputRegisters(5211, 4); - var acActualMeasurement9 = ModbusTcpClient.ReadInputRegisters(5221, 2); - var acActualTemp = ModbusTcpClient.ReadInputRegisters(5501, 1); - var acWarningValues = ModbusTcpClient.ReadInputRegisters(2402, 22); - var acAlarmValues = ModbusTcpClient.ReadInputRegisters(2809, 22); - var acSetValues = ModbusTcpClient.ReadInputRegisters(4196, 1); - - var warnings = Enumerable - .Range(2404, 20) - .Select(n => acWarningValues.GetUInt16((UInt16)n).ConvertTo()) - .ToArray(); - - var alarms = Enumerable - .Range(2811, 20) - .Select(n => acAlarmValues.GetUInt16((UInt16)n).ConvertTo()) - .Where(m => m != AlarmMessage.NoAlarm) - .ToArray(); - - - var dcPower = acActualMeasurement.GetInt16(5141) * 1m + acActualMeasurement.GetInt16(5142) * 1m + acActualMeasurement.GetInt16(5143) * 1m; - var dcVoltage = acActualMeasurement8.GetUInt16(5214) + acActualMeasurement8.GetUInt16(5213); - var dcCurrent = dcVoltage != 0m - ? dcPower / dcVoltage - : 0m; - - - // //acActualMeasurement - // PowerAcL1 = acActualMeasurement.GetInt16(5141) * 1m, // in Watt - // PowerAcL2 = acActualMeasurement.GetInt16(5142) * 1m, // in Watt - // PowerAcL3 = acActualMeasurement.GetInt16(5143) * 1m, // in Watt - // - //acActualMeasurement1 - // PhaseCurrentL1 = acActualMeasurement1.GetUInt16(5151) * 0.01m, - // PhaseCurrentL2 = acActualMeasurement1.GetUInt16(5152) * 0.01m, - // PhaseCurrentL3 = acActualMeasurement1.GetUInt16(5153) * 0.01m, - - //acActualMeasurement2 - // GridVoltageL1 = acActualMeasurement2.GetUInt16(5161) * 0.1m, - // GridVoltageL2 = acActualMeasurement2.GetUInt16(5162) * 0.1m, - // GridVoltageL3 = acActualMeasurement2.GetUInt16(5163) * 0.1m, - - //acActualMeasurement3 - // CosPhiL1 = acActualMeasurement3.GetInt16(5171) * 0.01m, - // CosPhiL2 = acActualMeasurement3.GetInt16(5172) * 0.01m, - // CosPhiL3 = acActualMeasurement3.GetInt16(5173) * 0.01m, - - // //acActualMeasurement4 - // SumPowerL1 = acActualMeasurement4.GetUInt32(5187) * 1m, // in Watt - // //acActualMeasurement5 - // SumPowerL2 = acActualMeasurement5.GetUInt32(5189) * 1m, // in Watt - // //acActualMeasurement6 - // SumPowerL3 = acActualMeasurement6.GetUInt32(5191) * 1m, // in Watt - // //acActualMeasurement9 - // GridFrequency = acActualMeasurement7.GetInt16(5201) * 0.01m, - - //acActualMeasurement11 - // VoltageIntNtoPE = acActualMeasurement9.GetInt16(5221) * 0.1m, - // VoltageExtNtoPE = acActualMeasurement9.GetInt16(5222) * 0.1m, - - // - // ApparentPowerAcL1 = acActualAcDc3.GetUInt16(5131) * 1m, // in VA - // ApparentPowerAcL2 = acActualAcDc3.GetUInt16(5132) * 1m, // in VA - // ApparentPowerAcL3 = acActualAcDc3.GetUInt16(5133) * 1m, // in VA - - var apparentPowerAcL1 = acActualAcDc3.GetUInt16(5131) * 1m; - var apparentPowerAcL2 = acActualAcDc3.GetUInt16(5132) * 1m; - var apparentPowerAcL3 = acActualAcDc3.GetUInt16(5133) * 1m; - - var powerAcL1 = acActualMeasurement.GetInt16(5141) * 1m; // in Watt - var powerAcL2 = acActualMeasurement.GetInt16(5142) * 1m; // in Watt - var powerAcL3 = acActualMeasurement.GetInt16(5143) * 1m; // in Watt - - var phaseCurrentL1 = acActualMeasurement1.GetUInt16(5151) * 0.01m; - var phaseCurrentL2 = acActualMeasurement1.GetUInt16(5152) * 0.01m; - var phaseCurrentL3 = acActualMeasurement1.GetUInt16(5153) * 0.01m; - - var gridVoltageL1 = acActualMeasurement2.GetUInt16(5161) * 0.1m; - var gridVoltageL2 = acActualMeasurement2.GetUInt16(5162) * 0.1m; - var gridVoltageL3 = acActualMeasurement2.GetUInt16(5163) * 0.1m; - - var gridFrequency = acActualMeasurement7.GetInt16(5201) * 0.01m; - - return new TruConvertAcStatus - { - Ac = new Ac3Bus - { - Frequency = gridFrequency, - - L1 = new AcPhase - { - Voltage = gridVoltageL1, - Current = phaseCurrentL1, - Phi = ACos(powerAcL1 / apparentPowerAcL1), // TODO: 2pi - }, - L2 = new AcPhase - { - Voltage = gridVoltageL2, - Current = phaseCurrentL2, - Phi = ACos(powerAcL2 / apparentPowerAcL2), // TODO: 2pi - }, - L3 = new AcPhase - { - Voltage = gridVoltageL3, - Current = phaseCurrentL3, - Phi = ACos(powerAcL3 / apparentPowerAcL3), // TODO: 2pi - } - }, - Dc = new DcBus - { - Current = dcCurrent, - Voltage = dcVoltage, - }, - - MainState = acActualMain.GetInt16(5001).ConvertTo(), - Alarms = alarms, - Warnings = warnings, - GridType = acActualAcDc.GetUInt16(5024).ConvertTo(), - SerialNumber = acSerialNumber.GetInt32(2009).ToString(), // TODO: why tostring ? - NumberOfConnectedSlaves = acActualMain.GetUInt16(5002), - NumberOfConnectedSubSlaves = acActualMain.GetUInt16(5003), - AcDcNominalGridFrequency = acActualAcDc.GetUInt16(5021) * 0.1m, - AcDcNominalGridVoltage = acActualAcDc.GetUInt16(5022), - AcDcActNominalPower = acActualAcDc.GetUInt16(5023), - AcDcPowerLimitingStatusAct = acActualAcDc.GetUInt16(5025), - AcDcDcVoltageReference = acActualAcDc.GetUInt16(5026), // DC link reference - AcDcDcLinkVoltageMinAct = acActualAcDc.GetUInt16(5027), // DC link min voltage - AcDcDcLinkVoltageMaxAct = acActualAcDc.GetUInt16(5028), // DC link max voltage - AcDcDcLinkChargedMinVoltage = acActualAcDc.GetUInt16(5029) * 0.01m, - AcDcStmActCustomer = acActualAcDc2.GetUInt16(5031), //need to check - AcDcOverloadIntegratorStatusL1 = acActualAcDc3.GetUInt16(5134) * 0.1m, - AcDcOverloadIntegratorStatusL2 = acActualAcDc3.GetUInt16(5135) * 0.1m, - AcDcOverloadIntegratorStatusL3 = acActualAcDc3.GetUInt16(5136) * 0.1m, - AcSignedPowerValue = acSetValues.GetInt16(4196) * -1.0m, // this is also used for control - ActualDcLinkVoltageUpperHalf = acActualMeasurement8.GetUInt16(5211), - ActualDcLinkVoltageLowerHalf = acActualMeasurement8.GetUInt16(5212), - ActualDcLinkVoltageUpperHalfExt = acActualMeasurement8.GetUInt16(5213), - ActualDcLinkVoltageLowerHalfExt = acActualMeasurement8.GetUInt16(5214), - VoltageIntNtoPe = acActualMeasurement9.GetInt16(5221) * 0.1m, - VoltageExtNtoPe = acActualMeasurement9.GetInt16(5222) * 0.1m, - InletAirTemperature = acActualTemp.GetInt16(5501) * 0.1m, - - }; - - - - // ( - // Ac: new Ac3Bus - // ( - // new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)), - // new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)), - // new AcPhase(gridVoltageL3,phaseCurrentL3, ACos(powerAcL3/apparentPowerAcL3)), - // gridFrequency // Gird Frequency - // ), - // Dc: new DcConnection(dcVoltage, dcCurrent), - // - // SerialNumber : acSerialNumber.GetInt32(2009).ToString(), - // - // // acActualMainValues - // MainState : acActualMain.GetInt16(5001).ConvertTo(), - // NumberOfConnectedSlaves : acActualMain.GetUInt16(5002), - // NumberOfConnectedSubSlaves : acActualMain.GetUInt16(5003), - // - // //acActualAcDc - // AcDcNominalGridFrequency : acActualAcDc.GetUInt16(5021) * 0.1m, - // AcDcNominalGridVoltage : acActualAcDc.GetUInt16(5022), - // AcDcActNominalPower : acActualAcDc.GetUInt16(5023), - // AcDcActiveGridType : acActualAcDc.GetUInt16(5024).ConvertTo(), - // AcDcPowerLimitingStatusAct : acActualAcDc.GetUInt16(5025), - // AcDcDcVoltageReference : acActualAcDc.GetUInt16(5026), // DC link reference - // AcDcDcLinkVoltageMinAct : acActualAcDc.GetUInt16(5027), // DC link min voltage - // AcDcDcLinkVoltageMaxAct : acActualAcDc.GetUInt16(5028), // DC link max voltage - // AcDcDcLinkChargedMinVoltage : acActualAcDc.GetUInt16(5029) * 0.01m, - // - // //ac Actual AcDc 2 - // AcDcStmActCustomer : acActualAcDc2.GetUInt16(5031), //need to check - // AcDcOverloadIntegratorStatusL1 : acActualAcDc3.GetUInt16(5134) * 0.1m, - // AcDcOverloadIntegratorStatusL2 : acActualAcDc3.GetUInt16(5135) * 0.1m, - // AcDcOverloadIntegratorStatusL3 : acActualAcDc3.GetUInt16(5136) * 0.1m, - // AcSignedPowerValue : acSetValues.GetInt16(4196) * -1.0m, // this is also used for control - // - // //acActualMeasurement10 - // ActualDcLinkVoltageUpperHalf : acActualMeasurement8.GetUInt16(5211), - // ActualDcLinkVoltageLowerHalf : acActualMeasurement8.GetUInt16(5212), - // ActualDcLinkVoltageUpperHalfExt : acActualMeasurement8.GetUInt16(5213), - // ActualDcLinkVoltageLowerHalfExt : acActualMeasurement8.GetUInt16(5214), - // - // VoltageIntNtoPe : acActualMeasurement9.GetInt16(5221) * 0.1m, - // VoltageExtNtoPe : acActualMeasurement9.GetInt16(5222) * 0.1m, - // //acActualTemp - // InletAirTemperature : acActualTemp.GetInt16(5501) * 0.1m, - // - // Warnings : warnings, - // Alarms : alarms - // ); - } -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs deleted file mode 100644 index b605d9ecc..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; - -using AlarmMessages = IReadOnlyList; -using WarningMessages = IReadOnlyList; - - -// ReSharper disable UnusedAutoPropertyAccessor.Global -#pragma warning disable CS8618 - - -public record TruConvertAcStatus : ThreePhaseInverterStatus -{ - public MainState MainState { get; init; } - public String SerialNumber { get; init; } - public AcDcGridType GridType { get; init; } - public WarningMessages Warnings { get; init; } - public AlarmMessages Alarms { get; init; } - public Decimal NumberOfConnectedSlaves { get; init; } - public Decimal NumberOfConnectedSubSlaves { get; init; } - public Frequency AcDcNominalGridFrequency { get; init; } - public Voltage AcDcNominalGridVoltage { get; init; } - public Power AcDcActNominalPower { get; init; } - public Decimal AcDcPowerLimitingStatusAct { get; init; } // TODO: enum - public Voltage AcDcDcVoltageReference { get; init; } - public Voltage AcDcDcLinkVoltageMinAct { get; init; } - public Voltage AcDcDcLinkVoltageMaxAct { get; init; } - public Voltage AcDcDcLinkChargedMinVoltage { get; init; } - public Decimal AcDcStmActCustomer { get; init; } - public Decimal AcDcOverloadIntegratorStatusL1 { get; init; } - public Decimal AcDcOverloadIntegratorStatusL2 { get; init; } - public Decimal AcDcOverloadIntegratorStatusL3 { get; init; } - public Power AcSignedPowerValue { get; init; } - public Voltage ActualDcLinkVoltageUpperHalf { get; init; } - public Voltage ActualDcLinkVoltageLowerHalf { get; init; } - public Voltage ActualDcLinkVoltageUpperHalfExt { get; init; } - public Voltage ActualDcLinkVoltageLowerHalfExt { get; init; } - public Voltage VoltageIntNtoPe { get; init; } - public Voltage VoltageExtNtoPe { get; init; } - public Temperature InletAirTemperature { get; init; } -} diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs deleted file mode 100644 index 4ceffc228..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; - -[SuppressMessage("ReSharper", "IdentifierTypo")] -[SuppressMessage("ReSharper", "UnusedMember.Global")] -[SuppressMessage("ReSharper", "CommentTypo")] - -public enum WarningMessage -{ - ERR_WARN_FAN = 10500, //AC-DC module warning - ERR_WARN_I_OFFSET = 10503, //AC-DC module warning - ERR_WARN_VG_OFFSET = 10504, //AC-DC module warning - ERR_WARN_VC_OFFSET = 10505, //AC-DC module warning - ERR_WARN_DC_OFFSET = 10506, //AC-DC module warning - ERR_WARN_NTC_PROTECT = 10507, //DC-link circuit need more time for cool down - ERR_WARN_AIR_TEMP = 10508, //Overtemperature inlet air: power is derated - SurgeDetected = 10509, //Temporary overvoltage in grid measurement detected (surge) - ERR_WARN_TEMP_DERATING = 11021, //Temperature derating active - ERR_WARN_OVERLOAD = 11022, //Overload handling is active - ERR_WARN_RUNTIME_EEPROM = 11023, //AC-DC module warning - ERR_WARN_OVERCURRENT = 11024 //Overcurrent handling is active -} - diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs deleted file mode 100644 index 8c9ef6251..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -[SuppressMessage("ReSharper", "IdentifierTypo")] -[SuppressMessage("ReSharper", "UnusedMember.Global")] -[SuppressMessage("ReSharper", "CommentTypo")] -public enum AlarmMessage -{ - NoAlarm = 0, // No Alarm - Iboosterhv = 60081, // DC-DC power module alarm - Iboosterhv1 = 60082, // wildcard to allocate TypeMessageNumber - Iboosterhv2 = 60083, // wildcard to allocate TypeMessageNumber - Ibat = 60084, // DC-DC power module alarm - Ibat1 = 60085, // wildcard to allocate TypeMessageNumber - Ibat2 = 60086, // wildcard to allocate TypeMessageNumber - Itrafolv = 60087, // DC-DC power module alarm - Itrafolv1 = 60088, // wildcard to allocate TypeMessageNumber - Itrafolv2 = 60089, // wildcard to allocate TypeMessageNumber - UbatHigh = 60090, // Battery overvoltage - UbatHigh1 = 60091, // wildcard to allocate TypeMessageNumber - UbatHigh2 = 60092, // wildcard to allocate TypeMessageNumber - Udclp = 60093, // DC link overvoltage - Udclp1 = 60094, // wildcard to allocate TypeMessageNumber - Udclp2 = 60095, // wildcard to allocate TypeMessageNumber - Udcln = 60096, // DC link overvoltage (lower half) - Udcln1 = 60097, // wildcard to allocate TypeMessageNumber - Udcln2 = 60098, // wildcard to allocate TypeMessageNumber - Udclllc = 60099, // DC-DC power module alarm - Udclllc1 = 60100, // wildcard to allocate TypeMessageNumber - Udclllc2 = 60101, // wildcard to allocate TypeMessageNumber - UdclLow = 60102, // DC link voltage too low for operation - UdclLow1 = 60103, // wildcard to allocate TypeMessageNumber - UdclLow2 = 60104, // wildcard to allocate TypeMessageNumber - VauxHigh = 60700, // Auxiliary supply overvoltage - VauxHigh1 = 60701, // wildcard to allocate TypeMessageNumber - VauxHigh2 = 60702, // wildcard to allocate TypeMessageNumber - UbatLow = 60142, // Battery undervoltage - UbatLow1 = 60143, // wildcard to allocate TypeMessageNumber - UbatLow2 = 60144, // wildcard to allocate TypeMessageNumber - VauxLow = 60703, // Auxiliary supply undervoltage - VauxLow1 = 60704, // wildcard to allocate TypeMessageNumber - VauxLow2 = 60705, // wildcard to allocate TypeMessageNumber - IbatIboosterPlausi = 60197, // DC-DC power module alarm -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/CurrentControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/CurrentControl.cs new file mode 100644 index 000000000..a37303051 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/CurrentControl.cs @@ -0,0 +1,22 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + +public class CurrentControl +{ + public Current CurrentSetpoint + { + get => _Self.DcDcCurrentSetpoint; + set => _Self.DcDcCurrentSetpoint = value.Value; + } + + public Current MaxCurrentChangePerMs + { + get => _Self.MaxCurrentChangePerMs; + set => _Self.MaxCurrentChangePerMs = value.Value; + } + + private readonly DcDcRecord _Self; + internal CurrentControl(DcDcRecord self) => _Self = self; +} + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcControlMode.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcControlMode.cs new file mode 100644 index 000000000..9d096ef93 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcControlMode.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + +public enum DcControlMode : UInt16 +{ + CurrentSetpoint = 0, + VoltageDroop = 1 +} diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcDcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcDcControl.cs new file mode 100644 index 000000000..b2cc6b2b4 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DcDcControl.cs @@ -0,0 +1,43 @@ +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + + +// https://stackoverflow.com/q/63724308 + + +public class DcDcControl +{ + public Vcc Vcc => new(_Self); + public VoltageLimits VoltageLimits => new(_Self); + public DroopControl DroopControl => new(_Self); + public CurrentControl CurrentControl => new(_Self); + + public ActivePower MaxDcPower + { + get => _Self.MaxDcPower; + set => _Self.MaxDcPower = value.Value; + } + + public DcControlMode ControlMode + { + get => _Self.DcControlMode; + set => _Self.DcControlMode = value; + } + + public Boolean ResetAlarmsAndWarnings + { + get => _Self.ResetAlarmsAndWarnings; + set => _Self.ResetAlarmsAndWarnings = value; + } + + public Boolean PowerStageEnable + { + get => _Self.PowerStageEnable; + set => _Self.PowerStageEnable = value; + } + + private readonly DcDcRecord _Self; + internal DcDcControl(DcDcRecord self) => _Self = self; +} + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DroopControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DroopControl.cs new file mode 100644 index 000000000..ecc226da9 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/DroopControl.cs @@ -0,0 +1,33 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + +public class DroopControl +{ + public Voltage ReferenceVoltage + { + get => _Self.DroopControlReferenceVoltage; + set => _Self.DroopControlReferenceVoltage = value.Value; + } + + public Voltage LowerVoltage + { + get => _Self.DroopControlLowerVoltage; + set => _Self.DroopControlLowerVoltage = value.Value; + } + + public Voltage UpperVoltage + { + get => _Self.DroopControlUpperVoltage; + set => _Self.DroopControlUpperVoltage = value.Value; + } + + public Voltage VoltageDeadband + { + get => _Self.DroopControlVoltageDeadband; + set => _Self.DroopControlVoltageDeadband = value.Value; + } + + private readonly DcDcRecord _Self; + internal DroopControl(DcDcRecord self) => _Self = self; +}; \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/Vcc.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/Vcc.cs new file mode 100644 index 000000000..0185ea6d1 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/Vcc.cs @@ -0,0 +1,27 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + +public class Vcc +{ + public Current EndPointCurrent + { + get => _Self.VccEndPointCurrent; + set => _Self.VccEndPointCurrent = value.Value; + } + + public Voltage EndPointVoltage + { + get => _Self.VccEndPointCurrent; + set => _Self.VccEndPointCurrent = value.Value; + } + + public Current StartPointCurrent + { + get => _Self.VccStartPointCurrent; + set => _Self.VccStartPointCurrent = value.Value; + } + + private readonly DcDcRecord _Self; + internal Vcc(DcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/VoltageLimits.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/VoltageLimits.cs new file mode 100644 index 000000000..ce8941f46 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Control/VoltageLimits.cs @@ -0,0 +1,33 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; + +public class VoltageLimits +{ + public Voltage MinBatteryVoltageAlarm + { + get => _Self.MinVoltageAlarm; + set => _Self.MinVoltageAlarm = value.Value; + } + + public Voltage MaxBatteryVoltageAlarm + { + get => _Self.MaxVoltageAlarm; + set => _Self.MaxVoltageAlarm = value.Value; + } + + public Voltage MinBatteryVoltage + { + get => _Self.MinBatteryVoltage; + set => _Self.MinBatteryVoltage = value.Value; + } + + public Voltage MaxBatteryVoltage + { + get => _Self.MaxBatteryVoltage; + set => _Self.MaxBatteryVoltage = value.Value; + } + + private readonly DcDcRecord _Self; + internal VoltageLimits(DcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs deleted file mode 100644 index 90b0552b6..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -public static class DcControlRegisters -{ - public const UInt16 Date = 1001; - public const UInt16 Time = 1003; - public const UInt16 IpAddress = 1005; - public const UInt16 Subnet = 1007; - public const UInt16 Gateway = 1009; - public const UInt16 ResetParamToDefault = 1011; - public const UInt16 TimeoutCommunication = 1017; - public const UInt16 RestartFlag = 1018; - public const UInt16 ConnectedSystemConfig = 1019; - public const UInt16 UpdateSwTrigger = 1027; - public const UInt16 AutomaticSwUpdate = 1028; - public const UInt16 CustomerValuesSaveReset = 1029; - public const UInt16 SerialNumberSystemControl = 2001; - public const UInt16 SerialNumberDcDc = 2003; - public const UInt16 MaterialNumberDcDc = 2005; - public const UInt16 PowerStageOperation = 4001; - public const UInt16 ResetsAlarmAndWarning = 4003; - public const UInt16 SlaveAddress = 4008; - public const UInt16 SlaveAlarmPolicy = 4009; - public const UInt16 SubSlaveAddress = 4011; - public const UInt16 ModbusSlaveId = 4012; - public const UInt16 MaximumBatteryVoltage = 4101; - public const UInt16 MinimumBatteryVoltage = 4102; - public const UInt16 MaximumBatteryVoltageR = 4103; // same as the two previous ones just Different resolution// not sure - public const UInt16 MinimumBatteryVoltageR = 4104; // same as the two previous ones just Different resolution// not sure - public const UInt16 MaximumBatteryChargingCurrent = 4107; - public const UInt16 MaximumBatteryDischargingCurrent = 4110; - public const UInt16 MaximumVoltageOfVcc = 4113; - public const UInt16 MaximumCurrentOfVcc = 4116; - public const UInt16 StartCurrentOfVcc = 4119; - public const UInt16 MaximalPowerAtDc = 4122; - public const UInt16 MaximumVoltageAlarmThreshold = 4125; - public const UInt16 MinimumVoltageAlarmThreshold = 4128; - - //DcDc operation only - public const UInt16 BatteryCurrentSet = 4501; - public const UInt16 DynamicCurrentPerMillisecond = 4502; - - public const UInt16 DcLinkControlMode = 4505; - public const UInt16 ReferenceVoltage = 4506; - public const UInt16 UpperVoltageWindow = 4507; - public const UInt16 LowerVoltageWindow = 4508; - public const UInt16 VoltageDeadBand = 4509; -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcCurrentLimitState.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcCurrentLimitState.cs deleted file mode 100644 index 1895221af..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcCurrentLimitState.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -[Flags] -public enum DcCurrentLimitState : UInt16 -{ - PMax = 0b_00001, - MaxChargingCurrent = 0b_00010, - MaxDischargingCurrent = 0b_00100, - MaxBatteryVoltage = 0b_01000, - MinBatteryVoltage = 0b_10000, -} - diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcDevicesRecord.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcDevicesRecord.cs new file mode 100644 index 000000000..e1491a49e --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcDevicesRecord.cs @@ -0,0 +1,42 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +public class DcDcDevicesRecord +{ + private static readonly DcBus NoDevice = DcBus.FromVoltageCurrent(0, 0); + + public DcDcDevicesRecord(SystemControlRegisters? systemControl, IReadOnlyList devices) + { + SystemControl = systemControl; + Devices = devices; + } + + public DcStatus Dc => new DcStatus + { + Battery = Devices.Count == 0 ? NoDevice + : DcBus.FromVoltageCurrent + ( + Devices.Average(r => r.Status.Dc.Battery.Voltage.Value), + Devices.Sum(r => r.Status.Dc.Battery.Current.Value) + ), + + Link = Devices.Count == 0 + ? NoDevice + : DcBus.FromVoltageCurrent + ( + Devices.Average(r => r.Status.Dc.Link.Voltage.Value), + Devices.Sum(r => r.Status.Dc.Link.Current.Value) + ) + }; + + public SystemControlRegisters? SystemControl { get; } + public IReadOnlyList Devices { get; } + + public IEnumerable Alarms => Devices.SelectMany(d => d.Status.Alarms ).Distinct(); + public IEnumerable Warnings => Devices.SelectMany(d => d.Status.Warnings).Distinct(); + + public static DcDcDevicesRecord Null => new DcDcDevicesRecord(null, Array.Empty()); +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Alarms.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Alarms.cs new file mode 100644 index 000000000..0c30b93f1 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Alarms.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +#pragma warning disable CS0649 + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +public partial class DcDcRecord +{ + private IEnumerable GetAlarms() + { + yield return Alarm1; + yield return Alarm2; + yield return Alarm3; + yield return Alarm4; + yield return Alarm5; + yield return Alarm6; + yield return Alarm7; + yield return Alarm8; + yield return Alarm9; + yield return Alarm10; + yield return Alarm11; + yield return Alarm12; + yield return Alarm13; + yield return Alarm14; + yield return Alarm15; + yield return Alarm16; + yield return Alarm17; + yield return Alarm18; + yield return Alarm19; + yield return Alarm20; + } + + internal IReadOnlyList Alarms => GetAlarms() + .Take(NumberOfAlarms) + .Where(IsDcDcAlarm) + .ToList(); + + private static Boolean IsDcDcAlarm(AlarmMessage alarm) => (UInt16)alarm >= 60000; +} + + + + + + + + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Modbus.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Modbus.cs new file mode 100644 index 000000000..62a8396e2 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Modbus.cs @@ -0,0 +1,116 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +#pragma warning disable CS0649 + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +public partial class DcDcRecord +{ + //[Coil(4000)] + [HoldingRegister(4000)] internal Boolean PowerStageEnable ; + + //[Coil(4002)] + [HoldingRegister(4002)] internal Boolean ResetAlarmsAndWarnings ; + + [HoldingRegister(4100, Scale = .01)] internal Double MaxBatteryVoltage ; + [HoldingRegister(4101, Scale = .01)] internal Double MinBatteryVoltage ; + + [HoldingRegister(4112, Scale = .1)] internal Double VccEndPointVoltage ; + [HoldingRegister(4115)] internal Double VccEndPointCurrent ; + [HoldingRegister(4118)] internal Double VccStartPointCurrent ; + + [HoldingRegister(4118)] internal Double MaxDcPower ; + + [HoldingRegister(4124, Scale = .1)] internal Double MaxVoltageAlarm ; + [HoldingRegister(4127, Scale = .1)] internal Double MinVoltageAlarm ; + + [HoldingRegister(4500)] internal Double DcDcCurrentSetpoint ; + [HoldingRegister(4501, Scale = .01)] internal Double MaxCurrentChangePerMs ; + + [HoldingRegister(4504)] internal DcControlMode DcControlMode ; + + [HoldingRegister(4505, Scale = .1)] internal Double DroopControlReferenceVoltage ; + [HoldingRegister(4506, Scale = .1)] internal Double DroopControlUpperVoltage ; + [HoldingRegister(4507, Scale = .1)] internal Double DroopControlLowerVoltage ; + [HoldingRegister(4508, Scale = .1)] internal Double DroopControlVoltageDeadband ; + + [InputRegister(5100, Scale = .1)] internal Double BatteryVoltage ; + [InputRegister(5110)] internal Double BatteryCurrent ; + + [InputRegister(5123)] internal BatteryPowerLimit BatteryPowerLimit ; + + [InputRegister(5126, Scale = .1)] internal Double OverloadCapacity ; + [InputRegister(5127)] internal Double DcLinkVoltage; + + // TODO: [InputRegister(5300)] + + [InputRegister(5510)] internal Double InletAirTemperature ; + [InputRegister(5511)] internal Double HighVoltageModuleTemperature ; + [InputRegister(5512)] internal Double LowVoltageModuleTemperature ; + + [InputRegister(2402)] internal UInt16 NumberOfWarnings; + + [InputRegister(2403)] internal WarningMessage Warning1; + [InputRegister(2404)] internal WarningMessage Warning2; + [InputRegister(2405)] internal WarningMessage Warning3; + [InputRegister(2406)] internal WarningMessage Warning4; + [InputRegister(2407)] internal WarningMessage Warning5; + [InputRegister(2408)] internal WarningMessage Warning6; + [InputRegister(2409)] internal WarningMessage Warning7; + [InputRegister(2410)] internal WarningMessage Warning8; + [InputRegister(2411)] internal WarningMessage Warning9; + [InputRegister(2412)] internal WarningMessage Warning10; + [InputRegister(2413)] internal WarningMessage Warning11; + [InputRegister(2414)] internal WarningMessage Warning12; + [InputRegister(2415)] internal WarningMessage Warning13; + [InputRegister(2416)] internal WarningMessage Warning14; + [InputRegister(2417)] internal WarningMessage Warning15; + [InputRegister(2418)] internal WarningMessage Warning16; + [InputRegister(2419)] internal WarningMessage Warning17; + [InputRegister(2420)] internal WarningMessage Warning18; + [InputRegister(2421)] internal WarningMessage Warning19; + [InputRegister(2422)] internal WarningMessage Warning20; + + [InputRegister(2809)] internal UInt16 NumberOfAlarms; + + [InputRegister(2810)] internal AlarmMessage Alarm1; + [InputRegister(2811)] internal AlarmMessage Alarm2; + [InputRegister(2812)] internal AlarmMessage Alarm3; + [InputRegister(2813)] internal AlarmMessage Alarm4; + [InputRegister(2814)] internal AlarmMessage Alarm5; + [InputRegister(2815)] internal AlarmMessage Alarm6; + [InputRegister(2816)] internal AlarmMessage Alarm7; + [InputRegister(2817)] internal AlarmMessage Alarm8; + [InputRegister(2818)] internal AlarmMessage Alarm9; + [InputRegister(2819)] internal AlarmMessage Alarm10; + [InputRegister(2820)] internal AlarmMessage Alarm11; + [InputRegister(2821)] internal AlarmMessage Alarm12; + [InputRegister(2822)] internal AlarmMessage Alarm13; + [InputRegister(2823)] internal AlarmMessage Alarm14; + [InputRegister(2824)] internal AlarmMessage Alarm15; + [InputRegister(2825)] internal AlarmMessage Alarm16; + [InputRegister(2826)] internal AlarmMessage Alarm17; + [InputRegister(2827)] internal AlarmMessage Alarm18; + [InputRegister(2828)] internal AlarmMessage Alarm19; + [InputRegister(2829)] internal AlarmMessage Alarm20; + + // #region IDcDc + // + // internal DcBus DcLeft => DcBus.FromVoltageCurrent(DcLinkVoltage, Double.NaN); // HV current not measured + // internal DcBus DcRight => DcBus.FromVoltageCurrent(BatteryVoltage, BatteryCurrent); + // + // #endregion IDcDc +} + + + + + + + + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Warnings.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Warnings.cs new file mode 100644 index 000000000..d229df10a --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.Warnings.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +#pragma warning disable CS0649 + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +public partial class DcDcRecord +{ + private IEnumerable GetWarnings() + { + yield return Warning1; + yield return Warning2; + yield return Warning3; + yield return Warning4; + yield return Warning5; + yield return Warning6; + yield return Warning7; + yield return Warning8; + yield return Warning9; + yield return Warning10; + yield return Warning11; + yield return Warning12; + yield return Warning13; + yield return Warning14; + yield return Warning15; + yield return Warning16; + yield return Warning17; + yield return Warning18; + yield return Warning19; + yield return Warning20; + } + + internal IReadOnlyList Warnings => GetWarnings() + .Take(NumberOfWarnings) + .Where(IsDcDcWarning) + .ToList(); + + private static Boolean IsDcDcWarning(WarningMessage warning) => (UInt16)warning is >= 11000 and < 12000; + +} + + + + + + + + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.cs new file mode 100644 index 000000000..9fca2eae4 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcDcRecord.cs @@ -0,0 +1,10 @@ +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +public partial class DcDcRecord +{ + public DcDcStatus Status => new(this); + public DcDcControl Control => new(this); +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/AlarmMessage.cs new file mode 100644 index 000000000..ddbed76da --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/AlarmMessage.cs @@ -0,0 +1,73 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +[SuppressMessage("ReSharper", "IdentifierTypo")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "CommentTypo")] +public enum AlarmMessage +{ + NoAlarm = 0, + + Iboosterhv = 60081, + Iboosterhv1 = 60082, + Iboosterhv2 = 60083, + + Ibat = 60084, + Ibat1 = 60085, + Ibat2 = 60086, + + Itrafolv = 60087, + Itrafolv2 = 60089, + + BatteryVoltageTooHigh = 60090, + BatteryVoltageTooHigh1 = 60091, + BatteryVoltageTooHigh2 = 60092, + + DcLinkVoltageTooHigh = 60093, + DcLinkVoltageTooHigh1 = 60094, + DcLinkVoltageTooHigh2 = 60095, + + Udcln = 60096, + Udcln1 = 60097, + Udcln2 = 60098, + + Udclllc = 60099, + Udclllc1 = 60100, + Udclllc2 = 60101, + + DcLinkVoltageTooLow = 60102, + DcLinkVoltageTooLow1 = 60103, + DcLinkVoltageTooLow2 = 60104, + + BatteryVoltageUnderTresholdSetting = 60129, + + Rs485CommunicationAlarm = 60132, + + BatteryVoltageTooLow = 60142, + BatteryVoltageTooLow1 = 60143, + BatteryVoltageTooLow2 = 60144, + + WrongDcPolarity = 60145, + + + BatteryVoltageOverTresholdSetting = 60150, + AmbientTemperatureTooHigh = 60168, + AmbientTemperatureTooLow = 60186, + + FanDefectiveOrStuck = 60192, + IbatIboosterPlausi = 60197, + PreChargeConditionsCouldNotBeMet = 60200, + AttachedLoadOnDcLinkCannotBeHandled = 60201, + DcLinkCouldNotBeCharged = 60202, + + AuxiliarySupplyOvervoltage = 60700, + AuxiliarySupplyOvervoltage1 = 60701, + AuxiliarySupplyOvervoltage2 = 60702, + + AuxiliarySupplyUndervoltage = 60703, + AuxiliarySupplyUndervoltage1 = 60704, + AuxiliarySupplyUndervoltage2 = 60705, + DcDcPrecharge = 60200, // DC-DC Precharge Conditions could not be met. + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/BatteryPowerLimit.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/BatteryPowerLimit.cs new file mode 100644 index 000000000..d992497d6 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/BatteryPowerLimit.cs @@ -0,0 +1,11 @@ +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +[Flags] +public enum BatteryPowerLimit : UInt16 +{ + MaxPower = 0b_00001, + MaxChargeCurrent = 0b_00010, + MaxDischargeCurrent = 0b_00100, + MaxVoltage = 0b_01000, + MinVoltage = 0b_10000, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcDcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcDcStatus.cs new file mode 100644 index 000000000..c6635a3ea --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcDcStatus.cs @@ -0,0 +1,118 @@ +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +public class DcDcStatus +{ + public DcStatus Dc + { + get + { + // if battery voltage is 0, Trumpf reports a nonsensical battery current + + var batteryCurrent = _Self.BatteryVoltage == 0 + ? 0 + : _Self.BatteryCurrent; + + // link current is not measured by trumpf, calculate it assuming 0 losses + // TODO: calculate losses + var linkCurrent = batteryCurrent == 0 + ? 0 + : _Self.BatteryVoltage * _Self.BatteryCurrent / _Self.DcLinkVoltage; + + return new() + { + Link = DcBus.FromVoltageCurrent + ( + voltage: _Self.DcLinkVoltage, + current: -linkCurrent // TODO: review sign is reversed + ), + Battery = DcBus.FromVoltageCurrent + ( + voltage: _Self.BatteryVoltage, + current: -batteryCurrent // TODO: review sign is reversed + ) + }; + } + } + + public Percent OverloadCapacity => _Self.OverloadCapacity; + public Temperatures Temperature => new(_Self); + public IReadOnlyList PowerLimitedBy => ListActivePowerLimits(); + public IReadOnlyList Alarms => ListAlarms(); + public IReadOnlyList Warnings => ListWarnings(); + + /////////////////////////////////////////////////////////////////////////////// + + + private AlarmMessage[] ListAlarms() => EnumerateAlarms() + .Take(_Self.NumberOfAlarms) + .ToArray(_Self.NumberOfAlarms); + + private WarningMessage[] ListWarnings() => EnumerateWarnings() + .Take(_Self.NumberOfWarnings) + .ToArray(_Self.NumberOfWarnings); + + private IEnumerable EnumerateWarnings() + { + yield return _Self.Warning1; + yield return _Self.Warning2; + yield return _Self.Warning3; + yield return _Self.Warning4; + yield return _Self.Warning5; + yield return _Self.Warning6; + yield return _Self.Warning7; + yield return _Self.Warning8; + yield return _Self.Warning9; + yield return _Self.Warning10; + yield return _Self.Warning11; + yield return _Self.Warning12; + yield return _Self.Warning13; + yield return _Self.Warning14; + yield return _Self.Warning15; + yield return _Self.Warning16; + yield return _Self.Warning17; + yield return _Self.Warning18; + yield return _Self.Warning19; + yield return _Self.Warning20; + } + + private IEnumerable EnumerateAlarms() + { + yield return _Self.Alarm1; + yield return _Self.Alarm2; + yield return _Self.Alarm3; + yield return _Self.Alarm4; + yield return _Self.Alarm5; + yield return _Self.Alarm6; + yield return _Self.Alarm7; + yield return _Self.Alarm8; + yield return _Self.Alarm9; + yield return _Self.Alarm10; + yield return _Self.Alarm11; + yield return _Self.Alarm12; + yield return _Self.Alarm13; + yield return _Self.Alarm14; + yield return _Self.Alarm15; + yield return _Self.Alarm16; + yield return _Self.Alarm17; + yield return _Self.Alarm18; + yield return _Self.Alarm19; + yield return _Self.Alarm20; + } + + + private List ListActivePowerLimits() + { + return Utils + .Utils + .GetEnumValues() + .Where(t => _Self.BatteryPowerLimit.HasFlag(t)) + .ToList(); + } + + private readonly DcDcRecord _Self; + internal DcDcStatus(DcDcRecord self) => _Self = self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcStatus.cs new file mode 100644 index 000000000..baad91b11 --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/DcStatus.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +public class DcStatus +{ + public DcBus Link { get; init; } + public DcBus Battery { get; init; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/Temperatures.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/Temperatures.cs new file mode 100644 index 000000000..66df3946a --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/Temperatures.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; + +public class Temperatures +{ + public Temperature InletAir => _Self.InletAirTemperature; + public Temperature HighVoltageModule => _Self.HighVoltageModuleTemperature; + public Temperature LowVoltageModule => _Self.LowVoltageModuleTemperature; + + + internal Temperatures(DcDcRecord self) => _Self = self; + private readonly DcDcRecord _Self; +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/WarningMessage.cs similarity index 96% rename from csharp/Lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/Status/WarningMessage.cs index bb44edcbf..2686739c0 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/Status/WarningMessage.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Status; [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "UnusedMember.Global")] @@ -29,5 +29,4 @@ public enum WarningMessage HveepromWrite1 = 11018, //DC-DC module warning HveepromWrite2 = 11019, //DC-DC module warning HveepromWrite3 = 11020 //DC-DC module warning - } \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj index f640e856c..66e6f2cb1 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj @@ -7,8 +7,8 @@ - + diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs deleted file mode 100644 index 82a4e4dcc..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs +++ /dev/null @@ -1,44 +0,0 @@ -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -public record TruConvertDcControl -{ - public UInt32 Date { get; init;} - public UInt32 Time { get; init;} - public UInt32 IpAddress { get; init;} //= 0x C0A80102; - public UInt32 Subnet { get; init;} //= 0x FFFFFF00; - public UInt32 Gateway { get; init;} //= 0x C0A80102; - public Boolean ResetParamToDefault { get; init;} = false ; // Coil - public TimeSpan TimeoutForCommunication { get; init;} = DefaultCommunicationTimeOut; - public Boolean RestartFlag { get; init;} = false ; // Coil - public SystemConfig ConnectedSystemConfig { get; init;} = SystemConfig.NoConfig ; - public UInt16 UpdateSwTrigger { get; init;} = 0 ; - public UInt16 AutomaticSwUpdate { get; init;} = 0 ; - public UInt16 CustomerValuesSaveReset { get; init;} = 0 ; - public UInt32 SerialNumberSystemControl { get; init;} - public UInt32 SerialNumberDcDc { get; init;} - public UInt32 MaterialNumberDcDc { get; init;} - public Boolean PowerStageEnable { get; init;} = true; //Coil - public Boolean ResetsAlarmAndWarning { get; init;} = false; //Coil - public UInt16 SlaveAddress { get; init;} = Slave.Broadcast; - public UInt16 SlaveAlarmPolicy { get; init;} = 0; // this is must be a an enum - public UInt16 SubSlaveAddress { get; init;} = 0; - public Boolean ModbusSlaveId { get; init;} = false; // Coil - public Decimal MaximumBatteryVoltage { get; init;} = 0; // resolution 0.01 - public Decimal MinimumBatteryVoltage { get; init;} = 0; // resolution 0.01 - public Decimal MaximumBatteryChargingCurrent { get; init;} = 0; // resolution 0.1 - public Decimal MaximumBatteryDischargingCurrent { get; init;} = 0; // resolution 0.1 - public Decimal MaximalPowerAtDc { get; init;} = 0; - public Decimal MaximumVoltageAlarmThreshold { get; init;} = 55; // resolution 0.1 - public Decimal MinimumVoltageAlarmThreshold { get; init;} = 0; // resolution 0.1 - public Decimal BatteryCurrentSet { get; init;} = 0; // resolution 1.0 - public Decimal DynamicCurrentPerMillisecond { get; init;} = 0; // resolution : 0.01 - public Decimal DcLinkControlMode { get; init;} = 0; // Parameter aktiviert/deaktiviert "DC link voltage droop mode" - public Decimal ReferenceVoltage { get; init;} = 800; // resolution : 0.1 - public Decimal UpperVoltageWindow { get; init;} = 40; // resolution : 0.1 - public Decimal LowerVoltageWindow { get; init;} = 40; // resolution : 0.1 - public Decimal VoltageDeadBand { get; init;} = 0; // resolution : 0.1 - - private static readonly TimeSpan DefaultCommunicationTimeOut = TimeSpan.FromMinutes(10); -} diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDcDevices.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDcDevices.cs new file mode 100644 index 000000000..54c374dce --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDcDevices.cs @@ -0,0 +1,88 @@ +using InnovEnergy.Lib.Devices.Trumpf.SystemControl; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +public class TruConvertDcDcDevices +{ + private readonly ModbusDevice _SystemControl; + private readonly IEnumerable> _DcDcs; + + public TruConvertDcDcDevices(String hostname, UInt16 port) : this(new TcpChannel(hostname, port)) + { + } + + public TruConvertDcDcDevices(Channel transport) + { + var modbusClient = new ModbusTcpClient(transport, 0); + + _SystemControl = new ModbusDevice(modbusClient); + + _DcDcs = Enumerable + .Range(1, Byte.MaxValue - 1) + .Memoize(CreateDcDc); + + ModbusDevice CreateDcDc(Int32 i) + { + var mb = new ModbusTcpClient(transport, (Byte)i); + return new ModbusDevice(mb); + } + } + + + public DcDcDevicesRecord Read() + { + SystemControlRegisters? scStatus; + + try + { + scStatus = _SystemControl.Read(); + } + catch (Exception e) + { + Console.WriteLine(e); + return DcDcDevicesRecord.Null; + } + + var n = scStatus.NumberOfConnectedSlaves; + + try + { + var dcDcRecords = _DcDcs + .Take(n) + .Select(dcdc => dcdc.Read()) + .ToArray(n); + + return new DcDcDevicesRecord(scStatus, dcDcRecords); + } + catch (Exception e) + { + Console.WriteLine(e); + return new DcDcDevicesRecord(scStatus, Array.Empty()); + } + } + + public void Write(DcDcDevicesRecord r) + { + if (r.SystemControl is not null) + _SystemControl.Write(r.SystemControl); // must run BEFORE the attached devices + + foreach (var (ctrl, device) in r.Devices.Zip(_DcDcs)) + { + try + { + device.Write(ctrl); + } + catch (Exception e) + { + Console.WriteLine(e); + // TODO: log + } + } + } + + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs deleted file mode 100644 index b7d0bc201..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Units.Composite; -using InnovEnergy.Lib.Utils; -using static InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.DcControlRegisters; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] -public class TruConvertDcDevice -{ - private ModbusTcpClient ModbusTcpClient { get; } - - public TruConvertDcDevice(String hostname, UInt16 port = ModbusTcpClient.DefaultPort, Byte slaveAddress = 0) - { - var connection = new ModbusTcpConnection(hostname, port); - ModbusTcpClient = new ModbusTcpClient(connection, slaveAddress); - } - - - public void WriteControl(TruConvertDcControl c) - { - - /* - TODO: maybe later - - ModbusTcpClient.WriteRegisters(Date, new[] { dcControl.Date.ConvertTo() }); - ModbusTcpClient.WriteRegisters(Time, new[] { dcControl.Time.ConvertTo() }); - ModbusTcpClient.WriteRegisters(IpAddress, dcControl.IpAddress.ConvertTo()); - ModbusTcpClient.WriteRegisters(Subnet, dcControl.Subnet.ConvertTo()); - ModbusTcpClient.WriteRegisters(Gateway, dcControl.Gateway.ConvertTo()); - ModbusTcpClient.WriteMultipleCoils(ResetParamToDefault, dcControl.ResetParamToDefault); - ModbusTcpClient.WriteMultipleCoils(RestartFlag, dcControl.RestartFlag); - ModbusTcpClient.WriteRegisters(DcControlRegisters.TimeoutCommunication, dcControl.TimeoutForCommunication.TotalSeconds.ConvertTo()); - ModbusTcpClient.WriteRegisters(ConnectedSystemConfig, dcControl.ConnectedSystemConfig.ConvertTo()); - ModbusTcpClient.WriteRegisters(UpdateSwTrigger, dcControl.UpdateSwTrigger, dcControl.AutomaticSwUpdate, dcControl.CustomerValuesSaveReset); - ModbusTcpClient.WriteRegisters(SerialNumberSystemControl, dcControl.SerialNumberSystemControl.ConvertTo()); - ModbusTcpClient.WriteRegisters(SerialNumberDcDc, dcControl.SerialNumberDcDc.ConvertTo()); - ModbusTcpClient.WriteRegisters(MaterialNumberDcDc, dcControl.MaterialNumberDcDc.ConvertTo()); - - */ - // TODO starting from 4000, evaluate what/if needs updating below 4000 - - WriteRegs(TimeoutCommunication, c.TimeoutForCommunication.TotalSeconds.ConvertTo()); - - WriteCoils(PowerStageOperation, c.PowerStageEnable); - WriteCoils(ResetsAlarmAndWarning, c.ResetsAlarmAndWarning); - - WriteRegs (SlaveAddress, c.SlaveAddress, - c.SlaveAlarmPolicy); - - WriteRegs (SubSlaveAddress, c.SubSlaveAddress); - WriteCoils(ModbusSlaveId, c.ModbusSlaveId); - - WriteRegs(MaximumBatteryVoltage, 0.01m, c.MaximumBatteryVoltage, - c.MinimumBatteryVoltage); - - WriteRegs(MaximumBatteryChargingCurrent, 0.1m, c.MaximumBatteryChargingCurrent); - WriteRegs(MaximumBatteryDischargingCurrent, 0.1m, c.MaximumBatteryDischargingCurrent); - - WriteRegs(MaximalPowerAtDc, 1.0m, c.MaximalPowerAtDc); - WriteRegs(MaximumVoltageAlarmThreshold, 0.1m, c.MaximumVoltageAlarmThreshold); - WriteRegs(MinimumVoltageAlarmThreshold, 0.1m, c.MinimumVoltageAlarmThreshold); - - WriteRegs(BatteryCurrentSet, 1.0m, c.BatteryCurrentSet); - WriteRegs(DynamicCurrentPerMillisecond, 0.01m, c.DynamicCurrentPerMillisecond); - - WriteRegs(DcLinkControlMode, 1.0m, c.DcLinkControlMode); - WriteRegs(ReferenceVoltage, 0.1m, c.ReferenceVoltage, - c.UpperVoltageWindow, - c.LowerVoltageWindow, - c.VoltageDeadBand); - } - - private void WriteRegs (UInt16 a, Decimal res = 1.0m, params Decimal[] regs) => ModbusTcpClient.WriteRegisters(a, regs.ToUInt16(res)); - private void WriteRegs (UInt16 a, params UInt16[] regs) => ModbusTcpClient.WriteRegisters(a, regs); - private void WriteCoils(UInt16 a, params Boolean[] coils) => ModbusTcpClient.WriteMultipleCoils(a, coils); - - public TruConvertDcStatus? ReadStatus() - { - try - { - return TryReadStatus(); - } - catch (Exception) - { - ModbusTcpClient.CloseConnection(); - return null; - } - } - - - private static IEnumerable GetFlags(UInt16 input) - { - var ls = (DcCurrentLimitState)input; - - return Enum - .GetValues() - .Where(f => ls.HasFlag(f)); - } - - - private TruConvertDcStatus TryReadStatus() - { - // Console.WriteLine("Reading DC Device"); - var dcPrValMain = ModbusTcpClient.ReadInputRegisters(5001, 3); - var dcBatteryValue = ModbusTcpClient.ReadInputRegisters(5101, 1); - var dcBatteryValue2 = ModbusTcpClient.ReadInputRegisters(5111, 1); - var dcBatteryValue3 = ModbusTcpClient.ReadInputRegisters(5114, 2); - var dcBatteryValue4 = ModbusTcpClient.ReadInputRegisters(5121, 1); - var dcPrValDcDc = ModbusTcpClient.ReadInputRegisters(5124, 1); - var dcPrValDcDc2 = ModbusTcpClient.ReadInputRegisters(5127, 2); - var dcTempValue = ModbusTcpClient.ReadInputRegisters(5511, 1); - var dcWarningValues = ModbusTcpClient.ReadInputRegisters(2404, 20); - var dcAlarmValues = ModbusTcpClient.ReadInputRegisters(2811, 20); - var dcSetValues = ModbusTcpClient.ReadInputRegisters(4001, 1); - - var warnings = Enumerable - .Range(2404, 20) - .Select(n => dcWarningValues.GetUInt16((UInt16)n).ConvertTo()) - .Where(m => m != WarningMessage.NoWarning) - .ToArray(); - - var alarms = Enumerable - .Range(2811, 20) - .Select(n => dcAlarmValues.GetUInt16((UInt16)n).ConvertTo()) - .Where(m => m != AlarmMessage.NoAlarm) - .ToArray(); - - var dcCurrentLimitState = GetFlags(dcPrValDcDc.GetUInt16(5124)).ToArray(); - - var dcLinkVoltage = dcPrValDcDc2.GetUInt16(5128); - - var dcPower = dcBatteryValue4.GetInt16(5121) * 1m; - - var dcCurrent = dcLinkVoltage != 0m ? dcPower / dcLinkVoltage : 0m; - - return new TruConvertDcStatus - { - Left = new DcBus() - { - Current = dcCurrent, - Voltage = dcLinkVoltage - }, - - Right = new DcBus() - { - Current = dcBatteryValue2.GetInt16(5111), - Voltage =dcBatteryValue.GetUInt16(5101) * 0.1m, - }, - - MainState = (MainState)dcPrValMain.GetInt16(5001), - NumberOfConnectedSlaves = dcPrValMain.GetUInt16(5002), - NumberOfConnectedSubSlaves = dcPrValMain.GetUInt16(5003), - TotalDcPower = dcBatteryValue3.GetInt32(5114) * 1m, // Resolution is 0.001 (kW) in Tru convert DC doc, but we want it in W - StatusOfCurrentLimiting = dcCurrentLimitState, - OverloadCapacity = dcPrValDcDc2.GetUInt16(5127) * 0.1m, - DcDcInletTemperature = dcTempValue.GetInt16(5511), - Warnings = warnings, - Alarms = alarms, - PowerOperation = dcSetValues.GetBoolean(4001), - - }; - } - -} \ No newline at end of file diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs deleted file mode 100644 index 1937849fa..000000000 --- a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs +++ /dev/null @@ -1,29 +0,0 @@ -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; -using InnovEnergy.Lib.Units.Composite; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -using AlarmMessages = IReadOnlyList; -using WarningMessages = IReadOnlyList; -using DcCurrentLimitStates = IReadOnlyList; - -public record TruConvertDcStatus : DcDcConverterStatus -{ - public MainState MainState { get; init; } - public Power TotalDcPower { get; init; } // TODO: necessary? - public DcCurrentLimitStates StatusOfCurrentLimiting { get; init; } - public Decimal OverloadCapacity { get; init; } - public Temperature DcDcInletTemperature { get; init; } - public AlarmMessages Alarms { get; init; } = Array.Empty(); - public WarningMessages Warnings { get; init; } = Array.Empty(); - public Boolean PowerOperation { get; init; } - public Decimal NumberOfConnectedSlaves { get; init; } // TODO: necessary? - public Decimal NumberOfConnectedSubSlaves { get; init; } // TODO: necessary? -} -// { -// public static TruConvertDcStatus operator |(TruConvertDcStatus left, TruConvertDcStatus right) => OpParallel(left, right); -// private static readonly Func OpParallel = Operators.Op("|"); -// } \ No newline at end of file diff --git a/csharp/Lib/Logging/Log.cs b/csharp/Lib/Logging/Log.cs new file mode 100644 index 000000000..7a532924b --- /dev/null +++ b/csharp/Lib/Logging/Log.cs @@ -0,0 +1,30 @@ +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Reactive.Subjects; + +namespace InnovEnergy.Lib.Logging; + +public static class Logger +{ + private static readonly Subject Subject = new Subject(); + + public static IObservable Messages { get; } = Subject.ObserveOn(TaskPoolScheduler.Default); + + + public static void Log(String message, Dictionary properties, params String[] tags) + { + var t = new HashSet(tags); + Log(message, properties, t); + } + + private static void Log(String message, Dictionary properties, HashSet tags) + { + var logMessage = new LogMessage(message, tags, properties); + Subject.OnNext(logMessage); + } + + + + + +} \ No newline at end of file diff --git a/csharp/Lib/Logging/LogMessage.cs b/csharp/Lib/Logging/LogMessage.cs new file mode 100644 index 000000000..078cf0a37 --- /dev/null +++ b/csharp/Lib/Logging/LogMessage.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.Logging; + +public readonly record struct LogMessage +( + String Message, + HashSet Tags, + Dictionary Properties +); \ No newline at end of file diff --git a/csharp/Lib/Logging/Logging.csproj b/csharp/Lib/Logging/Logging.csproj new file mode 100644 index 000000000..279fa8e59 --- /dev/null +++ b/csharp/Lib/Logging/Logging.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/csharp/Lib/Protocols/Modbus/Channels/Channel.cs b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs new file mode 100644 index 000000000..9fd8ec731 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public abstract class Channel +{ + public abstract IReadOnlyList Read(Int32 nBytes); + public abstract void Write(IReadOnlyList bytes); +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs new file mode 100644 index 000000000..bafbe947c --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs @@ -0,0 +1,82 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public abstract class ConnectionChannel : Channel, IDisposable +{ + private readonly Func _CloseAfterException ; + private readonly Boolean _CloseAfterSuccessfulRead ; + private readonly Boolean _CloseAfterSuccessfulWrite ; + + protected abstract T Open(); + protected abstract void Close(T connection); + + protected abstract IReadOnlyList Read (T connection, Int32 nBytes); + protected abstract void Write(T connection, IReadOnlyList data); + + private T? _Connection; + + protected ConnectionChannel(Boolean closeAfterSuccessfulRead = false, + Boolean closeAfterSuccessfulWrite = false, + Func? closeAfterException = null) + { + _CloseAfterSuccessfulRead = closeAfterSuccessfulRead; + _CloseAfterSuccessfulWrite = closeAfterSuccessfulWrite; + _CloseAfterException = closeAfterException ?? (_ => true); + } + + public override IReadOnlyList Read(Int32 nBytes) + { + try + { + return Read(Connection, nBytes); + } + catch (Exception e) + { + if (_CloseAfterException(e)) + Close(); + + throw; + } + finally + { + if (_CloseAfterSuccessfulRead) + Close(); + } + } + + + public override void Write(IReadOnlyList data) + { + try + { + Write(Connection, data); + } + catch (Exception e) + { + if (_CloseAfterException(e)) + Close(); + + throw; + } + finally + { + if (_CloseAfterSuccessfulWrite) + Close(); + } + } + + + private T Connection => _Connection ??= Open(); + + + private void Close() + { + if (_Connection is null) + return; + + Close(_Connection); + _Connection = default; + } + + + public void Dispose() => Close(); +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/PushToPullHelper.cs b/csharp/Lib/Protocols/Modbus/Channels/PushToPullHelper.cs new file mode 100644 index 000000000..115b97795 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/PushToPullHelper.cs @@ -0,0 +1,42 @@ +using System.Reactive.Linq; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public static class PushToPullHelper +{ + // TODO: this is incredibly hacky, improve + + public static Func>> PushToPull(this IObservable src) + { + var buffer = new Queue(); + + var nAvailable = src + .Do(buffer.Enqueue) + .Select(_ => buffer.Count) + .Publish() + .RefCount(); + + nAvailable.SelectError() + .Subscribe(e => e.WriteLine()); + + async Task> Read(Int32 n) + { + Console.WriteLine($"requesting {n}"); + + var available = buffer.Count; + if (available < n) + available = await nAvailable.FirstOrDefaultAsync(a => a >= n); + + if (available < n) + throw new Exception("Connection closed"); + + return Enumerable + .Range(0, n) + .Select(_ => buffer.Dequeue()) + .ToArray(n); + } + + return Read; + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs new file mode 100644 index 000000000..98ecd1cd5 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs @@ -0,0 +1,210 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Ports; +using System.Reactive.Linq; +using CliWrap; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public record RemoteSerialConnection + ( + Func> Read, + Action> Write, + Action Close + ); + + +// public class RemoteSerialChannel : ConnectionChannel +// { +// private readonly Command _Command; +// +// public RemoteSerialChannel(SshHost host, +// String tty, +// Int32 baudRate, +// Parity parity, +// Int32 stopBits, +// Int32 dataBits) +// { +// tty = tty.EnsureStartsWith("/dev/"); +// +// var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits); +// var redirectStreams = RedirectStreams(tty); +// +// var call = $"{configureTty}; {redirectStreams}"; +// +// _Command = host +// .Command +// .AppendArgument(call); +// +// _Command.WriteLine(); +// } +// +// +// protected override RemoteSerialConnection Open() +// { +// var observableProcess = new ObservableProcess(_Command); +// +// observableProcess.Start(); +// +// +// IReadOnlyList Read(Int32 i) +// { +// return observableProcess.Read(i).Result; +// } +// +// void Write(IReadOnlyList data) => observableProcess.StdIn.OnNext(data.ToArray()); +// +// return new RemoteSerialConnection(Read, Write, observableProcess.Interrupt); +// } +// +// protected override void Close(RemoteSerialConnection connection) +// { +// connection.Close(); +// } +// +// protected override IReadOnlyList Read(RemoteSerialConnection connection, Int32 nBytes) +// { +// return connection.Read(nBytes); +// } +// +// protected override void Write(RemoteSerialConnection connection, IReadOnlyList data) +// { +// connection.Write(data); +// } +// +// +// private static String RedirectStreams(String tty) +// { +// // https://unix.stackexchange.com/questions/19604/all-about-ssh-proxycommand +// +// return $"exec 3<>{tty}; " + +// $"cat <&3 & cat >&3; " + +// // $"(cat <&3 | tee -a ~/read) & cat | tee -a ~/write >&3; " + +// $"kill $!"; +// +// // var call = $"trap 'kill -HUP $(jobs -lp) 2>/dev/null || true' EXIT; " + +// // $"{configure} ; "+ +// // $"dd if={tty} of=/dev/stdout bs=1 & " + +// // $"dd if=/dev/stdin of={tty} bs=1 ;" +// } +// +// [SuppressMessage("ReSharper", "StringLiteralTypo")] +// private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits) +// { +// var oParity = parity switch +// { +// Parity.Even => "parenb -parodd", +// Parity.Odd => "parenb parodd", +// Parity.None => "-parenb", +// _ => throw new NotImplementedException() +// }; +// +// var oStopBits = stopBits switch +// { +// 1 => "-cstopb", +// 2 => "cstopb", +// _ => throw new NotImplementedException() +// }; +// +// var oDataBits = "cs" + dataBits; +// +// return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}"; +// } +// +// } + +public class RemoteSerialChannel : ConnectionChannel +{ + private readonly Command _Command; + private readonly TcpChannel _TcpChannel; + + const String SsDir = "/opt/victronenergy/serial-starter"; + const String KillTasks = "kill $!"; + + private CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource(); + + private CommandTask? _CommandTask; + + public RemoteSerialChannel(SshHost host, + String tty, + Int32 baudRate, + Parity parity, + Int32 dataBits, + Int32 stopBits) + { + const Int32 port = 6855; + + tty = tty.EnsureStartsWith("/dev/"); + + var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits); + + var stopTty = $"{SsDir}/stop-tty.sh {tty}"; + var startTty = $"{SsDir}/start-tty.sh {tty}"; + + // ReSharper disable once StringLiteralTypo + var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw"; + + //var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'"; + //var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}"; + + //var script = $"{configureTty} && {socat} ; {KillTasks}"; + var script = $"{configureTty} && {socat}"; + + _Command = host.Command.AppendArgument(script); + + _Command.WriteLine(); + + _TcpChannel = new TcpChannel(host.HostName, port); + } + + private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits) + { + var oParity = parity switch + { + Parity.Even => "parenb -parodd", + Parity.Odd => "parenb parodd", + Parity.None => "-parenb", + _ => throw new NotImplementedException() + }; + + var oStopBits = stopBits switch + { + 1 => "-cstopb", + 2 => "cstopb", + _ => throw new NotImplementedException() + }; + + var oDataBits = "cs" + dataBits; + + return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}"; + } + + + protected override TcpChannel Open() + { + //_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token); + + //Thread.Sleep(2000); // wait until socat is ready + return _TcpChannel; + } + + protected override void Close(TcpChannel connection) + { + _CancellationTokenSource.Cancel(); + connection.Dispose(); + + _CommandTask = null; + + _CancellationTokenSource = new CancellationTokenSource(); + } + + protected override IReadOnlyList Read(TcpChannel connection, Int32 nBytes) + { + return connection.Read(nBytes); + } + + protected override void Write(TcpChannel connection, IReadOnlyList data) + { + connection.Write(data); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs new file mode 100644 index 000000000..35e6ef694 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs @@ -0,0 +1,63 @@ +using System.IO.Ports; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public class SerialPortChannel : ConnectionChannel +{ + private readonly Func _Open; + + public SerialPortChannel(String portName, + Int32 baudRate, + Parity parity, + Int32 dataBits, + Int32 stopBits, + Boolean closeAfterSuccessfulRead = false, + Boolean closeAfterSuccessfulWrite = false) + : + base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite) + { + var sb = stopBits switch + { + 0 => StopBits.None, + 1 => StopBits.One, + 2 => StopBits.Two, + _ => StopBits.OnePointFive + }; + + _Open = () => + { + var serialPort = new SerialPort(portName, baudRate, parity, dataBits, sb); + serialPort.Open(); + return serialPort; + }; + } + + protected override SerialPort Open() => _Open(); + + protected override void Close(SerialPort serialPort) => serialPort.Dispose(); + + protected override IReadOnlyList Read(SerialPort serialPort, Int32 nBytes) + { + var buffer = new Byte[nBytes]; + + var bytesReceived = 0; + do + { + var received = serialPort.Read(buffer, bytesReceived, nBytes - bytesReceived); + if (received < 0) + throw new NotConnectedException("Serial Connection has been closed"); + + bytesReceived += received; + } + while (bytesReceived < nBytes); + + return buffer; + } + + protected override void Write(SerialPort serialPort, IReadOnlyList data) + { + var array = data.ToArray(); + serialPort.Write(array, 0, array.Length); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs new file mode 100644 index 000000000..f21946408 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs @@ -0,0 +1,48 @@ +using System.Net.Sockets; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels; + +public class TcpChannel : ConnectionChannel +{ + public TcpChannel(String hostname, + Int32 port, + Boolean closeAfterSuccessfulRead = false, + Boolean closeAfterSuccessfulWrite = false) : base(closeAfterSuccessfulRead, closeAfterSuccessfulWrite) + { + TcpClient Open() => new(hostname, port); + _Open = Open; + } + + private readonly Func _Open; + + protected override TcpClient Open() => _Open(); + protected override void Close(TcpClient tcpClient) => tcpClient.Close(); + + protected override IReadOnlyList Read(TcpClient tcpClient, Int32 nToRead) + { + var buffer = new Byte[nToRead]; + + var stream = tcpClient.GetStream(); + var nReceived = 0; + + do + { + var nRemaining = nToRead - nReceived; + var read = stream.Read(buffer, nReceived, nRemaining); + if (read <= 0) + throw new NotConnectedException("The TCP Connection was closed"); + + nReceived += read; + } + while (nReceived < nToRead); + + return buffer; + } + + protected override void Write(TcpClient tcpClient, IReadOnlyList data) + { + var array = data.ToArray(); + tcpClient.GetStream().Write(array, 0, array.Length); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs index 0a4ca6948..2aba23c4d 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs @@ -1,54 +1,38 @@ -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; -using InnovEnergy.Lib.Protocols.Modbus.Protocol; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; -using Coils = IReadOnlyList; +using Booleans = IReadOnlyCollection; +using UInt16s = IReadOnlyCollection; // TODO: ModbusClient = Framer(TCP/RTU) + Connection(Serial/TCP) + Encoder(binary/ascii) + quirk(+1/0) // Transport public abstract class ModbusClient { - protected ModbusConnection Connection { get; } - protected Byte SlaveId { get; } - + internal Channel Channel { get; } + internal Byte SlaveId { get; } + internal Endian Endian { get; } // TODO: add additional functions: coils... - - public abstract Coils ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues); - public abstract ModbusRegisters ReadInputRegisters (UInt16 readAddress, UInt16 nValues); - public abstract ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues); - public abstract UInt16 WriteMultipleCoils (UInt16 writeAddress, Coils coils); - public abstract UInt16 WriteRegisters (UInt16 writeAddress, IReadOnlyList values); - public abstract ModbusRegisters ReadWriteRegisters (UInt16 readAddress, UInt16 nbToRead, - UInt16 writeAddress, IReadOnlyList registersToWrite); - - public UInt16 WriteMultipleCoils(UInt16 writeAddress, params Boolean[] coils) - { - return WriteMultipleCoils(writeAddress, (IReadOnlyList)coils); - } - - public UInt16 WriteRegisters(ModbusRegisters registers) - { - return WriteRegisters(registers.StartRegister, registers); - } - public UInt16 WriteRegisters(UInt16 writeAddress, params UInt16[] values) - { - return WriteRegisters(writeAddress, (IReadOnlyList)values); - } - - protected ModbusClient(ModbusConnection connection, - Byte slaveId) - { - Connection = connection; - SlaveId = slaveId; - } + public abstract MbData ReadCoils (UInt16 readAddress, UInt16 nValues); + public abstract MbData ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues); + public abstract MbData ReadInputRegisters (UInt16 readAddress, UInt16 nValues); + public abstract MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues); + public abstract UInt16 WriteCoils (UInt16 writeAddress, Booleans coils); + public abstract UInt16 WriteRegisters (UInt16 writeAddress, UInt16s values); - public void CloseConnection() => Connection.Close(); + public abstract MbData ReadWriteRegisters (UInt16 readAddress, + UInt16 nbToRead, + UInt16 writeAddress, + UInt16s registersToWrite); - -} - + protected ModbusClient(Channel channel, Byte slaveId, Endian endian = Endian.Little) + { + Channel = channel; + SlaveId = slaveId; + Endian = endian; + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs index 24171c847..b98107af1 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs @@ -1,9 +1,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; +using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using InnovEnergy.Lib.Protocols.Modbus.Util; @@ -13,68 +13,69 @@ using static System.Runtime.CompilerServices.MethodImplOptions; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; -using UInt16s = IReadOnlyList; +using UInt16s = IReadOnlyCollection; +using Booleans = IReadOnlyCollection; public class ModbusRtuClient : ModbusClient { private const Int32 CrcSize = 2; - public ModbusRtuClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId) + public ModbusRtuClient(Channel channel, Byte slaveId) : base(channel, slaveId) { } - public override IReadOnlyList ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) + public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues) { throw new NotImplementedException(); } - public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) + public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) + { + throw new NotImplementedException(); + } + + public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd); // TX - cmd.Data.Concat(crc).Apply(Connection.Transmit); - - var nToRead = cmd.ExpectedResponseSize + CrcSize; + cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX - var response = nToRead - .ConvertTo() - .Apply(Connection.Receive) + var response = Channel + .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) .Apply(ReadInputRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return new MbData(response.RegistersRead.RawData, readAddress, Endian); } - public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) + public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); var crc = CalcCrc(cmd.Data); // TX - cmd.Data.Concat(crc).Apply(Connection.Transmit); + cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX - var nToRead = cmd.ExpectedResponseSize + CrcSize; - var response = nToRead - .ConvertTo() - .Apply(Connection.Receive) + var response = Channel + .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) .Apply(ReadHoldingRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return new MbData(response.RegistersRead.RawData, readAddress, Endian); } - public override UInt16 WriteMultipleCoils(UInt16 writeAddress, IReadOnlyList coils) + public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils) { throw new NotImplementedException(); } @@ -84,16 +85,13 @@ public class ModbusRtuClient : ModbusClient { var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values); var crc = CalcCrc(cmd); - var nToRead = cmd.ExpectedResponseSize + CrcSize; // TX - cmd.Data.Concat(crc).Apply(Connection.Transmit); - + cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX - var response = nToRead - .ConvertTo() - .Apply(Connection.Receive) + var response = Channel + .Read(cmd.ExpectedResponseSize + CrcSize) .ToArraySegment() .Apply(AssertCrc) .SkipLast(CrcSize) @@ -103,7 +101,7 @@ public class ModbusRtuClient : ModbusClient return response.NbWritten; } - public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) + public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, @@ -113,26 +111,25 @@ public class ModbusRtuClient : ModbusClient var crc = CalcCrc(cmd); // TX - cmd.Data.Concat(crc).Apply(Connection.Transmit); + cmd.Data.Concat(crc).ToList().Apply(Channel.Write); // RX var nToRead = cmd.ExpectedResponseSize + CrcSize; var response = nToRead - .ConvertTo() - .Apply(Connection.Receive) + .Apply(Channel.Read) .ToArraySegment() .SkipLast(CrcSize) .Apply(ReadWriteRegistersResponseFrame.Parse) .Apply(cmd.VerifyResponse); - return new ModbusRegisters(readAddress, response.RegistersRead.ToArray()); + return new MbData(response.RegistersRead.RawData, readAddress, Endian); } public static ArraySegment AssertCrc(ArraySegment data) { var expectedCrc = data.SkipLast(CrcSize).Apply(CalcCrc); var actualCrc = data.TakeLast(CrcSize); - + if (!actualCrc.SequenceEqual(expectedCrc)) throw new CrcException(expectedCrc, actualCrc); diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs index 795cb9189..ff206717f 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs @@ -1,15 +1,16 @@ using System.Diagnostics; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Protocols.Modbus.Conversions; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using InnovEnergy.Lib.Protocols.Modbus.Tcp; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; -using UInt16s = IReadOnlyList; -using Coils = IReadOnlyList; +using UInt16s = IReadOnlyCollection; +using Booleans = IReadOnlyCollection; public class ModbusTcpClient : ModbusClient { @@ -20,12 +21,34 @@ public class ModbusTcpClient : ModbusClient private UInt16 NextId() => unchecked(++_Id); - public ModbusTcpClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId) + public ModbusTcpClient(Channel channel, Byte slaveId) : base(channel, slaveId) { } - - public override IReadOnlyList ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) + public override MbData ReadCoils(UInt16 readAddress, UInt16 nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadCoilsCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() // TODO: optimize .ToArray() ? + .Apply(ReadCoilsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.Coils.RawData, readAddress, Endian); + } + + + public override MbData ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id @@ -33,18 +56,21 @@ public class ModbusTcpClient : ModbusClient var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ? + var hData = Channel.Read(MbapHeader.Size).ToArray(); // TODO: optimize .ToArray() ? var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); // TODO: optimize .ToArray() ? - var rxFrm = ReadDiscreteInputResponseFrame.Parse(fData); - - return cmd.VerifyResponse(rxFrm).Inputs; + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() // TODO: optimize .ToArray() ? + .Apply(ReadDiscreteInputsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.Inputs.RawData, readAddress, Endian); } - public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) + public override MbData ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id @@ -52,61 +78,65 @@ public class ModbusTcpClient : ModbusClient var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Channel.Read(MbapHeader.Size).ToArray(); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); - var rxFrm = ReadInputRegistersResponseFrame.Parse(fData); + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadInputRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); - var verified = cmd.VerifyResponse(rxFrm); - - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); } - - - public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) + public override MbData ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { var id = NextId(); // TODO: check response id + var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Channel.Read(MbapHeader.Size).ToArray(); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); - var rxFrm = ReadHoldingRegistersResponseFrame.Parse(fData); + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadHoldingRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); - var verified = cmd.VerifyResponse(rxFrm); - - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); } - public override UInt16 WriteMultipleCoils(UInt16 writeAddress, Coils coils) + public override UInt16 WriteCoils(UInt16 writeAddress, Booleans coils) { - var id = NextId(); // TODO: check response id + var id = NextId(); // TODO: check response id var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Channel.Read(MbapHeader.Size).ToArray(); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); - var rxFrm = WriteCoilsResponseFrame.Parse(fData); + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(WriteCoilsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); - return cmd.VerifyResponse(rxFrm).NbWritten; + return rxFrm.NbWritten; } - - + + public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values) { var id = NextId(); // TODO: check response id @@ -114,23 +144,22 @@ public class ModbusTcpClient : ModbusClient var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Channel.Read(MbapHeader.Size).ToArray(); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); - var rxFrm = WriteRegistersResponseFrame.Parse(fData); + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(WriteRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); - var verified = cmd.VerifyResponse(rxFrm); - - return verified.NbWritten; + return rxFrm.NbWritten; } - public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) + public override MbData ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { - - var id = NextId(); // TODO: check response id var cmd = new ReadWriteRegistersCommandFrame(SlaveId, @@ -142,17 +171,17 @@ public class ModbusTcpClient : ModbusClient var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); - Connection.Transmit(frm.Data); + Channel.Write(frm.Data); - var hData = Connection.Receive(MbapHeader.Size).ToArray(); + var hData = Enumerable.ToArray(Channel.Read(MbapHeader.Size)); var rxHdr = new MbapHeader(hData); - var fData = Connection.Receive(rxHdr.FrameLength).ToArray(); - var rxFrm = ReadWriteRegistersResponseFrame.Parse(fData); + var fData = Enumerable.ToArray(Channel.Read(rxHdr.FrameLength)); + var rxFrm = fData + .Apply(ReadWriteRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); - var verified = cmd.VerifyResponse(rxFrm); - - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Connections/ModbusConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusConnection.cs deleted file mode 100644 index 30ad8d5a9..000000000 --- a/csharp/Lib/Protocols/Modbus/Connections/ModbusConnection.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace InnovEnergy.Lib.Protocols.Modbus.Connections; - - - -public abstract class ModbusConnection -{ - public abstract IReadOnlyList Receive(UInt16 nBytes); - public abstract void Transmit(IEnumerable bytes); - - public abstract void Open(); // calls to opening an already open connection must be ignored - public abstract void Close(); // calls to closing an already closed connection must be ignored, must not throw -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs deleted file mode 100644 index 434bb467f..000000000 --- a/csharp/Lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.IO.Ports; -using InnovEnergy.Lib.Protocols.Modbus.Protocol; - -namespace InnovEnergy.Lib.Protocols.Modbus.Connections; - -public class ModbusSerialConnection : ModbusConnection -{ - private String PortName { get; } - private Int32 BaudRate { get; } - private Parity Parity { get; } - private Int32 DataBits { get; } - private StopBits StopBits { get; } - private Int32 TimeoutMs { get; } - - private Byte[] Buffer { get; } = new Byte[1024]; - private SerialPort? _SerialPort; - - public ModbusSerialConnection(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, TimeSpan timeout) - { - PortName = portName; - BaudRate = baudRate; - Parity = parity; - DataBits = dataBits; - StopBits = stopBits; - TimeoutMs = (Int32) timeout.TotalMilliseconds; - } - - - public override IReadOnlyList Receive(UInt16 bytesToRead) - { - var bytesReceived = 0; - var serialPort = SerialPort(); - - do - { - var received = serialPort.Read(Buffer, bytesReceived, Buffer.Length - bytesReceived); - if (received < 0) - throw new NotConnectedException("Serial Connection has been closed"); - - bytesReceived += received; - } - while (bytesReceived < bytesToRead); - - return new ArraySegment(Buffer, 0, bytesToRead); - } - - public override void Transmit(IEnumerable data) - { - var array = data.ToArray(); - SerialPort().Write(array, 0, array.Length); - } - - public override void Open() => SerialPort(); - - private SerialPort SerialPort() - { - if (_SerialPort == null) - { - _SerialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits) { ReadTimeout = TimeoutMs, WriteTimeout = TimeoutMs}; - _SerialPort.Open(); - } - - return _SerialPort; - } - - public override void Close() - { - if (_SerialPort != null) - { - try - { - _SerialPort.Close(); - _SerialPort?.Dispose(); - } - catch (Exception) - { - // ignored - } - } - - _SerialPort = null; - } - -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs deleted file mode 100644 index 7c75a7354..000000000 --- a/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Net.Sockets; -using InnovEnergy.Lib.Protocols.Modbus.Protocol; - -namespace InnovEnergy.Lib.Protocols.Modbus.Connections; - -public class ModbusTcpConnection : ModbusConnection -{ - private String Hostname { get; } - private Int32 Port { get; } - - private Byte[] Buffer { get; } = new Byte[1024]; - private TcpClient? _TcpClient; - - public ModbusTcpConnection(String hostname, Int32 port = 502) - { - Hostname = hostname; - Port = port; - } - - public override IReadOnlyList Receive(UInt16 bytesToRead) - { - var bytesReceived = 0; - var stream = TcpClient().GetStream(); - - do - { - var maxBytes = Math.Min(bytesToRead, Buffer.Length) - bytesReceived; - var read = stream.Read(Buffer, bytesReceived, maxBytes); - if (read <= 0) - throw new NotConnectedException("The TCP Connection was closed"); - - bytesReceived += read; - } - while (bytesReceived < bytesToRead); - - return new ArraySegment(Buffer, 0, bytesToRead); - } - - public override void Transmit(IEnumerable data) - { - var array = data.ToArray(); - TcpClient().GetStream().Write(array, 0, array.Length); - } - - public override void Open() => TcpClient(); - - private TcpClient TcpClient() - { - return _TcpClient ??= new TcpClient(Hostname, Port); - } - - public override void Close() - { - try - { - _TcpClient?.Dispose(); - } - catch (Exception) - { - // ignored - } - - _TcpClient = null; - } - -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Doc/Modbus_Application_Protocol_V1_1b.pdf b/csharp/Lib/Protocols/Modbus/Doc/Modbus_Application_Protocol_V1_1b.pdf new file mode 100644 index 000000000..7c458fa6c Binary files /dev/null and b/csharp/Lib/Protocols/Modbus/Doc/Modbus_Application_Protocol_V1_1b.pdf differ diff --git a/csharp/Lib/Protocols/Modbus/Modbus.csproj b/csharp/Lib/Protocols/Modbus/Modbus.csproj index 70b28e699..95b2d054a 100644 --- a/csharp/Lib/Protocols/Modbus/Modbus.csproj +++ b/csharp/Lib/Protocols/Modbus/Modbus.csproj @@ -1,18 +1,18 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - + + + + - - - + + - - - + + + diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs index 2a2e9e2c5..6c27b7f72 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs @@ -6,5 +6,5 @@ public static class Accessors public static MbWord WordAt (this ArraySegment data, Byte i) => new MbWord(data, i); public static MbWords WordsAt (this ArraySegment data, Byte i) => new MbWords(data, i); public static MbBits BitsAt (this ArraySegment data, Byte i) => new MbBits(data, i); - public static MbByte ByteAt(this ArraySegment data,Byte i) where T : struct, IConvertible => new MbByte(data, i); + public static MbByte ByteAt(this ArraySegment data, Byte i) where T : struct, IConvertible => new MbByte(data, i); } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs new file mode 100644 index 000000000..7c6597d42 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Endian.cs @@ -0,0 +1,20 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +public enum Endian +{ + Undefined = 0, + Little = 1, + Big = 2, + Default = 1, +} + + +public static class EndianCombinator +{ + public static Endian InheritFrom(this Endian child, Endian parent) + { + return child == Endian.Undefined + ? parent + : child; + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs index 137c377f3..ac990f5d8 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs @@ -3,11 +3,15 @@ using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; -public readonly struct MbBits : IReadOnlyList +public readonly struct MbBits : IReadOnlyList, IReadOnlyList { - private readonly ArraySegment _Data; + public readonly ArraySegment RawData; - internal MbBits(ArraySegment data, Byte startIndex) : this(data, startIndex, CountBits(data, startIndex)) + internal MbBits(Int32 bitCount) : this(new Byte[NbRegistersFromNbBits(bitCount)]) + { + } + + internal MbBits(ArraySegment data, Byte startIndex = 0) : this(data, startIndex, CountBits(data, startIndex)) { } @@ -24,23 +28,32 @@ public readonly struct MbBits : IReadOnlyList internal MbBits(Byte[] data, Byte startIndex, UInt16 bitCount) { - _Data = new ArraySegment(data, startIndex, Math.Ceiling(bitCount / 8.0).ConvertTo()); + RawData = new ArraySegment(data, startIndex, NbRegistersFromNbBits(bitCount)); } - internal void Set(IReadOnlyList values) + private static UInt16 NbRegistersFromNbBits(Int32 bitCount) { - for (var i = 0; i < values.Count; i++) - { - SetBit((UInt16)i, values[i]); - } + return Math.Ceiling(bitCount / 8.0).ConvertTo(); } + internal void Set(IReadOnlyCollection values) + { + var i = 0; + foreach (var value in values) + SetBit((UInt16)i++, value); + } - public IEnumerator GetEnumerator() => Enumerable.Range(0, Count).Select(GetBit).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetBits() + .OfType() + .GetEnumerator(); + + public IEnumerator GetEnumerator() => GetBits().GetEnumerator(); + + private IEnumerable GetBits() => Enumerable.Range(0, Count).Select(GetBit); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public Int32 Count => _Data.Count * 8; + public Int32 Count => RawData.Count * 8; private Boolean GetBit(Int32 index) @@ -48,10 +61,10 @@ public readonly struct MbBits : IReadOnlyList var byteIndex = index / 8; var bitIndex = index % 8; - return (_Data[byteIndex] & (1 << bitIndex)) != 0; + return (RawData[byteIndex] & (1 << bitIndex)) != 0; } - private void SetBit(UInt16 index, Boolean value) + internal void SetBit(UInt16 index, Boolean value) { var byteIndex = index / 8; var bitIndex = index % 8; @@ -59,12 +72,13 @@ public readonly struct MbBits : IReadOnlyList var mask = 1 << bitIndex; // !! needs the u suffix! if (value) - _Data[byteIndex] |= (Byte)mask; + RawData[byteIndex] |= (Byte)mask; else - _Data[byteIndex] &= (Byte)~mask; + RawData[byteIndex] &= (Byte)~mask; } public Boolean this[Int32 index] => GetBit(index); + IConvertible IReadOnlyList.this[Int32 index] => this[index]; } diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs new file mode 100644 index 000000000..4273882b7 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbData.cs @@ -0,0 +1,245 @@ +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +using Float32 = Single; +using Float64 = Double; + +// switch exhaustion +#pragma warning disable CS8524 +#pragma warning disable CS8509 + +public struct MbData +{ + + //TODO: use System.Buffers.Binary.BinaryPrimitives + // see Decimal class + + private readonly ArraySegment _Data; + private readonly Endian _Endian; + private readonly UInt16 _StartAddress; + + public static MbData Registers(UInt16 startAddress, UInt16 nRegisters, Endian endian = Endian.Default) + { + if (nRegisters > Constants.MaxRegs) + throw new ArgumentException(nameof(nRegisters)); + + var nBytes = nRegisters * 2; + var data = new Byte[nBytes]; + return new MbData(data, startAddress, endian); + } + + public static MbData Coils(UInt16 startAddress, UInt16 nCoils, Endian endian = Endian.Default) + { + if (nCoils > Constants.MaxCoils) + throw new ArgumentException(nameof(nCoils)); + + var nBytes = Math.Ceiling(nCoils / 8.0).ConvertTo(); + var data = new Byte[nBytes]; + return new MbData(data, startAddress, endian); // endian has no influence on coils + } + + internal MbData(ArraySegment data, UInt16 startAddress, Endian endian) + { + if (endian != Endian.Big && endian != Endian.Little) + throw new ArgumentOutOfRangeException(nameof(endian), endian, null); + + _Endian = endian; + _StartAddress = startAddress; + _Data = data; + } + + public MbData WithEndian(Endian endian) + { + return new MbData(_Data, _StartAddress, endian.InheritFrom(_Endian)); + } + + #region Coils + + public IReadOnlyList GetCoils() + { + IEnumerable GetBits(Byte b) => 1.Unfold(m => m << 1) + .Take(8) + .Select(m => (b & m) > 0); + + return _Data.SelectMany(GetBits).ToList(); + } + + public Boolean GetInput(UInt16 address) => GetCoil(address); + + public Boolean GetCoil(UInt16 address) + { + var index = address - _StartAddress; + + var byteIndex = index / 8; + var bitIndex = index % 8; + + return (_Data[byteIndex] & (1 << bitIndex)) != 0; + } + + public void SetCoil(UInt16 address, Boolean value) + { + var index = address - _StartAddress; + + var byteIndex = index / 8; + var bitIndex = index % 8; + + var mask = 1 << bitIndex; + + if (value) + _Data[byteIndex] |= (Byte)mask; + else + _Data[byteIndex] &= (Byte)~mask; + } + + #endregion Coils + + #region 16Bit + + public UInt16 GetUInt16(UInt16 address) + { + return GetRegister((address - _StartAddress) * 2); + } + + public Int16 GetInt16(UInt16 address) + { + return (Int16) GetUInt16(address); + } + + public UInt16 SetUInt16(UInt16 address, UInt16 value) + { + var i = (address - _StartAddress) * 2; + + _Data[i ] = (Byte)(value >> 8); + _Data[i + 1] = (Byte)(value & 0xFF); + + return value; + } + + public void SetInt16(UInt16 address, Int16 value) + { + SetUInt16(address, (UInt16)value); + } + + #endregion 16Bit + + #region 32Bit + + public UInt32 GetUInt32(UInt16 address) + { + var hi = (UInt32) GetUInt16(address); + var lo = (UInt32) GetUInt16(++address); + + + return _Endian switch + { + Endian.Big => hi << 16 | lo, + Endian.Little => lo << 16 | hi, + }; + } + + public Int32 GetInt32(UInt16 address) => (Int32)GetUInt32(address); + + public void SetUInt32(UInt16 address, UInt32 value) + { + var hi = (UInt16)(value >> 16); + var lo = (UInt16)(value & 0xFF_FF); + + if (_Endian == Endian.Big) + { + SetUInt16(address, hi); + SetUInt16(++address, lo); + } + else + { + SetUInt16(address, lo); + SetUInt16(++address, hi); + } + } + + public void SetInt32(UInt16 address, Int64 value) => SetUInt32(address, (UInt32)value); + + public Float32 GetFloat32(UInt16 address) + { + return address + .Apply(GetUInt32) + .Apply(BitConverter.UInt32BitsToSingle); + } + + public void SetFloat32(UInt16 address, Float32 value) + { + SetUInt32(address, BitConverter.SingleToUInt32Bits(value)); + } + + #endregion 32Bit + + #region 64Bit + + public UInt64 GetUInt64(UInt16 address) + { + var hi = (UInt64) GetUInt32(address); + var lo = (UInt64) GetUInt32(++address); + + return _Endian switch + { + Endian.Big => hi << 32 | lo, + Endian.Little => lo << 32 | hi, + }; + } + + public Int64 GetInt64(UInt16 address) => (Int32)GetUInt64(address); + + public void SetUInt64(UInt16 address, UInt64 value) + { + var hi = (UInt32)(value >> 32); + var lo = (UInt32)(value & 0xFF_FF_FF_FF); + + if (_Endian == Endian.Big) + { + SetUInt32(address, hi); + SetUInt32((UInt16)(address + 2), lo); + } + else + { + SetUInt32(address, lo); + SetUInt32((UInt16)(address + 2), hi); + } + } + + public void SetInt64(UInt16 address, Int64 value) => SetUInt64(address, (UInt64)value); + + public Float64 GetFloat64(UInt16 address) + { + return address + .Apply(GetUInt64) + .Apply(BitConverter.UInt64BitsToDouble); + } + + public void SetFloat64(UInt16 address, Float64 value) + { + SetUInt64(address, BitConverter.DoubleToUInt64Bits(value)); + } + + #endregion 64Bit + + + private UInt16 GetRegister(Int32 i) + { + var hi = _Data[i] << 8; + var lo = _Data[i + 1]; + + return (UInt16)(hi | lo); + } + + public IReadOnlyList GetRegisters() + { + var nRegisters = _Data.Count / 2; + + return Enumerable + .Range(0, nRegisters) + .Select(r => r * 2) + .Select(GetRegister) + .ToArray(nRegisters); + } + +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs deleted file mode 100644 index 6ff463460..000000000 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Collections; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; - -using Float32 = Single; - -public struct MbRegisters : IReadOnlyList -{ - private MbWords Words { get; } - private UInt16 StartRegister { get; } - - public MbRegisters(MbWords words, UInt16 startRegister) - { - Words = words; - StartRegister = startRegister; - } - - public IEnumerator GetEnumerator() => Words.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public Int32 Count => Words.Count; - - public UInt16 this[Int32 index] => Words[index]; - - private Byte MapIndex(UInt16 index) - { - var i = index - StartRegister; - if (i is < Byte.MinValue or > Byte.MaxValue) - throw new IndexOutOfRangeException(); - - return (Byte)i; - } - - public UInt16 GetUInt16(UInt16 index) - { - return index - .Apply(MapIndex) - .Apply(Words.GetUInt16); - } - - public void SetUInt16(UInt16 index, UInt16 value) - { - Words.SetUInt16(MapIndex(index), value); - } - - public Int16 GetInt16(UInt16 index) - { - return (Int16) GetUInt16(index); - } - - public void SetUInt16(UInt16 index, Int16 value) - { - SetUInt16(index, (UInt16)value); - } - - public UInt32 GetUInt32(UInt16 index) - { - var i = MapIndex(index); - - var hi = (UInt32) GetUInt16(i); - var lo = (UInt32) GetUInt16(++i); - - return hi << 16 | lo; - } - - // TODO - // public void SetUInt32(UInt32 index, UInt32 value) - - public Int32 GetInt32(UInt16 index) => (Int32)GetUInt32(index); - - // TODO - // public void SetInt32(Int32 index, Int32 value) - - - public Float32 GetFloat32(UInt16 index) - { - return index - .Apply(GetInt32) - .Apply(BitConverter.Int32BitsToSingle); - } - - // TODO - // public void SetFloat32(Float32 index, Float32 value) - - - - -} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs index 5511c5c63..5ff9232f9 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs @@ -3,9 +3,9 @@ using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; -public readonly struct MbWords : IReadOnlyList +public readonly struct MbWords : IReadOnlyList , IReadOnlyList { - internal readonly ArraySegment Data; + public readonly ArraySegment RawData; internal MbWords(ArraySegment data, Byte startIndex = 0) : this(data, startIndex, CountWords(data, startIndex)) @@ -13,7 +13,7 @@ public readonly struct MbWords : IReadOnlyList internal MbWords(ArraySegment data, Byte startIndex, UInt16 wordCount) { - Data = new ArraySegment(data.Array!, startIndex + data.Offset, wordCount * 2); + RawData = new ArraySegment(data.Array!, startIndex + data.Offset, wordCount * 2); } private static UInt16 CountWords(ArraySegment data, Byte startIndex) @@ -24,28 +24,40 @@ public readonly struct MbWords : IReadOnlyList internal IReadOnlyCollection Set(IReadOnlyCollection values) { - if (values.Count != Data.Count / 2) + if (values.Count != RawData.Count / 2) throw new ArgumentException($"Expecting an list of size {values.Count}!", nameof(values)); var i = 0; foreach (var value in values) { - Data[i++] = (Byte) (value >> 8); - Data[i++] = (Byte) (value & 0xFF); + RawData[i++] = (Byte) (value >> 8); + RawData[i++] = (Byte) (value & 0xFF); } return values; } - - public IEnumerator GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { - var end = Data.Count; + var end = RawData.Count; for (var i = 0; i < end; ) { - var hi = Data[i++] << 8; - var lo = Data[i++]; + var hi = RawData[i++] << 8; + var lo = RawData[i++]; + + yield return (UInt16) (hi | lo); + } + } + + public IEnumerator GetEnumerator() + { + var end = RawData.Count; + + for (var i = 0; i < end; ) + { + var hi = RawData[i++] << 8; + var lo = RawData[i++]; yield return (UInt16) (hi | lo); } @@ -53,7 +65,7 @@ public readonly struct MbWords : IReadOnlyList IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public Int32 Count => Data.Count / 2; + public Int32 Count => RawData.Count / 2; public UInt16 this[Int32 index] { @@ -63,8 +75,8 @@ public readonly struct MbWords : IReadOnlyList { var i = index * 2; - var hi = Data[i] << 8; - var lo = Data[i+1]; + var hi = RawData[i] << 8; + var lo = RawData[i+1]; return (UInt16) (hi | lo); } @@ -76,7 +88,9 @@ public readonly struct MbWords : IReadOnlyList public void SetUInt16(Byte index, UInt16 value) { var i = index * 2; - Data[i + 0] = (Byte)(value >> 8); - Data[i + 1] = (Byte)(value & 0xFF); + RawData[i + 0] = (Byte)(value >> 8); + RawData[i + 1] = (Byte)(value & 0xFF); } + + IConvertible IReadOnlyList.this[Int32 index] => this[index]; } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs new file mode 100644 index 000000000..e5034f8de --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadCoilsCommandFrame.cs @@ -0,0 +1,51 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; +using InnovEnergy.Lib.Utils; +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; + +internal class ReadCoilsCommandFrame : ModbusFrame +{ + private const Int32 Size = 6; + + public MbWord ReadAddress => Data.WordAt(2); + public MbWord NumberOfCoils => Data.WordAt(4); + + + public ReadCoilsCommandFrame(Byte slave, UInt16 readAddress, UInt16 nBits) : base(Size) + { + if (nBits > Constants.MaxCoils) + throw new ArgumentOutOfRangeException($"Maximum number of registers ({Constants.MaxCoils}) exceeeded!", nameof(nBits)); + + SlaveAddress .Set(slave); + FunctionCode .Set(ReadCoils); + ReadAddress .Set(readAddress); + NumberOfCoils.Set(nBits); + } + + + private ReadCoilsCommandFrame(ArraySegment data) : base(data) + { + if (data.Count != Size) + throw new ArgumentException($"Expecting an array of size {Size}", nameof(data)); + + AssertFunctionCode(ReadCoils); + } + + public ReadCoilsResponseFrame VerifyResponse(ReadCoilsResponseFrame response) + { + if (response.SlaveAddress != SlaveAddress) + throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); + + if (response.Coils.Count != Math.Ceiling(NumberOfCoils / 8.0).ConvertTo() * 8) + throw new UnexpectedResponseFieldException(nameof(response.Coils), NumberOfCoils.ToString(), response.Coils.Count); + + return response; + } + + public static ReadCoilsCommandFrame Parse(ArraySegment data) + { + return new ReadCoilsCommandFrame(data); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs index 325d88e5c..6133ad74f 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs @@ -37,7 +37,7 @@ internal class ReadDiscreteInputsCommandFrame : ModbusFrame AssertFunctionCode(ReadDiscreteInputs); } - public ReadDiscreteInputResponseFrame VerifyResponse(ReadDiscreteInputResponseFrame response) + public ReadDiscreteInputsResponseFrame VerifyResponse(ReadDiscreteInputsResponseFrame response) { if (response.SlaveAddress != SlaveAddress) throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs index 9134b0be9..65b072dc2 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs @@ -40,12 +40,10 @@ internal class ReadHoldingRegistersCommandFrame : ModbusFrame } - public ReadHoldingRegistersResponseFrame VerifyResponse(ModbusFrame response) + public ReadHoldingRegistersResponseFrame VerifyResponse(ReadHoldingRegistersResponseFrame r) { - if (response.SlaveAddress != SlaveAddress) - throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); - - var r = ReadHoldingRegistersResponseFrame.Parse(response.Data); + if (r.SlaveAddress != SlaveAddress) + throw new UnexpectedResponseFieldException(nameof(r.SlaveAddress), SlaveAddress.ToString(), r.SlaveAddress); if (r.RegistersRead.Count != NbToRead) throw new UnexpectedResponseFieldException(nameof(r.RegistersRead), NbToRead.ToString(), r.RegistersRead.Count); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs index a17fe34f8..c873c1d9d 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs @@ -40,7 +40,6 @@ internal class ReadInputRegistersCommandFrame : ModbusFrame if (response.SlaveAddress != SlaveAddress) throw new UnexpectedResponseFieldException(nameof(response.SlaveAddress), SlaveAddress.ToString(), response.SlaveAddress); - if (response.RegistersRead.Count != NbToRead) throw new UnexpectedResponseFieldException(nameof(response.RegistersRead), NbToRead.ToString(), response.RegistersRead.Count); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs index e280c071d..c11d73563 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs @@ -5,7 +5,7 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; -using UInt16s = IReadOnlyList; +using UInt16s = IReadOnlyCollection; internal class ReadWriteRegistersCommandFrame : ModbusFrame { @@ -21,10 +21,10 @@ internal class ReadWriteRegistersCommandFrame : ModbusFrame public Int32 ExpectedResponseSize => ReadWriteRegistersResponseFrame.ExpectedSize(NbToRead); public ReadWriteRegistersCommandFrame(Byte slave, - UInt16 readAddress, - UInt16 nbToRead, - UInt16 writeAddress, - UInt16s registersToWrite) : base(MinSize + registersToWrite.Count * 2) + UInt16 readAddress, + UInt16 nbToRead, + UInt16 writeAddress, + UInt16s registersToWrite) : base(MinSize + registersToWrite.Count * 2) { if (nbToRead > MaxRegs) throw new ArgumentOutOfRangeException($"Maximum number of registers ({MaxRegs}) exceeeded!", nameof(nbToRead)); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs index e366616cb..1873fd34b 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs @@ -7,7 +7,7 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Constants; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; -using Booleans = IReadOnlyList; +using Booleans = IReadOnlyCollection; using MbFc = MbByte; public class WriteCoilsCommandFrame : ModbusFrame @@ -17,7 +17,7 @@ public class WriteCoilsCommandFrame : ModbusFrame private MbWord WriteAddress => Data.WordAt(2); private MbWord NbOfCoils => Data.WordAt(4); private MbByte ByteCount => Data.ByteAt(6); - public MbBits CoilsToWrite => Data.BitsAt(7); + private MbBits CoilsToWrite => Data.BitsAt(7); public WriteCoilsCommandFrame(Byte slave, UInt16 writeAddress, Booleans coils) : base(MinSize + NbBytes(coils)) { diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs index b317767c1..e934392c1 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs @@ -4,8 +4,8 @@ using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; -using UInt16s = IReadOnlyList; -using MbFc = MbByte; +using UInt16s = IReadOnlyCollection; +using MbFc = MbByte; internal class WriteRegistersCommandFrame : ModbusFrame { diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs new file mode 100644 index 000000000..8dd78b859 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadCoilsResponseFrame.cs @@ -0,0 +1,45 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; + +public class ReadCoilsResponseFrame : ModbusFrame +{ + private new const Int32 MinSize = 3; + + private MbByte ByteCount => Data.ByteAt(2); + internal MbBits Coils => Data.BitsAt(3); + + public ReadCoilsResponseFrame(Byte slave, IReadOnlyList inputs) : base (inputs.Count) + { + var nBytes = Math.Ceiling(inputs.Count / 8.0).ConvertTo(); + + SlaveAddress .Set(slave); + FunctionCode .Set(Protocol.FunctionCode.ReadCoils); + ByteCount .Set(nBytes); + Coils .Set(inputs); + } + + private ReadCoilsResponseFrame(Byte[] data) : this(new ArraySegment(data)) + { } + + + private ReadCoilsResponseFrame(ArraySegment data) : base(data) + { + + AssertFunctionCode(Protocol.FunctionCode.ReadCoils); + + // TODO + // var expectedSize = ByteCount + MinSize; + // if (data.Count != expectedSize) + // throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data)); + // if (data.Count < MinSize) + // throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data)); + + } + + + public static ReadCoilsResponseFrame Parse(Byte[] data) => new(data); + + public static ReadCoilsResponseFrame Parse(ArraySegment data) => new(data); +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs similarity index 66% rename from csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs index 438523786..7b92fd651 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputsResponseFrame.cs @@ -7,14 +7,14 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using Booleans = IReadOnlyList; using MbFc = MbByte; -public class ReadDiscreteInputResponseFrame : ModbusFrame +public class ReadDiscreteInputsResponseFrame : ModbusFrame { private new const Int32 MinSize = 3; private MbByte ByteCount => Data.ByteAt(2); public MbBits Inputs => Data.BitsAt(3); - public ReadDiscreteInputResponseFrame(Byte slave, Booleans inputs) : base (inputs.Count) + public ReadDiscreteInputsResponseFrame(Byte slave, Booleans inputs) : base (inputs.Count) { var nBytes = Math.Ceiling(inputs.Count / 8.0).ConvertTo(); @@ -24,11 +24,11 @@ public class ReadDiscreteInputResponseFrame : ModbusFrame Inputs .Set(inputs); } - private ReadDiscreteInputResponseFrame(Byte[] data) : this(new ArraySegment(data)) + private ReadDiscreteInputsResponseFrame(Byte[] data) : this(new ArraySegment(data)) { } - private ReadDiscreteInputResponseFrame(ArraySegment data) : base(data) + private ReadDiscreteInputsResponseFrame(ArraySegment data) : base(data) { AssertFunctionCode(ReadDiscreteInputs); @@ -43,13 +43,13 @@ public class ReadDiscreteInputResponseFrame : ModbusFrame } - public static ReadDiscreteInputResponseFrame Parse(ModbusFrame rawFrame) + public static ReadDiscreteInputsResponseFrame Parse(Byte[] data) { - return new ReadDiscreteInputResponseFrame(rawFrame.Data); + return new ReadDiscreteInputsResponseFrame(data); } - - public static ReadDiscreteInputResponseFrame Parse(ArraySegment data) + + public static ReadDiscreteInputsResponseFrame Parse(ArraySegment data) { - return new ReadDiscreteInputResponseFrame(data); + return new ReadDiscreteInputsResponseFrame(data); } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs index 7fcd77983..5edbaef45 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs @@ -30,11 +30,11 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame private ReadHoldingRegistersResponseFrame(ArraySegment data) : base(data) { + AssertFunctionCode(ReadHoldingRegisters); + if (data.Count < MinSize) throw new ArgumentOutOfRangeException($"Expecting an array of size {MinSize} or more", nameof(data)); - AssertFunctionCode(ReadHoldingRegisters); - var expectedSize = ByteCount + MinSize; if (data.Count != expectedSize) throw new ArgumentOutOfRangeException($"Expecting an array of size {expectedSize}", nameof(data)); @@ -43,8 +43,7 @@ internal class ReadHoldingRegistersResponseFrame : ModbusFrame throw new ArgumentException(nameof(RegistersRead)); } - - public static ReadHoldingRegistersResponseFrame Parse(ModbusFrame frame) => new ReadHoldingRegistersResponseFrame(frame.Data); + public static ReadHoldingRegistersResponseFrame Parse(Byte[] data) => new ReadHoldingRegistersResponseFrame(data); public static ReadHoldingRegistersResponseFrame Parse(ArraySegment data) => new ReadHoldingRegistersResponseFrame(data); } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs index 29cebda39..63479732c 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs @@ -31,11 +31,11 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame private ReadInputRegistersResponseFrame(ArraySegment data) : base(data) { + AssertFunctionCode(ReadInputRegisters); + if (data.Count < MinSize) throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data)); - AssertFunctionCode(ReadInputRegisters); - var expectedSize = ByteCount + MinSize; if (data.Count != expectedSize) throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data)); @@ -46,9 +46,9 @@ internal class ReadInputRegistersResponseFrame : ModbusFrame - public static ReadInputRegistersResponseFrame Parse(ModbusFrame rawFrame) + public static ReadInputRegistersResponseFrame Parse(Byte[] data) { - return new ReadInputRegistersResponseFrame(rawFrame.Data); + return new ReadInputRegistersResponseFrame(data); } public static ReadInputRegistersResponseFrame Parse(ArraySegment data) diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs index 43eed5e21..0248feb10 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs @@ -32,11 +32,11 @@ internal class ReadWriteRegistersResponseFrame : ModbusFrame private ReadWriteRegistersResponseFrame(ArraySegment data) : base(data) { + AssertFunctionCode(ReadWriteMultipleRegisters); + if (data.Count < MinSize) throw new ArgumentException($"Expecting an array of size {MinSize} or more", nameof(data)); - AssertFunctionCode(ReadWriteMultipleRegisters); - var expectedSize = ByteCount + MinSize; if (data.Count != expectedSize) throw new ArgumentException($"Expecting an array of size {expectedSize}", nameof(data)); diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs index 76518a0e5..d48c7c33e 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs @@ -1,4 +1,5 @@ using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Utils; using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; @@ -24,16 +25,14 @@ public class WriteCoilsResponseFrame : ModbusFrame private WriteCoilsResponseFrame(Byte[] data) : this (new ArraySegment(data) ) { } - private WriteCoilsResponseFrame(ArraySegment data) : base(data) { + AssertFunctionCode(WriteMultipleCoils); + if (data.Count != Size) throw new ArgumentException($"Expecting an array of size {Size}", nameof(data)); - - AssertFunctionCode(WriteMultipleCoils); } - public static WriteCoilsResponseFrame Parse(ArraySegment data) => new WriteCoilsResponseFrame(data); - public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data); - public static WriteCoilsResponseFrame Parse(ModbusFrame frame) => new WriteCoilsResponseFrame(frame.Data); + public static WriteCoilsResponseFrame Parse(Byte[] data) => new WriteCoilsResponseFrame(data); + } diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs index 76a0a6b7d..bf64eac81 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs @@ -27,13 +27,13 @@ internal class WriteRegistersResponseFrame : ModbusFrame private WriteRegistersResponseFrame(ArraySegment data) : base(data) { + AssertFunctionCode(WriteMultipleRegisters); + if (data.Count != Size) throw new ArgumentException($"Expecting an array of size {Size}", nameof(data)); - - AssertFunctionCode(WriteMultipleRegisters); } public static WriteRegistersResponseFrame Parse(ArraySegment data) => new WriteRegistersResponseFrame(data); public static WriteRegistersResponseFrame Parse(Byte[] data) => new WriteRegistersResponseFrame(data); - public static WriteRegistersResponseFrame Parse(ModbusFrame frame) => new WriteRegistersResponseFrame(frame.Data); + } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs b/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs index a21f30d94..101175d7a 100644 --- a/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs @@ -1,39 +1,42 @@ +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.FunctionCode; + namespace InnovEnergy.Lib.Protocols.Modbus.Protocol; [Flags] public enum FunctionCode : byte { - ReadCoil = 1, ReadCoilError = ReadCoil | Error , - ReadDiscreteInputs = 2, ReadDiscreteInputsError = ReadDiscreteInputs | Error , - ReadHoldingRegisters = 3, ReadHoldingRegistersError = ReadHoldingRegisters | Error , - ReadInputRegisters = 4, ReadInputRegistersError = ReadInputRegisters | Error , + ReadCoils = 1, + ReadDiscreteInputs = 2, + ReadHoldingRegisters = 3, + ReadInputRegisters = 4, - WriteSingleCoil = 5, WriteSingleCoilError = WriteSingleCoil | Error , - WriteSingleRegister = 6, WriteSingleRegisterError = WriteSingleRegister | Error , - WriteMultipleCoils = 15, WriteMultipleCoilsError = WriteMultipleCoils | Error , - WriteMultipleRegisters = 16, WriteMultipleRegistersError = WriteMultipleRegisters | Error , + WriteSingleCoil = 5, + WriteSingleRegister = 6, + WriteMultipleCoils = 15, + WriteMultipleRegisters = 16, - ReadWriteMultipleRegisters = 23, ReadWriteMultipleRegistersError = ReadWriteMultipleRegisters | Error , + ReadWriteMultipleRegisters = 23, Error = 128 } + public static class FunctionCodeExtensions { public static Boolean IsWriteOnly(this FunctionCode fc) { - return fc == FunctionCode.WriteSingleCoil || - fc == FunctionCode.WriteSingleRegister || - fc == FunctionCode.WriteMultipleCoils || - fc == FunctionCode.WriteMultipleRegisters; + return fc is WriteSingleCoil + or WriteSingleRegister + or WriteMultipleCoils + or WriteMultipleRegisters; } public static Boolean IsMultiWrite(this FunctionCode fc) { - return fc == FunctionCode.WriteMultipleCoils || - fc == FunctionCode.WriteMultipleRegisters || - fc == FunctionCode.ReadWriteMultipleRegisters; + return fc is WriteMultipleCoils + or WriteMultipleRegisters + or ReadWriteMultipleRegisters; } public static Boolean IsError(this FunctionCode fc) @@ -47,6 +50,5 @@ public static class FunctionCodeExtensions { return fc | FunctionCode.Error; } - } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs b/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs new file mode 100644 index 000000000..f97d6cce0 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/ModbusKind.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol; + +[Flags] +public enum ModbusKind : byte +{ + HoldingRegister, + InputRegister, + Coil, + DiscreteInput, +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs new file mode 100644 index 000000000..8e45974ac --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/AddressOffset.cs @@ -0,0 +1,11 @@ + +using static System.AttributeTargets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(Class | Struct)] +public class AddressOffset : Attribute +{ + public Int32 Offset { get; } + public AddressOffset(Int32 offset) => Offset = offset; +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs new file mode 100644 index 000000000..410b35b13 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/BigEndian.cs @@ -0,0 +1,12 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using static System.AttributeTargets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(Class | Struct | Property | Field)] +public class BigEndian : EndianAttribute +{ + public BigEndian() : base(Endian.Big) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs new file mode 100644 index 000000000..1a73e8142 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs @@ -0,0 +1,10 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class Coil : ModbusBoolean +{ + public Coil(UInt16 address) : base(address, ModbusKind.Coil) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs new file mode 100644 index 000000000..8840f8f33 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/DiscreteInput.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class DiscreteInput : ModbusBoolean +{ + public DiscreteInput(UInt16 address) : base(address, ModbusKind.DiscreteInput) { } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs new file mode 100644 index 000000000..de9fa6dc8 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/EndianAttribute.cs @@ -0,0 +1,11 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using static System.AttributeTargets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(Class | Struct | Property | Field)] +public class EndianAttribute : Attribute +{ + public EndianAttribute(Endian endian) => Endian = endian; + public Endian Endian { get; } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs new file mode 100644 index 000000000..0e0128a4c --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/HoldingRegister.cs @@ -0,0 +1,27 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + + +public class HoldingRegister : ModbusRegister +{ + private static readonly Type DefaultModbusType = typeof(UInt16); + + public HoldingRegister(UInt16 address) : this(address, DefaultModbusType) + { + } + + public HoldingRegister(UInt16 address, Type modbusType) : base(address, modbusType, ModbusKind.HoldingRegister) + { + } +} + + +public class HoldingRegister : HoldingRegister where T : IConvertible +{ + public HoldingRegister(UInt16 address) : base(address, typeof(T)) + { + } +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs new file mode 100644 index 000000000..38ce1accd --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/InputRegister.cs @@ -0,0 +1,28 @@ + + +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class InputRegister : ModbusRegister +{ + private static readonly Type DefaultModbusType = typeof(UInt16); + + public InputRegister(UInt16 address) : this(address, DefaultModbusType) + { + } + + public InputRegister(UInt16 address, Type modbusType) : base(address, modbusType, ModbusKind.InputRegister) + { + } +} + + +public class InputRegister : InputRegister where T : IConvertible +{ + public InputRegister(UInt16 address) : base(address, typeof(T)) + { + } +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs new file mode 100644 index 000000000..06de598cf --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/LittleEndian.cs @@ -0,0 +1,11 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class LittleEndian : EndianAttribute +{ + public LittleEndian() : base(Endian.Little) + { + } +} + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs new file mode 100644 index 000000000..cbe40dda8 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusAttribute.cs @@ -0,0 +1,48 @@ +using InnovEnergy.Lib.Protocols.Modbus.Protocol; +using static System.AttributeTargets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(Field | Property)] +public abstract class ModbusAttribute : Attribute +{ + public UInt16 Address { get; } + public UInt16 Size { get; } + public Type ModbusType { get; } + public ModbusKind Kind { get; } + + protected ModbusAttribute(UInt16 address, UInt16 size, Type modbusType, ModbusKind kind) + { + Address = address; + Size = size; + ModbusType = modbusType; + Kind = kind; + } + + protected ModbusAttribute(UInt16 address, Type modbusType, ModbusKind kind) : this(address, GetSize(modbusType), modbusType, kind) + { + } + + private static UInt16 GetSize(Type modbusType) + { + if (!TypeToSize.TryGetValue(modbusType, out var size)) + throw new ArgumentException("cannot infer size of" + nameof(modbusType), nameof(modbusType)); + + return size; + } + + + private static readonly Dictionary TypeToSize = new() + { + [typeof(Boolean)] = 1, + [typeof(Int16)] = 1, + [typeof(UInt16)] = 1, + [typeof(Int32)] = 2, + [typeof(UInt32)] = 2, + [typeof(Single)] = 2, + [typeof(Int64)] = 4, + [typeof(UInt64)] = 4, + [typeof(Double)] = 4, + }; +} + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs new file mode 100644 index 000000000..fa527d80e --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusBoolean.cs @@ -0,0 +1,11 @@ + +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +public class ModbusBoolean : ModbusAttribute +{ + protected ModbusBoolean(UInt16 address, ModbusKind kind) : base(address, typeof(Boolean), kind) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs new file mode 100644 index 000000000..6a456a48f --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/ModbusRegister.cs @@ -0,0 +1,36 @@ + + +using InnovEnergy.Lib.Protocols.Modbus.Protocol; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + + +public abstract class ModbusRegister : ModbusAttribute +{ + public Double Scale { get; init; } = 1; + public Double Offset { get; init; } = 0; + + protected ModbusRegister(UInt16 address, Type modbusType, ModbusKind kind) : base(address, modbusType, kind) + { + if (!SupportedTypes.Contains(modbusType)) + throw new ArgumentException($"Type {modbusType.Name} is not supported " + + $"for {nameof(ModbusRegister)}", + nameof(modbusType)); + } + + + private static readonly Type[] SupportedTypes = + { + typeof(Boolean), + typeof(Int16) , + typeof(UInt16), + typeof(Int32), + typeof(UInt32), + typeof(Single), + typeof(Int64) , + typeof(UInt64), + typeof(Double), + }; +} + + diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs new file mode 100644 index 000000000..de10759fc --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/OneBasedAddressing.cs @@ -0,0 +1,11 @@ +using static System.AttributeTargets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +[AttributeUsage(Class | Struct)] +public class OneBasedAddressing : AddressOffset +{ + public OneBasedAddressing() : base(-1) + { + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs new file mode 100644 index 000000000..15be8c7d0 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs @@ -0,0 +1,128 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; + +#pragma warning disable CS8509 +#pragma warning disable CS8524 + +internal record Batch(Action Read, Action Write, String DebugString) +{ + public override String ToString() => DebugString; +} + +public static class Batches +{ + internal static IReadOnlyList> MakeBatchesFor(this ModbusClient modbusClient, Int32 addressOffset) + { + var members = ModbusMembers + .From(addressOffset) + .OrderBy(m => m.Kind) + .ThenBy(m => m.StartAddress) + .ThenBy(m => m.EndAddress); + + return MakeBatches(modbusClient, members).ToList(); + } + + private static IEnumerable> MakeBatches(ModbusClient mb, IEnumerable modbusMembers) + { + var batchMembers = new List(); + + foreach (var member in modbusMembers) + { + if (CloseBatch(member)) + { + yield return MakeBatch(mb, batchMembers); + batchMembers = new List(); + } + + batchMembers.Add(member); + } + + if (batchMembers.Count > 0) + yield return MakeBatch(mb, batchMembers); + + Boolean CloseBatch(ModbusMember m) + { + if (batchMembers.Count == 0) + return false; + + return m.StartAddress > batchMembers[^1].EndAddress // gap between registers + || m.EndAddress > batchMembers[0].StartAddress + Constants.MaxRegs // max batch size reached + || m.Kind != batchMembers[0].Kind; // different Kind + } + } + + + private static Batch MakeBatch(ModbusClient modbusClient, IReadOnlyList members) + { + var startAddress = members[0].StartAddress; + var endAddress = members[^1].EndAddress; + var count = (UInt16)(endAddress - startAddress); + var kind = members[0].Kind; + var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil; + var debugString = $"{kind}: {startAddress}" + (endAddress - 1 == startAddress ? "" : $"-{endAddress - 1}"); + + + // var modPoll = $"{kind}: {startAddress}-{endAddress}"; // TODO + var read = MakeRead(); + var write = MakeWrite(); + + return new Batch(read, write, debugString); + + Action MakeRead() + { + Func readModbus = kind switch + { + ModbusKind.InputRegister => () => modbusClient.ReadInputRegisters (startAddress, count), + ModbusKind.HoldingRegister => () => modbusClient.ReadHoldingRegisters(startAddress, count), + ModbusKind.DiscreteInput => () => modbusClient.ReadDiscreteInputs (startAddress, count), + ModbusKind.Coil => () => modbusClient.ReadCoils (startAddress, count), + }; + + //Console.WriteLine("start: " + startAddress + " count: " + count); + + return record => + { + var mbData = readModbus(); + foreach (var member in members) + { + member.ModbusToRecord(mbData, record!); + } + }; + } + + Action MakeWrite() + { + if (!isWritable) + return _ => { }; // nop + + Func createMbData = kind switch + { + ModbusKind.HoldingRegister => () => MbData.Registers(startAddress, count), + ModbusKind.Coil => () => MbData.Coils (startAddress, count), + }; + + + Action writeModbus = kind switch + { + ModbusKind.HoldingRegister => d => modbusClient.WriteRegisters(startAddress, d.GetRegisters()), + ModbusKind.Coil => d => modbusClient.WriteCoils (startAddress, d.GetCoils().Take(count).ToList()), + // ^^ TODO: Coils.count is broken, fix when refactoring to use direct binary codec + }; + + + return rec => + { + var mbData = createMbData(); + + foreach (var member in members) + member.RecordToModbus(rec!, mbData); + + writeModbus(mbData); + }; + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs b/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs new file mode 100644 index 000000000..ed62ba2b1 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs @@ -0,0 +1,220 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; +using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using InnovEnergy.Lib.Utils; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; +using static System.Reflection.BindingFlags; + +namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; + +#pragma warning disable CS8509 + +internal record ModbusMember +( + UInt16 StartAddress, + UInt16 EndAddress, + ModbusKind Kind, + Action ModbusToRecord, + Action RecordToModbus +); + +internal static class ModbusMembers +{ + private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0); + + internal static IEnumerable From(Int32 globalAddressOffset, Endian globalEndian = Endian.Undefined) + { + var recordType = typeof(R); + + // "=======================================================================".WriteLine(); + // recordType.Name.WriteLine(); + + var offset = recordType.GetRecordOffset(globalAddressOffset); + var endian = recordType.GetEndian(globalEndian); + + return recordType + .GetDataMembers() + .Where(HasAttribute) + .Select(m => m.CreateModbusMember(offset, endian)); + } + + private static Int32 GetRecordOffset([DynamicallyAccessedMembers(All)] this Type recordType, Int32 globalAddressOffset) + { + return recordType + .GetCustomAttributes() + .Aggregate(globalAddressOffset, (a, b) => a + b.Offset); + } + + private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian) + { + return recordType + .GetCustomAttributes() + .Aggregate(endian, (a, b) => b.Endian.InheritFrom(a)); + } + + private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian) + { + var attribute = info.GetCustomAttributes().Single(); + var endian = info.GetCustomAttributes() + .Select(a => a.Endian) + .Append(Endian.Undefined) + .First() + .InheritFrom(globalEndian); + + var address = (UInt16)(attribute.Address + addressOffset); + var endAddress = (UInt16)(address + attribute.Size); + var modbusType = attribute.ModbusType; + var transform = attribute is ModbusRegister mra + ? (mra.Scale, mra.Offset) + : NoTransform; + + //Console.WriteLine(info.Name +" " + address + " " + modbusType); + + return new ModbusMember + ( + address, + endAddress, + attribute.Kind, + ModbusToRecord(), + RecordToModbus() + ); + + Action ModbusToRecord() + { + var decode = ConvertModbusToRecord(transform); + + Func readFromMbData = + modbusType == typeof(Boolean) ? d => d.GetInput(address) : + modbusType == typeof(UInt16) ? d => d.GetUInt16(address) : + modbusType == typeof(Int16) ? d => d.GetInt16(address) : + modbusType == typeof(UInt32) ? d => d.GetUInt32(address) : + modbusType == typeof(Int32) ? d => d.GetInt32(address) : + modbusType == typeof(Single) ? d => d.GetFloat32(address) : + modbusType == typeof(UInt64) ? d => d.GetUInt64(address) : + modbusType == typeof(Int64) ? d => d.GetInt64(address) : + modbusType == typeof(Double) ? d => d.GetFloat64(address) : + throw new ArgumentException(nameof(modbusType)); + + + var memberType = info switch + { + FieldInfo fi => fi.FieldType, + PropertyInfo pi => pi.PropertyType, + }; + + var ctr = memberType.GetConstructor(new[] { typeof(Double) }); // TODO: hardcoded double constructor for Units + + Func convert = ctr is null + ? value => value.ConvertTo(memberType) + : value => ctr.Invoke(new Object[] { value.ConvertTo() }); + + Action set = info switch + { + FieldInfo fi => (rec, value) => fi.SetValue(rec, convert(value)), + PropertyInfo pi => (rec, value) => pi.SetValue(rec, convert(value)), + }; + + return (mbData, rec) => + { + var rawModbusValue = readFromMbData(mbData.WithEndian(endian)); + var decoded = decode(rawModbusValue); + + set(rec, decoded); + }; + } + + Action RecordToModbus() + { + var encode = ConvertRecordToModbus(transform); + + Func get = info switch + { + FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!, + PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!, + }; + + Action writeToMbData = + modbusType == typeof(Boolean)? (value, mbData) => mbData.SetCoil (address, value.ConvertTo()) : + modbusType == typeof(UInt16) ? (value, mbData) => mbData.SetUInt16 (address, value.ConvertTo()) : + modbusType == typeof(Int16) ? (value, mbData) => mbData.SetInt16 (address, value.ConvertTo()) : + modbusType == typeof(UInt32) ? (value, mbData) => mbData.SetUInt32 (address, value.ConvertTo()) : + modbusType == typeof(Int32) ? (value, mbData) => mbData.SetInt32 (address, value.ConvertTo()) : + modbusType == typeof(Single) ? (value, mbData) => mbData.SetFloat32(address, value.ConvertTo()) : + modbusType == typeof(UInt64) ? (value, mbData) => mbData.SetUInt64 (address, value.ConvertTo()) : + modbusType == typeof(Int64) ? (value, mbData) => mbData.SetInt64 (address, value.ConvertTo()) : + modbusType == typeof(Double) ? (value, mbData) => mbData.SetFloat64(address, value.ConvertTo()) : + throw new ArgumentException(nameof(modbusType)); + + return (rec, mbData) => + { + var memberValue = get(rec); + var encoded = encode(memberValue); + + writeToMbData(encoded, mbData.WithEndian(endian)); + }; + } + } + + + private static Func ConvertModbusToRecord((Double scale, Double offset) transform) + { + if (transform == NoTransform) + return Nop; + + var scale = transform.scale.ConvertTo(); + var offset = transform.offset.ConvertTo(); + + return c => + { + var value = c.ConvertTo(); + + return + /**********************************/ + /**/ (value + offset) * scale; /**/ + /**********************************/ + }; + } + + private static Func ConvertRecordToModbus((Double scale, Double offset) transform) + { + if (transform == NoTransform) + return Nop; + + var scale = transform.scale.ConvertTo(); + var offset = transform.offset.ConvertTo(); + + return c => + { + var value = c.ConvertTo(); + + return + /*******************************/ + /**/ value / scale - offset; /**/ + /*******************************/ + }; + } + + + private static T Nop(T c) => c; + + + private static IEnumerable GetDataMembers([DynamicallyAccessedMembers(All)] this Type recordType) + { + const BindingFlags bindingFlags = Instance + | Public + | NonPublic + | FlattenHierarchy; + + var fields = recordType.GetFields(bindingFlags); + var props = recordType.GetProperties(bindingFlags); + + return fields.Concat(props); + } + + private static Boolean HasAttribute(MemberInfo i) where T : Attribute + { + return i.GetCustomAttributes().Any(); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs new file mode 100644 index 000000000..8f0acf770 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Reflection; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; + +public class ModbusDevice where R : notnull +{ + private readonly IReadOnlyList> _Batches; + + public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0) + { + _Batches = modbusClient.MakeBatchesFor(addressOffset); + } + + public R Read() + { + R r; + + try + { + r = Activator.CreateInstance(); + } + catch (Exception e) + { + throw new Exception + ( + $"type {typeof(R).Name} seems to lack a parameterless constructor. " + + $"Either create one or use the other overload of{nameof(Read)} instead.", + e + ); + } + + return Read(r); + } + + public R Read([DynamicallyAccessedMembers(All)] R record) + { + foreach (var batch in _Batches) + batch.Read(record); + + return record; + } + + public void Write([DynamicallyAccessedMembers(All)] R record) + { + foreach (var batch in _Batches) + batch.Write(record); + } +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusEncoding.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusEncoding.cs new file mode 100644 index 000000000..7e749cb3b --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusEncoding.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; + +public enum ModbusEncoding +{ + Binary, + Ascii +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusProtocol.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusProtocol.cs new file mode 100644 index 000000000..74ec6c3c3 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusProtocol.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; + +public enum ModbusProtocol +{ + Rtu, + Tcp +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs new file mode 100644 index 000000000..3b1870058 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs @@ -0,0 +1,55 @@ +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; + +namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; + +public static class ModbusSlave +{ + + public static Func ModbusTcp(this Channel channel) + { + ModbusTcpClient SlaveId(Byte slaveId) => new ModbusTcpClient(channel, slaveId); + return SlaveId; + } + + public static Func ModbusRtu(this Channel channel) + { + ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId); + return SlaveId; + } + + + public static Func ModbusTcp(this Channel channel) where R : notnull, new() + { + ModbusTcpClient SlaveId(Byte slaveId) + { + return new ModbusTcpClient(channel, slaveId); + } + + return SlaveId; + } + + public static Func ModbusRtu(this Channel channel) where R : notnull, new() + { + ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId); + return SlaveId; + } + + public static ModbusDevice TcpSlave(this Channel channel, Byte slaveId) where T : notnull, new() + { + var client = new ModbusTcpClient(channel, slaveId); + return new ModbusDevice(client); + } + + public static ModbusDevice RtuSlave(this Channel channel, Byte slaveId) where T : notnull, new() + { + var client = new ModbusRtuClient(channel, slaveId); + return new ModbusDevice(client); + } + + public static ModbusDevice Slave(this ModbusClient modbusClient) where T : notnull, new() + { + return new ModbusDevice(modbusClient); + } + +} \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs b/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs index bad3ee640..9c069d02b 100644 --- a/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs +++ b/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs @@ -1,4 +1,3 @@ -using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; namespace InnovEnergy.Lib.Protocols.Modbus.Tcp; diff --git a/csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs b/csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs index 1e0217ff6..82b1e4de4 100644 --- a/csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs +++ b/csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs @@ -1,17 +1,15 @@ +using InnovEnergy.Lib.Utils; + namespace InnovEnergy.Lib.Protocols.Modbus.Util; public static class ArraySegmentExtensions { - public static ArraySegment ToArraySegment(this IEnumerable enumerable) + public static ArraySegment ToArraySegment(this IEnumerable enumerable) => enumerable switch { - if (enumerable is T[] array) - return new ArraySegment(array); - - if (enumerable is ArraySegment arraySegment) - return arraySegment; // already an ArraySegment, doh! - - return new ArraySegment(enumerable.ToArray()); - } + ArraySegment ars => ars, + IReadOnlyList rol => rol.ToArray(), + _ => enumerable.ToArray() + }; public static ArraySegment Skip(this ArraySegment seg, Int32 count) { diff --git a/csharp/Lib/SrcGen/Attributes/Generate.cs b/csharp/Lib/SrcGen/Attributes/Generate.cs new file mode 100644 index 000000000..f2161b573 --- /dev/null +++ b/csharp/Lib/SrcGen/Attributes/Generate.cs @@ -0,0 +1,16 @@ + +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.SrcGen.Attributes; + +[SuppressMessage("ReSharper", "UnusedTypeParameter")] +[AttributeUsage(AttributeTargets.Struct, AllowMultiple = true)] +public class Generate : Attribute +{ + public String[] Defines { get; } + + public Generate(params String[] defines) + { + Defines = defines; + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Attributes/NestProperties.cs b/csharp/Lib/SrcGen/Attributes/NestProperties.cs new file mode 100644 index 000000000..afb6fb6a0 --- /dev/null +++ b/csharp/Lib/SrcGen/Attributes/NestProperties.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.SrcGen.Attributes; + +public class NestProperties : Attribute +{ + public String StructName { get; } + + public NestProperties(String structName) => StructName = structName; +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs b/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs new file mode 100644 index 000000000..0d9925e9a --- /dev/null +++ b/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace InnovEnergy.Lib.SrcGen; + + +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(IeCodeRefactoringProvider))] +public sealed class IeCodeRefactoringProvider : CodeRefactoringProvider +{ + + // NOT WORKING + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + // var root = await context + // .Document + // .GetSyntaxRootAsync(context.CancellationToken) + // .ConfigureAwait(false); + + await File.WriteAllTextAsync("/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/test.txt", context.Span.ToString()); + + var action = CodeAction.Create("IeAction", async token => + { + var text = await context.Document.GetTextAsync(token); + + var modified = text.Replace(0, 0, "// " + context.Span + "\n"); + + return context.Document.WithText(modified); + }); + + context.RegisterRefactoring(action); + } +} diff --git a/csharp/Lib/SrcGen/Editor.cs b/csharp/Lib/SrcGen/Editor.cs new file mode 100644 index 000000000..cc6cb4538 --- /dev/null +++ b/csharp/Lib/SrcGen/Editor.cs @@ -0,0 +1,81 @@ +using Microsoft.CodeAnalysis; + +namespace InnovEnergy.Lib.SrcGen; + +public readonly ref struct Rewriter where TRoot : SyntaxNode where TNode : SyntaxNode +{ + internal readonly TRoot Root; + internal readonly IEnumerable Descendants; + + internal Rewriter(TRoot root, IEnumerable descendants) + { + Root = root; + Descendants = descendants; + } + + public Rewriter Where(Func predicate) + { + return new Rewriter(Root, Descendants.Where(predicate)); + } + + public Rewriter OfType() where T : TNode + { + return new Rewriter(Root, Descendants.OfType()); + } + + public Rewriter HasAncestor() where T : SyntaxNode + { + return new Rewriter(Root, Descendants.Where(d => d.Ancestors().OfType().Any())); + } + + public Rewriter HasParent() where T : SyntaxNode + { + return new Rewriter(Root, Descendants.Where(d => d.Parent is T)); + } + + public Rewriter SelectNodes(Func> nodes) + { + return new Rewriter(Root, Descendants.SelectMany(nodes)); + } + + public Rewriter SelectNode(Func node) where T: SyntaxNode + { + return new Rewriter(Root, Descendants.Select(node)); + } + + public Rewriter GetAncestor() where T : SyntaxNode + { + return SelectNode(n => n.Ancestors().OfType().First()); + } + + public TRoot Replace(SyntaxNode syntaxNode) + { + return Root.ReplaceNodes(Descendants, (_, _) => syntaxNode); + } + + public TRoot Replace(Func map) + { + return Root.ReplaceNodes(Descendants, (_, n) => map(n)); + } + + public TRoot Remove() => Remove(SyntaxRemoveOptions.KeepNoTrivia); + + public TRoot Remove(SyntaxRemoveOptions options) => Root.RemoveNodes(Descendants, options)!; +} + +public static class Rewrite +{ + public static Rewriter EditNodes(this R root) where R : SyntaxNode + { + return new Rewriter(root, root.DescendantNodes()); + } +} + + + + + + + + + diff --git a/csharp/Lib/SrcGen/GenerateAttributes.cs b/csharp/Lib/SrcGen/GenerateAttributes.cs new file mode 100644 index 000000000..0afdcde63 --- /dev/null +++ b/csharp/Lib/SrcGen/GenerateAttributes.cs @@ -0,0 +1,74 @@ +using InnovEnergy.Lib.Utils; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace InnovEnergy.Lib.SrcGen; + +public static class GenerateAttributes +{ + public static async Task Generate(Project project, Compilation compilation) + { + var syntaxTrees = await project + .Documents + .Where(d => d.Project == project) + .Where(d => d.SupportsSyntaxTree) + .Select(async d => await d.GetSyntaxRootAsync()) + .WhenAll(); + + syntaxTrees.NotNull() + .SelectMany(c => c.GetGenerateAttributes()) + .ForEach(GenerateFile); + + String GenerateFile(AttributeSyntax attribute) + { + var derivedType = attribute.GetAncestor(); + var defineArgs = attribute.ArgumentList?.Arguments ?? Enumerable.Empty(); + var filePath = derivedType.SyntaxTree.FilePath; + var derivedName = derivedType.Identifier.ValueText; + var baseRef = attribute.GetGenericArgument(); + var baseType = compilation.GetDefinition(baseRef); + var baseName = baseType.Identifier.ValueText; + var baseFile = baseType.GetAncestor(); + var generatedPath = filePath.GetGeneratedPath(baseName); + + var defines = defineArgs + .Select(n => n.ToString()) + .Select(s => s.Replace("\"", "").ToUpper()) + .Select(s => $"#define {s}"); + + Console.WriteLine($"Generating {generatedPath}"); + + var code = GenerateCode(); + + var fileContents = Utils.AutoGeneratedMessage(nameof(GenerateAttributes)) + + defines.JoinLines() + "\n" + + code; + + File.WriteAllText(generatedPath, fileContents); + + return generatedPath; + + + String GenerateCode() + { + try + { + return baseFile + .AddPartialModifier() + .ReplaceIdentifiers(baseName, derivedName) + .RemoveNotImplemented() + .GetText() + .ToString(); + } + catch (Exception e) + { + return $"Failed to generate source for {filePath}\n\n{e}" + .SplitLines() + .Select(l => $"// {l}") + .JoinLines(); + } + } + + } + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/NestProperties.cs b/csharp/Lib/SrcGen/NestProperties.cs new file mode 100644 index 000000000..bafcdd0d8 --- /dev/null +++ b/csharp/Lib/SrcGen/NestProperties.cs @@ -0,0 +1,251 @@ +using InnovEnergy.Lib.SrcGen.Trees; +using InnovEnergy.Lib.Utils; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Sawmill; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Microsoft.CodeAnalysis.CSharp.SyntaxKind; + +namespace InnovEnergy.Lib.SrcGen; + + + +public static class NestProperties +{ + private static readonly SyntaxToken PublicKeyword = Token(SyntaxKind.PublicKeyword); + + public static async Task Generate(Project project, Compilation compilation) + { + var syntaxTrees = await project + .Documents + .Where(d => d.Project == project) + .Where(d => d.SupportsSyntaxTree) + .Select(async d => await d.GetSyntaxRootAsync()) + .WhenAll(); + + syntaxTrees.NotNull() + .SelectMany(c => c.GetNestPropertiesAttributes()) + .ForEach(GenerateFile); + + String GenerateFile(AttributeSyntax attribute) + { + var typeName = attribute.ArgumentList!.Arguments.Single().ToString().Replace("\"", ""); + var type = attribute.GetAncestor(); + var nameSpace = attribute.GetAncestor(); + var filePath = type.SyntaxTree.FilePath; + var generatedPath = filePath.GetGeneratedPath(nameof(NestProperties)); + + var recordProperties = type + .Members + .OfType() + .Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword))) + .ToList(); + + var propNames = recordProperties.Select(m => m.Identifier.ValueText); + + var prefixTree = PrefixTree.Create(propNames); + + prefixTree.WriteLine(); + Console.WriteLine("$$$$$$$$$$$$$"); + + + var wrappedTypeName = type.Identifier.ValueText; + + var records = new List(); + + prefixTree.Fold, MemberDeclarationSyntax>((memory, tree) => + { + var path = tree + .Ancestors + .Prepend(tree) + .Aggregate("", (a, b) => b.Node + a); + + if (tree.IsLeaf) + { + var prop = recordProperties.First(p => p.Identifier.ValueText == path); + + var writable = prop + .DescendantNodes() + .OfType() + .Any(d => d.IsKind(SetAccessorDeclaration) + && !d.DescendantTokens().Any(t => t.IsKind(PrivateKeyword))); + + return CreateAccessorProperty + ( + prop.Type, + tree.Node, + path, + writable + ); + } + else + { + var children = memory.ToArray(); + + var rec = CreateWrapperRecord(typeName + path, wrappedTypeName, children); + records.Add(rec); + + return CreateWrapperProperty(IdentifierName(typeName + path), Identifier(tree.Node)); + } + }); + + //x.NormalizeWhitespace().GetText().WriteLine(); + + records.Reverse(); + records.ForEach(r => r.NormalizeWhitespace().WriteLine("\n")); + + + + Console.WriteLine($"Generating {generatedPath}"); + + + + + + return ""; + + + + } + + } + + private static CompilationUnitSyntax CreateCompilationUnit(NameSyntax nameSpaceName, params MemberDeclarationSyntax[] members) + { + var nameSpace = NamespaceDeclaration(nameSpaceName) + .WithMembers(members.Apply(List)); + + return CompilationUnit().WithMembers(nameSpace.Apply(SingletonList)); + } + + private static RecordDeclarationSyntax CreateWrapperRecord(String structName, + String wrappedTypeName, + IEnumerable properties) + { + const String self = "Self"; + + var wrappedType = IdentifierName(wrappedTypeName); + var recId = Identifier(structName); + var modifiers = TokenList(Token(SyntaxKind.PublicKeyword), Token(ReadOnlyKeyword)); + + + + + //properties.Prepend(selfProperty).Prepend(constructor).Apply(List); + + // var structMembers = new MemberDeclarationSyntax[] { selfProperty, constructor } + // .Apply(List); + + var recMembers = properties.Apply(List); + + + var recParams = self + .Apply(Identifier) + .Apply(Parameter) + .WithType(wrappedType) + .Apply(SingletonSeparatedList) + .Apply(ParameterList); + + return RecordDeclaration(Token(RecordKeyword), recId) + .WithModifiers(modifiers) + .WithClassOrStructKeyword(Token(StructKeyword)) + .WithParameterList(recParams) + .WithModifiers(modifiers) + .WithOpenBraceToken(Token(OpenBraceToken)) + .WithMembers(recMembers) + .WithCloseBraceToken(Token(CloseBraceToken)); + + //return /*StructDeclaration(structId)*/ + + + } + + + private static PropertyDeclarationSyntax CreateAccessorProperty(TypeSyntax propertyTypeName, + String propertyName, + String parentPropertyName, + Boolean writeable) + { + const String self = "Self"; + const String value = "value"; + + var propertyModifier = SyntaxKind + .PublicKeyword + .Apply(Token) + .Apply(TokenList); + + var semicolonToken = Token(SemicolonToken); + + var accessExpression = MemberAccessExpression + ( + SimpleMemberAccessExpression, + IdentifierName(self), + IdentifierName(parentPropertyName) + ); + + var getterBody = ArrowExpressionClause(accessExpression); + + var getter = GetAccessorDeclaration + .Apply(AccessorDeclaration) + .WithExpressionBody(getterBody) + .WithSemicolonToken(semicolonToken); + + var setterBody = AssignmentExpression(SimpleAssignmentExpression, accessExpression, IdentifierName(value)) + .Apply(ArrowExpressionClause); + + var setter = SetAccessorDeclaration + .Apply(AccessorDeclaration) + .WithExpressionBody(setterBody) + .WithSemicolonToken(semicolonToken); + + var accessors = writeable + ? new[] { getter, setter } + : new[] { getter }; + + var accessorsList = accessors + .Apply(List) + .Apply(AccessorList); + + var property = PropertyDeclaration + ( + type : propertyTypeName, + identifier: Identifier(propertyName) + ); + + return property + .WithModifiers(propertyModifier) + .WithAccessorList(accessorsList); + } + + + + private static PropertyDeclarationSyntax CreateWrapperProperty(TypeSyntax type, SyntaxToken identifier) + { + var modifiers = SyntaxKind + .PublicKeyword + .Apply(Token) + .Apply(TokenList); + + var self = IdentifierName("Self") + .Apply(Argument) + .Apply(SingletonSeparatedList) + .Apply(ArgumentList); + + var body = ObjectCreationExpression(type) + .WithArgumentList(self) + .Apply(ArrowExpressionClause); + + return PropertyDeclaration(type, identifier) + .WithModifiers(modifiers) + .WithExpressionBody(body) + .WithSemicolonToken(Token(SemicolonToken)); + } +} + + + + + + + \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Program.cs b/csharp/Lib/SrcGen/Program.cs new file mode 100644 index 000000000..be1903bb6 --- /dev/null +++ b/csharp/Lib/SrcGen/Program.cs @@ -0,0 +1,154 @@ +using InnovEnergy.Lib.SrcGen.Trees; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis.MSBuild; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen; + + + +using S = IEnumerable; + +//public record Node(String Prefix, Node Children); + +public static class Program +{ + + private const String GeneratedCodeMsg = "// This file has been automatically generated, do not edit!\n\n"; + + public static async Task Main(String[] args) + { + //Nest(); + + var projectPath = args.FirstOrDefault() + ?? "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj"; + + if (projectPath is null) + throw new ArgumentException(nameof(projectPath)); + + MSBuildLocator.RegisterDefaults(); // WTF + + using var workspace = MSBuildWorkspace.Create(); + await workspace.OpenProjectAsync(projectPath); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(p => p.FilePath == projectPath); + var compilation = await project.GetCompilationAsync(); + + if (compilation is null) + throw new Exception($"Project {projectPath} failed to build!"); + + await GenerateAttributes.Generate(project, compilation); + await NestProperties.Generate(project, compilation); + } + + + private static void Nest() + { + var strings = new[] + { + "AcCurrent", + "AcVoltage", + "AcPowerReactive", + "AcPowerActive", + "DcCurrent", + "DcVoltage", + "Temperature", + "Soc" + }; + + + var tree1 = new ForwardTree("", strings.Select(s => new ForwardTree(s)).ToList()); + + static ForwardTree GroupByPrefix(ForwardTree tree) + { + var newChildren = tree + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => new ForwardTree(s.Node[1..])) + .Select(g => GroupByPrefix(new ForwardTree(g.Key, g.ToList()))) + .ToList(); + + return newChildren.Count == 0 + ? tree // leaf + : new ForwardTree(tree.Node, newChildren); + } + + + ForwardTree ConcatUnaryNodes(ForwardTree tree) + { + return tree.Children.Count == 1 && tree.Children[0] is var child + ? new ForwardTree(tree.Node + child.Node, child.Children) + : tree; + } + + var tree2 = tree1.Rewrite(GroupByPrefix); + var tree3 = tree2.Rewrite(ConcatUnaryNodes); + + + Console.WriteLine(tree1); + Console.WriteLine("\n======\n"); + Console.WriteLine(tree2); + Console.WriteLine("\n======\n"); + Console.WriteLine(tree3); + } + + + private static Func, ForwardTree> RewriteTree(Func, ForwardTree> rec) + => tree + => tree.RewriteIter(n => + { + if (n.Children.Count == 0 || n.Children.SelectMany(c => c.Children).Any()) + return n; + + var groups = n + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => rec(new ForwardTree(s.Node[1..]))) + .Select(g => new ForwardTree(g.Key, g.ToList())) + .ToList(); + + if (groups.Count == 0) + return n; + + return new ForwardTree(n.Node, groups); + }); +} + + + + + // static IReadOnlyList GetChildren(IEnumerable strings) + // { + // return strings + // .Where(s => s.Length > 0) + // .GroupBy(s => s[..1], s => s[1..]) + // .Select(l => + // { + // var children = GetChildren(l); + // return children.Count == 1 + // ? new StringTree(l.Key + children[0].Node, children[0].Children) + // : new StringTree(l.Key, children); + // }) + // .ToList(); + // } + //var t2 = new Tree<(String prefix, String[] strings)>(stringNode, ); + + // var strs = Func (Func rec) + // => S (ss) + // => ss.Where(s => s.Length > 0) + // .GroupBy(s => s[..1], s => s[1..]) + // .SelectMany(g=> !g.Any() ? new[] { g.Key } : rec(g)) + // + // ; + // + // var g = Func (Func h) + // => Int32 (Int32 m) + // => m > 1 ? h(m - 1) + h(m - 2) : m; + // + // var fib = Combinator.Y(g); + // var fib2 = Combinator.Y(strs); + // + // var y = fib(5); + // var x = fib2(strings).ToList(); + // \ No newline at end of file diff --git a/csharp/Lib/SrcGen/SrcGen.csproj b/csharp/Lib/SrcGen/SrcGen.csproj new file mode 100644 index 000000000..7f0fcac19 --- /dev/null +++ b/csharp/Lib/SrcGen/SrcGen.csproj @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/Lib/SrcGen/SyntaxUtils.cs b/csharp/Lib/SrcGen/SyntaxUtils.cs new file mode 100644 index 000000000..bac279f52 --- /dev/null +++ b/csharp/Lib/SrcGen/SyntaxUtils.cs @@ -0,0 +1,182 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace InnovEnergy.Lib.SrcGen; + +public static class SyntaxUtils +{ + // const String DebugFile = "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/Debug.txt"; + // + // static SyntaxUtils() => File.WriteAllText(DebugFile, ""); + + public static Boolean HasExtendsAttribute(this TypeDeclarationSyntax td) + { + return td + .GetAttributes() + .Select(a => a.Name) + .OfType() + .Any(n => n.Identifier.ValueText == "Extends"); + } + + + public static T GetAncestor(this SyntaxNode sn) where T: SyntaxNode + { + return sn + .Ancestors() + .OfType() + .First(); + } + + + public static IEnumerable GetGenerateAttributes(this SyntaxNode sn) + { + return sn + .DescendantNodes() + .Where(n => n is AttributeSyntax { Name : GenericNameSyntax { Identifier.ValueText: "Generate" } }) + .OfType(); + } + + + public static IEnumerable GetNestPropertiesAttributes(this SyntaxNode sn) + { + return sn + .DescendantNodes() + .Where(n => n is AttributeSyntax { Name : IdentifierNameSyntax { Identifier.ValueText: "NestProperties" } }) + .OfType(); + } + + + + + public static IEnumerable GetAttributes(this MemberDeclarationSyntax member) + { + return member + .AttributeLists + .SelectMany(al => al.Attributes); + } + + // public static T Debug(this T t, String? title = null) + // { + // var prefix = title is null ? "" : title + ": "; + // var contents = t is null ? "" : t.ToString(); + // + // File.AppendAllText(DebugFile, prefix + contents + '\n'); + // return t; + // } + + public static IEnumerable GetAttributes(this SyntaxNode node, String attributeName) + { + return node + .DescendantTokens() + .Where(st => st.ValueText == attributeName) + .Where(st => st.IsKind(SyntaxKind.IdentifierToken)) + .Select(st => st.Parent?.Parent) + .OfType(); + } + + public static IEnumerable ChildNodes(this SyntaxNode node) where T : SyntaxNode + { + return node.ChildNodes().OfType(); + } + + public static IEnumerable DescendantNodes(this SyntaxNode node) where T : SyntaxNode + { + return node.DescendantNodes().OfType(); + } + + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static T ReplaceIdentifiers(this T root, String idToReplace, String replacementId) where T: SyntaxNode + { + var newIdentifier = SyntaxFactory.Identifier(replacementId); + + return root + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) + + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) + + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier.WithTrailingTrivia(NewLine))); + } + + + private static readonly SyntaxTrivia NewLine = SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); + private static readonly SyntaxTrivia WhiteSpace = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); + + private static readonly SyntaxToken PartialKeyword = SyntaxFactory + .Token(SyntaxKind.PartialKeyword) + .WithTrailingTrivia(WhiteSpace); + + public static CompilationUnitSyntax AddPartialModifier(this CompilationUnitSyntax baseFile) + { + return baseFile + .EditNodes() + .OfType() + .Where(s => !s.Modifiers.Contains(PartialKeyword)) + .Replace(s => s.AddModifiers(PartialKeyword)); + } + + + public static T GetDefinition(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode + { + return compilation + .GetDefinitions(baseTypeDeclaration) + .OfType() + .Single(); + } + + public static IEnumerable GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode + { + return compilation + .GetDefinitions(baseTypeDeclaration) + .OfType(); + } + + public static IEnumerable GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration) + { + return compilation + .GetSemanticModel(baseTypeDeclaration.SyntaxTree) + .GetTypeInfo(baseTypeDeclaration) + .Type? + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + ?? Enumerable.Empty(); + } + + public static IEnumerable GetGenericArguments(this AttributeSyntax attribute) => attribute.Name switch + { + GenericNameSyntax gns => gns.TypeArgumentList.Arguments, + _ => Enumerable.Empty() + }; + + public static TypeSyntax GetGenericArgument(this AttributeSyntax attribute) => attribute.GetGenericArguments().Single(); + + public static CompilationUnitSyntax RemoveNotImplemented(this CompilationUnitSyntax baseFile) + { + return baseFile + .EditNodes() + .OfType() + .Where(i => i.Identifier.ValueText == nameof(NotImplementedException)) + .HasAncestor() + .GetAncestor() + .Remove(); + } + + public static String GetGeneratedPath(this String path, String postfix = "generated") + { + var ext = Path.GetExtension(path); + var name = Path.GetFileNameWithoutExtension(path); + var dir = Path.GetDirectoryName(path); + + return $"{dir}/{name}.{postfix}{ext}"; + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/ForwardTree.cs b/csharp/Lib/SrcGen/Trees/ForwardTree.cs new file mode 100644 index 000000000..3776856c7 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/ForwardTree.cs @@ -0,0 +1,46 @@ +using InnovEnergy.Lib.Utils; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + + +public class ForwardTree : IRewritable> where T : notnull +{ + public T Node { get; } + public IReadOnlyList> Children { get; } + public Boolean IsLeaf => Children.Count == 0; + + public ForwardTree(T node) : this(node, Array.Empty>()) + { + } + + public ForwardTree(T node, IReadOnlyList> children) + { + Node = node; + Children = children; + } + + public ForwardTree WithNode(T node) => new ForwardTree(node, Children); + public ForwardTree WithNode(Func makeNode) => new ForwardTree(makeNode(Node), Children); + + public override String ToString() + { + var node = Node.ToString()!; + return Children.Aggregate(node, (s, c) => s + "\n" + c.ToString().Indent(node.Length)); + } + + Int32 IRewritable>.CountChildren() => Children.Count; + + void IRewritable>.GetChildren(Span> childrenReceiver) + { + for (var i = 0; i < Children.Count; i++) + childrenReceiver[i] = Children[i]; + } + + ForwardTree IRewritable>.SetChildren(ReadOnlySpan> newChildren) + { + return new ForwardTree(Node, newChildren.ToArray()); + } + + +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/PrefixTree.cs b/csharp/Lib/SrcGen/Trees/PrefixTree.cs new file mode 100644 index 000000000..40a50d421 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/PrefixTree.cs @@ -0,0 +1,41 @@ +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +using T = ForwardTree; + +public static class PrefixTree +{ + public static Tree Create(IEnumerable strings, String root = "") + { + var children = strings.Select(s => new T(s)).ToList(); + var tree = new T(root, children); + + var forwardTree = tree + .Rewrite(GroupByPrefix) + .Rewrite(ConcatUnaryNodes); + + return new Tree(forwardTree); + } + + private static T GroupByPrefix(T tree) + { + var newChildren = tree + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => new T(s.Node[1..])) + .Select(g => GroupByPrefix(new T(g.Key, g.ToList()))) + .ToList(); + + return newChildren.Count == 0 + ? tree // leaf + : new T(tree.Node, newChildren); + } + + private static T ConcatUnaryNodes(ForwardTree tree) + { + return tree.Children.Count == 1 && tree.Children[0] is var child + ? new T(tree.Node + child.Node, child.Children) + : tree; + } +} diff --git a/csharp/Lib/SrcGen/Trees/Tree.cs b/csharp/Lib/SrcGen/Trees/Tree.cs new file mode 100644 index 000000000..3e13a5bbf --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/Tree.cs @@ -0,0 +1,47 @@ +using InnovEnergy.Lib.Utils; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +public class Tree :IRewritable> where T : notnull +{ + private readonly ForwardTree _Tree; + public Tree? Parent { get; } + + public Boolean IsRoot => Parent is null; + public Boolean IsLeaf => !Children.Any(); + + public Tree(ForwardTree tree, Tree? parent = default) + { + _Tree = tree; + Parent = parent; + } + + public T Node => _Tree.Node; + + public IEnumerable> Children => _Tree + .Children + .Select(c => new Tree(c, this)); + + public IEnumerable> Ancestors => Parent.Unfold(t => t.Parent); + + public override String ToString() => _Tree.ToString(); + + public Tree Root => this.Unfold(t => t.Parent).Last(); + + Int32 IRewritable>.CountChildren() => _Tree.Children.Count; + + void IRewritable>.GetChildren(Span> childrenReceiver) + { + var i = 0; + foreach (var child in Children) + childrenReceiver[i++] = child; + } + + Tree IRewritable>.SetChildren(ReadOnlySpan> newChildren) + { + var forwardTree = new ForwardTree(Node, _Tree.Children.ToArray()); + + return new Tree(forwardTree, Parent); + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/TreeStruct.cs b/csharp/Lib/SrcGen/Trees/TreeStruct.cs new file mode 100644 index 000000000..b69be66e4 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/TreeStruct.cs @@ -0,0 +1,34 @@ + +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +public readonly struct TreeStruct where T : notnull +{ + [Obsolete] public TreeStruct() => throw new Exception("Forbidden"); + + public TreeStruct(T node, Func> getChildren) + { + Node = node; + _GetChildren = getChildren; + } + + public IEnumerable> Children + { + get + { + var getChildren = _GetChildren; + return _GetChildren(Node).Select(c => new TreeStruct(c, getChildren)); + } + } + + public T Node { get; } + + private readonly Func> _GetChildren; + + public IEnumerable TraverseDepthFirstPostOrder() => TreeTraversal.TraverseDepthFirstPostOrder(Node, _GetChildren); + public IEnumerable TraverseDepthFirstPreOrder() => TreeTraversal.TraverseDepthFirstPreOrder(Node, _GetChildren); + public IEnumerable TraverseBreadthFirst() => TreeTraversal.TraverseBreadthFirst(Node, _GetChildren); + + +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Utils.cs b/csharp/Lib/SrcGen/Utils.cs new file mode 100644 index 000000000..e75daf65c --- /dev/null +++ b/csharp/Lib/SrcGen/Utils.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.SrcGen; + +public static class Utils +{ + public static String AutoGeneratedMessage(String name) + { + return $"// This file has been automatically generated by {name}, do not edit!\n\n"; + } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/BatteryStatus.cs b/csharp/Lib/StatusApi/BatteryStatus.cs deleted file mode 100644 index be2c8f0d5..000000000 --- a/csharp/Lib/StatusApi/BatteryStatus.cs +++ /dev/null @@ -1,15 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - - -#pragma warning disable CS8618 - -public record BatteryStatus : IDcConnection -{ - public DcBus Dc { get; init; } - public Percent Soc { get; init; } - public Temperature Temperature { get; init; } -} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/CombinedStatus.cs b/csharp/Lib/StatusApi/CombinedStatus.cs deleted file mode 100644 index 132f0c573..000000000 --- a/csharp/Lib/StatusApi/CombinedStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record CombinedStatus -{ - public T Combined { get; init; } - public IReadOnlyList Children { get; init; } - - public Boolean Available => Children.Any(); - -} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Connections/IAc1Connection.cs b/csharp/Lib/StatusApi/Connections/IAc1Connection.cs index 54060cb20..8ede39061 100644 --- a/csharp/Lib/StatusApi/Connections/IAc1Connection.cs +++ b/csharp/Lib/StatusApi/Connections/IAc1Connection.cs @@ -5,4 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections; public interface IAc1Connection { Ac1Bus Ac { get; } -} \ No newline at end of file +} + diff --git a/csharp/Lib/StatusApi/Connections/IAc3Connection.cs b/csharp/Lib/StatusApi/Connections/IAc3Connection.cs index a1c0ca1b5..752db3c92 100644 --- a/csharp/Lib/StatusApi/Connections/IAc3Connection.cs +++ b/csharp/Lib/StatusApi/Connections/IAc3Connection.cs @@ -5,4 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections; public interface IAc3Connection { Ac3Bus Ac { get; } -} \ No newline at end of file +} + diff --git a/csharp/Lib/StatusApi/Connections/IDcConnection.cs b/csharp/Lib/StatusApi/Connections/IDcConnection.cs index 04d83289f..f891bbf6e 100644 --- a/csharp/Lib/StatusApi/Connections/IDcConnection.cs +++ b/csharp/Lib/StatusApi/Connections/IDcConnection.cs @@ -2,8 +2,8 @@ using InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.StatusApi.Connections; - public interface IDcConnection { DcBus Dc { get; } -} \ No newline at end of file +} + diff --git a/csharp/Lib/StatusApi/Connections/IPvConnection.cs b/csharp/Lib/StatusApi/Connections/IPvConnection.cs index 6dfddccea..29a2d666e 100644 --- a/csharp/Lib/StatusApi/Connections/IPvConnection.cs +++ b/csharp/Lib/StatusApi/Connections/IPvConnection.cs @@ -5,4 +5,9 @@ namespace InnovEnergy.Lib.StatusApi.Connections; public interface IPvConnection { IReadOnlyList Strings { get; } +} + +public interface IPvConnection where T : IReadOnlyList +{ + T Strings { get; } } \ No newline at end of file diff --git a/csharp/Lib/StatusApi/DcDcConverterStatus.cs b/csharp/Lib/StatusApi/DcDcConverterStatus.cs deleted file mode 100644 index 9a7e1c2d1..000000000 --- a/csharp/Lib/StatusApi/DcDcConverterStatus.cs +++ /dev/null @@ -1,14 +0,0 @@ -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record DcDcConverterStatus -{ - public DcBus Left { get; init; } - public DcBus Right { get; init; } -} - - - \ No newline at end of file diff --git a/csharp/Lib/StatusApi/DeviceTypes/IAc1Meter.cs b/csharp/Lib/StatusApi/DeviceTypes/IAc1Meter.cs new file mode 100644 index 000000000..76c44e55c --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IAc1Meter.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IAc1Meter : IAc1Connection +{ +} diff --git a/csharp/Lib/StatusApi/DeviceTypes/IAc3Meter.cs b/csharp/Lib/StatusApi/DeviceTypes/IAc3Meter.cs new file mode 100644 index 000000000..58a1c689c --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IAc3Meter.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IAc3Meter : IAc3Connection +{ +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IAcDc1.cs b/csharp/Lib/StatusApi/DeviceTypes/IAcDc1.cs new file mode 100644 index 000000000..9ed14c27b --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IAcDc1.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IAcDc1 : IAc1Connection, IDcConnection +{ +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IAcDc3.cs b/csharp/Lib/StatusApi/DeviceTypes/IAcDc3.cs new file mode 100644 index 000000000..490f0b80e --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IAcDc3.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.StatusApi.Connections; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IAcDc3 : IAc3Connection, IDcConnection +{ +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IBattery.cs b/csharp/Lib/StatusApi/DeviceTypes/IBattery.cs new file mode 100644 index 000000000..b40f1a711 --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IBattery.cs @@ -0,0 +1,11 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IBattery : IDcConnection +{ + Percent Soc { get; } +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IDcDc.cs b/csharp/Lib/StatusApi/DeviceTypes/IDcDc.cs new file mode 100644 index 000000000..9ff3ec702 --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IDcDc.cs @@ -0,0 +1,12 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +#pragma warning disable CS8618 + +public interface IDcDc +{ + DcBus DcLeft { get; } + DcBus DcRight { get; } +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IMppt.cs b/csharp/Lib/StatusApi/DeviceTypes/IMppt.cs new file mode 100644 index 000000000..70e208ae0 --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IMppt.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IMppt : IDcConnection, IPvConnection +{ +} + diff --git a/csharp/Lib/StatusApi/DeviceTypes/IPvAc1.cs b/csharp/Lib/StatusApi/DeviceTypes/IPvAc1.cs new file mode 100644 index 000000000..fc04322da --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IPvAc1.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IPvAc1 : IAc1Connection, IPvConnection +{ +} diff --git a/csharp/Lib/StatusApi/DeviceTypes/IPvAc3.cs b/csharp/Lib/StatusApi/DeviceTypes/IPvAc3.cs new file mode 100644 index 000000000..7d45ce989 --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceTypes/IPvAc3.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.DeviceTypes; + +public interface IPvAc3 : IAc3Connection, IPvConnection +{ +} + diff --git a/csharp/Lib/StatusApi/IDeviceRecord.cs b/csharp/Lib/StatusApi/IDeviceRecord.cs new file mode 100644 index 000000000..ea6a25f1b --- /dev/null +++ b/csharp/Lib/StatusApi/IDeviceRecord.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.Lib.StatusApi; + +public interface IDeviceRecord +{ + S Status { get; } + C Control { get; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/MpptStatus.cs b/csharp/Lib/StatusApi/MpptStatus.cs deleted file mode 100644 index c8b52cb98..000000000 --- a/csharp/Lib/StatusApi/MpptStatus.cs +++ /dev/null @@ -1,14 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record MpptStatus : IDcConnection, IPvConnection -{ - public DcBus Dc { get; init; } - public IReadOnlyList Strings { get; init; } -} - - \ No newline at end of file diff --git a/csharp/Lib/StatusApi/PowerMeterStatus.cs b/csharp/Lib/StatusApi/PowerMeterStatus.cs deleted file mode 100644 index 5c10f1ded..000000000 --- a/csharp/Lib/StatusApi/PowerMeterStatus.cs +++ /dev/null @@ -1,11 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record PowerMeterStatus : IAc3Connection -{ - public Ac3Bus Ac { get; init; } -} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs b/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs deleted file mode 100644 index 247831ffc..000000000 --- a/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs +++ /dev/null @@ -1,13 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - - -#pragma warning disable CS8618 - -public record SinglePhaseInverterStatus : IAc1Connection, IDcConnection -{ - public Ac1Bus Ac { get; init; } - public DcBus Dc { get; init; } -} diff --git a/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs b/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs deleted file mode 100644 index 6a47bb521..000000000 --- a/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record SinglePhasePvInverterStatus : IAc1Connection, IPvConnection -{ - public Ac1Bus Ac { get; init; } - public IReadOnlyList Strings { get; init; } -} diff --git a/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs b/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs deleted file mode 100644 index b33618c97..000000000 --- a/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs +++ /dev/null @@ -1,13 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record ThreePhaseInverterStatus : IAc3Connection, IDcConnection -{ - public Ac3Bus Ac { get; init; } - public DcBus Dc { get; init; } -} - \ No newline at end of file diff --git a/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs b/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs deleted file mode 100644 index 94c31980d..000000000 --- a/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.Units.Composite; - -namespace InnovEnergy.Lib.StatusApi; - -#pragma warning disable CS8618 - -public record ThreePhasePvInverterStatus : IAc3Connection, IPvConnection -{ - public Ac3Bus Ac { get; init; } - public IReadOnlyList Strings { get; init; } -} diff --git a/csharp/Lib/Units/Angle.cs b/csharp/Lib/Units/Angle.cs index 87a3ed2b4..cdcd1bb50 100644 --- a/csharp/Lib/Units/Angle.cs +++ b/csharp/Lib/Units/Angle.cs @@ -1,25 +1,24 @@ -using DecimalMath; -using InnovEnergy.Lib.Units.Generator; -using InnovEnergy.Lib.Utils; +using static System.Math; namespace InnovEnergy.Lib.Units; -[Generate] -public readonly partial struct Angle +public sealed class Angle : Unit { - public static String Unit => "rad"; - public static String Symbol => "∠"; + public override String Symbol => "rad"; - public static readonly Angle Pi = new Angle(DecimalEx.Pi); - - - public Angle(Decimal value) + public Angle(Double value): base(Normalize(value)) { - var modulo = value.Modulo(DecimalEx.TwoPi); - - Value = modulo > DecimalEx.Pi - ? modulo - DecimalEx.TwoPi - : modulo; } - + + private static Double Normalize(Double value) + { + var modulo = value % Tau; // tau is 2pi + + return modulo > PI + ? modulo - Tau + : modulo; + } + + public static implicit operator Angle(Double d) => new Angle(d); + public static implicit operator Double(Angle d) => d.Value; } \ No newline at end of file diff --git a/csharp/Lib/Units/Angle.generated.cs b/csharp/Lib/Units/Angle.generated.cs deleted file mode 100644 index ccc8722f8..000000000 --- a/csharp/Lib/Units/Angle.generated.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Generate - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = Angle; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(AngleConverter))] -public readonly partial struct Angle -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class AngleConverter : JsonConverter -{ - public override Angle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Angle(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, Angle value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/ApparentPower.cs b/csharp/Lib/Units/ApparentPower.cs deleted file mode 100644 index b2a5bba6c..000000000 --- a/csharp/Lib/Units/ApparentPower.cs +++ /dev/null @@ -1,16 +0,0 @@ -using InnovEnergy.Lib.Units.Generator; - -namespace InnovEnergy.Lib.Units; - -[Generate] -public readonly partial struct ApparentPower -{ - public static String Unit => "VA"; - public static String Symbol => "S"; - - public ApparentPower(Decimal value) - { - if (value < 0) throw new ArgumentException("Apparent power cannot be negative", nameof(value)); - Value = value; - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/ApparentPower.generated.cs b/csharp/Lib/Units/ApparentPower.generated.cs deleted file mode 100644 index 68d418277..000000000 --- a/csharp/Lib/Units/ApparentPower.generated.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Generate - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = ApparentPower; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(ApparentPowerConverter))] -public readonly partial struct ApparentPower -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class ApparentPowerConverter : JsonConverter -{ - public override ApparentPower Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new ApparentPower(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, ApparentPower value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/Ac1Bus.cs b/csharp/Lib/Units/Composite/Ac1Bus.cs index b968d72e9..abdc15110 100644 --- a/csharp/Lib/Units/Composite/Ac1Bus.cs +++ b/csharp/Lib/Units/Composite/Ac1Bus.cs @@ -1,27 +1,25 @@ -using System.Diagnostics.CodeAnalysis; - namespace InnovEnergy.Lib.Units.Composite; -public record Ac1Bus : AcPhase +public sealed class Ac1Bus { - public Frequency Frequency { get; init; } - - // [SuppressMessage("ReSharper", "RedundantCast")] - // public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right) - // { - // var f = left.Frequency | right.Frequency; - // var p = (AcPhase)left | (AcPhase)right; - // - // return new Ac1Bus - // { - // Frequency = f, - // Current = p.Current, - // Voltage = p.Voltage, - // Phi = p.Phi - // }; - // } - -} + private Ac1Bus() + {} + public Voltage Voltage { get; private init; } = null!; + public Current Current { get; private init; } = null!; + public AcPower Power { get; private init; } = null!; + public Frequency Frequency { get; private init; } = null!; + public static Ac1Bus FromVoltageCurrentFrequencyPhi(Double voltageRms, + Double currentRms, + Double frequency, + Double phi) => new() + { + Frequency = frequency, + Current = currentRms, + Voltage = voltageRms, + Power = AcPower.FromVoltageCurrentPhi(voltageRms, currentRms, phi) + }; + public static Ac1Bus Null => FromVoltageCurrentFrequencyPhi(0, 0, 0, 0); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/Ac3Bus.cs b/csharp/Lib/Units/Composite/Ac3Bus.cs index bdd505a07..884fcc690 100644 --- a/csharp/Lib/Units/Composite/Ac3Bus.cs +++ b/csharp/Lib/Units/Composite/Ac3Bus.cs @@ -1,19 +1,28 @@ -using static DecimalMath.DecimalEx; namespace InnovEnergy.Lib.Units.Composite; -#pragma warning disable CS8618 - -public record Ac3Bus +public sealed class Ac3Bus { - public AcPhase L1 { get; init; } - public AcPhase L2 { get; init; } - public AcPhase L3 { get; init; } - public Frequency Frequency { get; init; } - - public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower; - public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower; - public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower; - public Angle Phi => ATan2(ReactivePower, ActivePower); -} \ No newline at end of file + private Ac3Bus() {} + + public AcPhase L1 { get; private init; } = null!; + public AcPhase L2 { get; private init; } = null!; + public AcPhase L3 { get; private init; } = null!; + public AcPower Power { get; private init; } = null!; + public Frequency Frequency { get; private init; } = null!; + + public static Ac3Bus FromPhasesAndFrequency(AcPhase l1, + AcPhase l2, + AcPhase l3, + Frequency frequency) => new() + { + L1 = l1, + L2 = l2, + L3 = l3, + Power = AcPower.SumOf(l1.Power, l2.Power, l3.Power), + Frequency = frequency, + }; + + public static Ac3Bus Null => FromPhasesAndFrequency(AcPhase.Null, AcPhase.Null, AcPhase.Null, 0); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/AcPhase.cs b/csharp/Lib/Units/Composite/AcPhase.cs index d57b68aa8..acacd1343 100644 --- a/csharp/Lib/Units/Composite/AcPhase.cs +++ b/csharp/Lib/Units/Composite/AcPhase.cs @@ -1,66 +1,48 @@ -using static DecimalMath.DecimalEx; +using InnovEnergy.Lib.Units.Power; namespace InnovEnergy.Lib.Units.Composite; -public record AcPhase : IBus +public sealed class AcPhase { - private readonly Voltage _Voltage; - public Voltage Voltage + private AcPhase(){} + + public Voltage Voltage { get; private init; } = null!; + public Current Current { get; private init; } = null!; + public AcPower Power { get; private init; } = null!; + + public static AcPhase FromVoltageCurrentPhi(Voltage voltageRms, + Current currentRms, + Angle phi) => new() { - get => _Voltage; - init => _Voltage = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative"); - } - - private readonly Current _Current; - public Current Current + Current = currentRms, + Voltage = voltageRms, + Power = AcPower.FromVoltageCurrentPhi(voltageRms, currentRms, phi) + }; + + public static AcPhase FromVoltageCurrentActiveReactive(Voltage voltageRms, + Current currentRms, + ActivePower activePower, + ReactivePower reactivePower) => new() { - get => _Current; - init => _Current = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative"); - } + Current = currentRms, + Voltage = voltageRms, + Power = AcPower.FromActiveReactive(activePower, reactivePower) + }; - public Angle Phi { get; init; } - - public ApparentPower ApparentPower => Voltage.Value * Current.Value ; - public Power ActivePower => ApparentPower.Value * PowerFactor; - public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi); - public Decimal PowerFactor => Cos(Phi); - - - // public static AcPhase operator |(AcPhase left, AcPhase right) - // { - // // the Voltages of two phases are expected to be in phase and equal - // - // var v = left.Voltage | right.Voltage; - // - // // currents (RMS) can be different and out of phase - // // https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/ - // - // // IF - // // left(t) = ILeft sin(ωt) - // // right(t) = IRight sin(ωt + φ). - // // sum(t) = left(t) + right(t) = ISum sin(ωt + ψ). - // - // // THEN - // // ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ). - // // C = IRight * sin(φ) / sin(ψ). - // - // // in this calculation left(t) has zero phase shift. - // // we can shift both waves by -left.Phi, so - // // φ := right.phi - left.phi - // - // - // var phi = right.Phi - left.Phi; - // var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi)); - // var iSum = right.Current * Sin(phi) / Sin(phiSum); - // - // return new AcPhase - // { - // Voltage = v, - // Current = iSum, - // Phi = phiSum - // }; - // } + public static AcPhase FromVoltageCurrentActiveReactiveApparent(Voltage voltageRms, + Current currentRms, + ActivePower activePower, + ReactivePower reactivePower, + ApparentPower apparentPower) => new() + { + Current = currentRms, + Voltage = voltageRms, + Power = AcPower.FromActiveReactiveApparent(activePower, reactivePower, apparentPower) + }; + public static AcPhase Null => FromVoltageCurrentPhi(0, 0, 0); + + } \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/AcPower.cs b/csharp/Lib/Units/Composite/AcPower.cs new file mode 100644 index 000000000..e9de3a735 --- /dev/null +++ b/csharp/Lib/Units/Composite/AcPower.cs @@ -0,0 +1,106 @@ +using InnovEnergy.Lib.Units.Power; +using static System.Math; + +namespace InnovEnergy.Lib.Units.Composite; + + +public sealed class AcPower +{ + private AcPower(){} + + public ApparentPower Apparent { get; private init; } = null!; + public ActivePower Active { get; private init; } = null!; + public ReactivePower Reactive { get; private init; } = null!; + public Angle Phi { get; private init; } = null!; + public Double CosPhi { get; private init; } + + public static AcPower FromActiveReactiveApparent(ActivePower activePower, ReactivePower reactivePower, ApparentPower apparentPower) + { + var q = reactivePower.Value; + var p = activePower.Value; + var s = apparentPower.Value; + var phi = Atan2(q, p); + + return new AcPower + { + Active = p, + Reactive = q, + Apparent = s, + Phi = phi, + CosPhi = Cos(phi), + }; + } + + public static AcPower FromActiveReactive(ActivePower activePower, ReactivePower reactivePower) + { + var q = reactivePower.Value; + var p = activePower.Value; + var s = Sqrt(p * p + q * q); + var phi = Atan2(q, p); + + return new AcPower + { + Active = p, + Reactive = q, + Apparent = s, + Phi = phi, + CosPhi = Cos(phi), + }; + } + + public static AcPower FromVoltageCurrentPhi(Voltage voltageRms, Current currentRms, Angle phi) + { + if (voltageRms < 0) throw new ArgumentException("RMS value cannot be negative", nameof(voltageRms)); + if (currentRms < 0) throw new ArgumentException("RMS value cannot be negative", nameof(currentRms)); + + var cosPhi = Cos(phi.Value); + var apparent = voltageRms.Value * currentRms.Value; + + return new AcPower + { + Apparent = apparent, + Active = apparent * cosPhi, + Reactive = apparent * Sin(phi.Value), + Phi = phi, + CosPhi = cosPhi + }; + } + + public static AcPower SumOf(params AcPower[] phases) + { + var p = phases.Sum(l => l.Active.Value); + var q = phases.Sum(l => l.Reactive.Value); + var s = Sqrt(p * p + q * q); + + Angle phi = Atan2(q, p); + + return new AcPower + { + Apparent = s, + Active = p, + Reactive = q, + Phi = phi, + CosPhi = Cos(phi.Value) + }; + } + + public static AcPower Null => FromVoltageCurrentPhi(0, 0, 0); + + public static AcPower operator +(AcPower left, AcPower right) => FromActiveReactive + ( + left.Active + right.Active, + left.Reactive + right.Reactive + ); + + public static AcPower operator -(AcPower left, AcPower right) => FromActiveReactive + ( + left.Active - right.Active, + left.Reactive - right.Reactive + ); + + public static AcPower operator -(AcPower p) => FromActiveReactive(-p.Active, p.Reactive); + + + + public override String ToString() => Active.ToString(); // TODO: show all +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/DcBus.cs b/csharp/Lib/Units/Composite/DcBus.cs index 8fbdd9802..c75de28ed 100644 --- a/csharp/Lib/Units/Composite/DcBus.cs +++ b/csharp/Lib/Units/Composite/DcBus.cs @@ -1,9 +1,21 @@ +using InnovEnergy.Lib.Units.Power; + namespace InnovEnergy.Lib.Units.Composite; -public record DcBus : IBus +public sealed class DcBus { - public Voltage Voltage { get; init; } - public Current Current { get; init; } - - public Power Power => Current * Voltage; + private DcBus() {} + + public Voltage Voltage { get; private init; } = null!; + public Current Current { get; private init; } = null!; + public ActivePower Power { get; private init; } = null!; + + public static DcBus FromVoltageCurrent(Voltage voltage, Current current) => new() + { + Voltage = voltage, + Current = current, + Power = current.Value * voltage.Value, + }; + + public static DcBus Null => FromVoltageCurrent(0, 0); } \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/IBus.cs b/csharp/Lib/Units/Composite/IBus.cs deleted file mode 100644 index 8b9694972..000000000 --- a/csharp/Lib/Units/Composite/IBus.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace InnovEnergy.Lib.Units.Composite; - -[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")] - -public interface IBus -{ - public Voltage Voltage { get; } - public Current Current { get; } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Current.cs b/csharp/Lib/Units/Current.cs index 07b473c24..95c92d699 100644 --- a/csharp/Lib/Units/Current.cs +++ b/csharp/Lib/Units/Current.cs @@ -1,19 +1,13 @@ -using InnovEnergy.Lib.Units.Generator; - namespace InnovEnergy.Lib.Units; -[Generate] -public readonly partial struct Current +public sealed class Current : Unit { - public static String Unit => "A"; - public static String Symbol => "I"; + public override String Symbol => "A"; - public Current(Decimal value) => Value = value; + public Current(Double value) : base(value) + {} + + public static implicit operator Current(Double d) => new Current(d); + public static implicit operator Double(Current d) => d.Value; - // P=UI - public static Power operator *(Current current, Voltage voltage) => new Power(current.Value * voltage.Value); - - // U=RI - public static Voltage operator *(Current current, Resistance resistance) => new Voltage(resistance.Value* current.Value); - } \ No newline at end of file diff --git a/csharp/Lib/Units/Current.generated.cs b/csharp/Lib/Units/Current.generated.cs deleted file mode 100644 index b8b19963c..000000000 --- a/csharp/Lib/Units/Current.generated.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Generate - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = Current; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(CurrentConverter))] -public readonly partial struct Current -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class CurrentConverter : JsonConverter -{ - public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Current(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Energy.cs b/csharp/Lib/Units/Energy.cs index ab05ba6c4..0d5412244 100644 --- a/csharp/Lib/Units/Energy.cs +++ b/csharp/Lib/Units/Energy.cs @@ -1,16 +1,13 @@ -using InnovEnergy.Lib.Time.Unix; -using InnovEnergy.Lib.Units.Generator; - namespace InnovEnergy.Lib.Units; -[Generate] -public readonly partial struct Energy +public sealed class Energy : Unit { - public static String Unit => "kWh"; - public static String Symbol => "E"; + public override String Symbol => "kWh"; - public Energy(Decimal value) => Value = value; + public Energy(Double value) : base(value) + { + } - public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value * 1000m / (Decimal) timeSpan.TotalHours ; - public static Power operator /(Energy energy, UnixTimeSpan timeSpan) => energy.Value * 3_600_000m / timeSpan.Ticks; + public static implicit operator Energy(Double d) => new Energy(d); + public static implicit operator Double(Energy d) => d.Value; } \ No newline at end of file diff --git a/csharp/Lib/Units/Energy.generated.cs b/csharp/Lib/Units/Energy.generated.cs deleted file mode 100644 index dabe6ed16..000000000 --- a/csharp/Lib/Units/Energy.generated.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Generate - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = Energy; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(EnergyConverter))] -public readonly partial struct Energy -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class EnergyConverter : JsonConverter -{ - public override Energy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Energy(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, Energy value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Frequency.cs b/csharp/Lib/Units/Frequency.cs index f34b83334..06671577a 100644 --- a/csharp/Lib/Units/Frequency.cs +++ b/csharp/Lib/Units/Frequency.cs @@ -1,19 +1,15 @@ -using InnovEnergy.Lib.Units.Generator; - namespace InnovEnergy.Lib.Units; - -[Generate] -public readonly partial struct Frequency +public sealed class Frequency : Unit { - public static String Unit => "Hz"; - public static String Symbol => "f"; + public override String Symbol => "Hz"; - public Frequency(Decimal value) + public Frequency(Double value) : base(value) { if (value < 0) throw new ArgumentException(nameof(Frequency) + " cannot be negative", nameof(value)); - - Value = value; } + + public static implicit operator Frequency(Double d) => new Frequency(d); + public static implicit operator Double(Frequency d) => d.Value; } \ No newline at end of file diff --git a/csharp/Lib/Units/Frequency.generated.cs b/csharp/Lib/Units/Frequency.generated.cs deleted file mode 100644 index 6bdfb8ded..000000000 --- a/csharp/Lib/Units/Frequency.generated.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Generate - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = Frequency; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(FrequencyConverter))] -public readonly partial struct Frequency -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class FrequencyConverter : JsonConverter -{ - public override Frequency Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Frequency(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, Frequency value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Average.cs b/csharp/Lib/Units/Generator/Average.cs new file mode 100644 index 000000000..6ec0c6ad5 --- /dev/null +++ b/csharp/Lib/Units/Generator/Average.cs @@ -0,0 +1,11 @@ + +// ReSharper disable once CheckNamespace +namespace InnovEnergy.Lib.Units; + +public readonly struct Average +{ + public Average(Double value) => throw new NotImplementedException(); + public Double Value => throw new NotImplementedException(); + + public static Average operator |(Average left, Average right) => new((left.Value + right.Value) / 2); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/GenerateAttribute.cs b/csharp/Lib/Units/Generator/GenerateAttribute.cs deleted file mode 100644 index 12337cf50..000000000 --- a/csharp/Lib/Units/Generator/GenerateAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Text.Json.Serialization; - -namespace InnovEnergy.Lib.Units.Generator; - -internal class GenerateAttribute : Attribute -{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Operators.cs b/csharp/Lib/Units/Generator/Operators.cs new file mode 100644 index 000000000..1c28c9427 --- /dev/null +++ b/csharp/Lib/Units/Generator/Operators.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Utils; + +// ReSharper disable once CheckNamespace +namespace InnovEnergy.Lib.Units; + +[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] +public readonly struct Operators +{ + public static String Unit => throw new NotImplementedException(); + public static String Symbol => throw new NotImplementedException(); + + #if !HAS_CONSTRUCTOR + public Operators(Double value) => Value = value; + #endif + + public Double Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static Operators operator *(Double scalar, Operators t) => new Operators(scalar * t.Value); + public static Operators operator *(Operators t, Double scalar) => new Operators(scalar * t.Value); + public static Operators operator /(Operators t, Double scalar) => new Operators(t.Value / scalar); + + // addition + + public static Operators operator +(Operators left, Operators right) => new Operators(left.Value + right.Value); + public static Operators operator -(Operators left, Operators right) => new Operators(left.Value - right.Value); + public static Operators operator -(Operators t) => new Operators(-t.Value); + + // compare + + public static Boolean operator ==(Operators left, Operators right) => left.Value == right.Value; + public static Boolean operator !=(Operators left, Operators right) => left.Value != right.Value; + public static Boolean operator > (Operators left, Operators right) => left.Value > right.Value; + public static Boolean operator < (Operators left, Operators right) => left.Value < right.Value; + public static Boolean operator >=(Operators left, Operators right) => left.Value >= right.Value; + public static Boolean operator <=(Operators left, Operators right) => left.Value <= right.Value; + + // conversion + + public static implicit operator Operators(Double d) => new Operators(d); + + // equality + + public Boolean Equals(Operators other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is Operators other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Parallel.cs b/csharp/Lib/Units/Generator/Parallel.cs new file mode 100644 index 000000000..942cb416e --- /dev/null +++ b/csharp/Lib/Units/Generator/Parallel.cs @@ -0,0 +1,12 @@ +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace InnovEnergy.Lib.Units; + +public readonly struct Parallel +{ + public Parallel(Double value) => throw new NotImplementedException(); + public Double Value => throw new NotImplementedException(); + + public static Parallel operator |(Parallel left, Parallel right) => new((left.Value * right.Value) / (left.Value + right.Value)); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Sum.cs b/csharp/Lib/Units/Generator/Sum.cs new file mode 100644 index 000000000..30b06abff --- /dev/null +++ b/csharp/Lib/Units/Generator/Sum.cs @@ -0,0 +1,11 @@ + +// ReSharper disable once CheckNamespace +namespace InnovEnergy.Lib.Units; + +public readonly struct Sum +{ + public Sum(Double value) => throw new NotImplementedException(); + public Double Value => throw new NotImplementedException(); + + public static Sum operator |(Sum left, Sum right) => new(left.Value + right.Value); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Template.txt b/csharp/Lib/Units/Generator/Template.txt deleted file mode 100644 index c23e0820a..000000000 --- a/csharp/Lib/Units/Generator/Template.txt +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define AggregationType - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; -using System.CodeDom.Compiler; - -namespace InnovEnergy.Lib.Units; - -using T = Template; - -[GeneratedCode("generate.sh", "1")] -[JsonConverter(typeof(TemplateConverter))] -public readonly partial struct Template -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // addition - - public static T operator +(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T left, T right) => new T(left.Value - right.Value); - public static T operator -(T t) => new T(-t.Value); - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class TemplateConverter : JsonConverter