diff --git a/csharp/App/DeligreenBatteryCommunication/Program.cs b/csharp/App/DeligreenBatteryCommunication/Program.cs index b8ccf02c7..ac24c6f47 100644 --- a/csharp/App/DeligreenBatteryCommunication/Program.cs +++ b/csharp/App/DeligreenBatteryCommunication/Program.cs @@ -55,7 +55,7 @@ internal static class Program Console.WriteLine("Time used for reading all batteries:" + (stopTime - startTime)); Console.WriteLine("Average SOC " + batteriesRecord?.Soc); - /* Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]); + Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]); Console.WriteLine("Cell Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[1]); Console.WriteLine("Cell Temperature Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[0]); @@ -68,7 +68,7 @@ internal static class Program Console.WriteLine("Battery 2 Current Alarm : " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.CurrentAlarm); Console.WriteLine("TotalVoltage Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.TotalVoltageAlarm); - Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm);*/ + Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm); Console.WriteLine("CellVoltageDropoutFault : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent1.CellVoltageDropoutFault); Console.WriteLine("HighVoltageAlarmForTotalVoltage : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent2.HighVoltageAlarmForTotalVoltage); @@ -85,6 +85,18 @@ internal static class Program Console.WriteLine("ManualChargingWaiting : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.OnOffState.ChargeSwitchState); Console.WriteLine("CurrentCalibrationNotPerformed : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.SystemState.Charge); + Console.WriteLine("Power : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Power); + Console.WriteLine("BusCurrent : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BusCurrent); + Console.WriteLine("BusVoltage : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BusVoltage); + Console.WriteLine("BatteryCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BatteryCapacity); + Console.WriteLine("NumberOfCycles : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.NumberOfCycles); + Console.WriteLine("FwVersion : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.FwVersion); + Console.WriteLine("Soc : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Soc); + Console.WriteLine("Soh : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Soh); + Console.WriteLine("RatedCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.RatedCapacity); + Console.WriteLine("ResidualCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.ResidualCapacity); + + // Wait for 2 seconds before the next reading await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds) } diff --git a/csharp/App/GrowattCommunication/DataTypes/Configuration.cs b/csharp/App/GrowattCommunication/DataTypes/Configuration.cs index c0222e448..f678eaa68 100644 --- a/csharp/App/GrowattCommunication/DataTypes/Configuration.cs +++ b/csharp/App/GrowattCommunication/DataTypes/Configuration.cs @@ -10,6 +10,6 @@ public class Configuration public Double MaximumChargingCurrent { get; set; } public EssMode OperatingPriority { get; set; } public Int16 BatteriesCount { get; set; } - + public Boolean ControlPermission { get; set; } } diff --git a/csharp/App/GrowattCommunication/MiddlewareClasses/MiddlewareAgent.cs b/csharp/App/GrowattCommunication/MiddlewareClasses/MiddlewareAgent.cs index c044bb1b8..279e39052 100644 --- a/csharp/App/GrowattCommunication/MiddlewareClasses/MiddlewareAgent.cs +++ b/csharp/App/GrowattCommunication/MiddlewareClasses/MiddlewareAgent.cs @@ -68,7 +68,8 @@ public static class MiddlewareAgent if (config != null) { Console.WriteLine($"Received a configuration message: " + - "MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + config.MaximumChargingCurrent + config.MaximumDischargingCurrent); + "MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + + "MaximumChargingCurrent is " + config.MaximumChargingCurrent + "MaximumDischargingCurrent " + config.MaximumDischargingCurrent + " Control permission is" + config.ControlPermission ); // Send the reply to the sender's endpoint _udpListener.Send(replyData, replyData.Length, serverEndpoint); diff --git a/csharp/App/GrowattCommunication/MiddlewareClasses/RabbitMQManager.cs b/csharp/App/GrowattCommunication/MiddlewareClasses/RabbitMQManager.cs index 2207ba0af..01c5ff692 100644 --- a/csharp/App/GrowattCommunication/MiddlewareClasses/RabbitMQManager.cs +++ b/csharp/App/GrowattCommunication/MiddlewareClasses/RabbitMQManager.cs @@ -56,6 +56,4 @@ public static class RabbitMqManager Console.WriteLine($"Producer sent message: {message}"); } - - } \ No newline at end of file diff --git a/csharp/App/GrowattCommunication/Program.cs b/csharp/App/GrowattCommunication/Program.cs index 939373298..f643a613a 100644 --- a/csharp/App/GrowattCommunication/Program.cs +++ b/csharp/App/GrowattCommunication/Program.cs @@ -32,13 +32,13 @@ namespace InnovEnergy.App.GrowattCommunication; public static class Program { - private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5); + private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(6); private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file private static UInt16 _fileCounter = 0; // private static Channel _growattChannel; - private const String SwVersionNumber =" V1.00.310725 beta"; + private static readonly String SwVersionNumber = " V1.00." + DateTime.Today; private const String VpnServerIp = "10.2.0.11"; private static Boolean _subscribedToQueue = false; private static Boolean _subscribeToQueueForTheFirstTime = false; @@ -142,14 +142,14 @@ public static class Program statusrecord.InverterRecord.EnableEmsCommunicationFailureTime.WriteLine(" = EnableEmsCommunicationFailureTime"); statusrecord.InverterRecord.EnableCommand.WriteLine(" = EnableCommand"); - statusrecord.InverterRecord.ControlPermession.WriteLine(" = ControlPermession"); + statusrecord.InverterRecord.ControlPermission.WriteLine(" = ControlPermession"); statusrecord.InverterRecord.GridPower.WriteLine(" MeterPower"); statusrecord.InverterRecord.ConsumptionPower.WriteLine(" ConsumptionPower"); statusrecord.InverterRecord.ExportedPowerToGridMeter.WriteLine(" ExportedPowerToGrid"); statusrecord.InverterRecord.ImportedPowerFromGrid.WriteLine(" ImportedPowerFromGrid"); statusrecord.InverterRecord.InverterActivePower.WriteLine(" InverterActivePower"); - statusrecord.InverterRecord.MaxSoc.WriteLine(" BatteryChargeCutoffVoltage "); //30409 we set power here - statusrecord.InverterRecord.MinSoc.WriteLine(" BatteryDishargeCutoffVoltage "); //30409 we set power here + statusrecord.InverterRecord.MaxSoc.WriteLine(" MaxSoc "); //30409 we set power here + statusrecord.InverterRecord.MinSoc.WriteLine(" MinSoc "); //30409 we set power here statusrecord.InverterRecord.PowerFactor.WriteLine(" = PowerFactor"); statusrecord.InverterRecord.Battery1Soc.WriteLine(" SOC"); statusrecord.InverterRecord.Battery1Power.WriteLine(" Battery Power"); @@ -157,7 +157,6 @@ public static class Program statusrecord.InverterRecord.Battery1Voltage.WriteLine(" Battery Voltage"); statusrecord.InverterRecord.BatteryMaxChargingCurrent.WriteLine(" BatteryMaxChargeCurrent "); //30409 we set power here statusrecord.InverterRecord.BatteryMaxDischargingCurrent.WriteLine(" BatteryMaxDischargeCurrent "); //30409 we set power here - statusrecord.InverterRecord.MinSoc.WriteLine(" MinSoc "); //30409 we set power here statusrecord.InverterRecord.ChargeCutoffSocVoltage.WriteLine(" ChargeCutoffSoc "); //30409 we set power here statusrecord.InverterRecord.PvPower.WriteLine(" Pv Power "); //30409 we set power here @@ -206,6 +205,7 @@ public static class Program status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent; status.Config.OperatingPriority = config.OperatingPriority; status.Config.BatteriesCount = config.BatteriesCount; + status.Config.ControlPermission = config.ControlPermission; } private static String EssModeControl(StatusRecord? statusrecord) @@ -247,11 +247,10 @@ public static class Program var alarmList = new List(); var warningList = new List(); - if (record.InverterRecord.SystemOperatingMode == GrowattSystemStatus.Fault) - { - if (record.InverterRecord.FaultMainCode != 0) + + if (record.InverterRecord.WarningMainCode != 0) { - alarmList.Add(new AlarmOrWarning + warningList.Add(new AlarmOrWarning { Date = DateTime.Now.ToString("yyyy-MM-dd"), Time = DateTime.Now.ToString("HH:mm:ss"), @@ -260,9 +259,9 @@ public static class Program }); } - if (record.InverterRecord.WarningMainCode != 0) + if (record.InverterRecord.FaultMainCode != 0) { - warningList.Add(new AlarmOrWarning + alarmList.Add(new AlarmOrWarning { Date = DateTime.Now.ToString("yyyy-MM-dd"), Time = DateTime.Now.ToString("HH:mm:ss"), @@ -270,7 +269,7 @@ public static class Program Description = record.InverterRecord.FaultMainCode.ToString(), //to add the sub code }); } - } + _sodiohomeAlarmState = warningList.Any() ? SodistoreAlarmState.Orange @@ -376,7 +375,7 @@ public static class Program _lastHeartbeatTime = DateTime.Now; } - if (s3Bucket != null) + if (s3Bucket != null) // why this is based on s3 bucket? { RabbitMqManager.InformMiddleware(currentSalimaxState); } @@ -401,9 +400,8 @@ public static class Program st.InverterRecord.EmsCommunicationFailureTime = 20; // 20 sec st.InverterRecord.EnableEmsCommunicationFailureTime = false; st.InverterRecord.EnableCommand = true; - st.InverterRecord.ControlPermession = true; - st.InverterRecord.MaxSoc = 60; //st.Config.BatteryChargeCutoffVoltage; - st.InverterRecord.MinSoc = 25; //st.Config.BatteryDischargeCutoffVoltage; + st.InverterRecord.ControlPermission = st.Config.ControlPermission;; + st.InverterRecord.MaxSoc = 100; //st.Config.MaxSoc; } private static Dictionary ConvertToModbusRegisters(Object value, String outputType, @@ -645,7 +643,7 @@ public static class Program Console.WriteLine(error); await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); - Heartbit(); + // Heartbit(); return false; } @@ -654,7 +652,7 @@ public static class Program Console.WriteLine( "---------------------------------------- Resending FailedUploadedFiles----------------------------------------"); - Heartbit(); + //Heartbit(); await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files } @@ -667,7 +665,7 @@ public static class Program await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); } - Heartbit(); + //Heartbit(); return false; } } @@ -676,7 +674,7 @@ public static class Program return true; } - private static void Heartbit() + /* private static void Heartbit() { var s3Bucket = Config.Load().S3?.Bucket; var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId); @@ -693,7 +691,7 @@ public static class Program if (s3Bucket != null) RabbitMqManager.InformMiddleware(returnedStatus); } - } + }*/ private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension) { diff --git a/csharp/App/GrowattCommunication/SystemConfig/Config.cs b/csharp/App/GrowattCommunication/SystemConfig/Config.cs index cbb05ae9c..f4360374a 100644 --- a/csharp/App/GrowattCommunication/SystemConfig/Config.cs +++ b/csharp/App/GrowattCommunication/SystemConfig/Config.cs @@ -21,6 +21,8 @@ public class Config //TODO: let IE choose from config files (Json) and connect t public required EssMode OperatingPriority { get; set; } public required Int16 BatteriesCount { get; set; } public required Double ModbusProtcolNumber { get; set; } + public required Boolean ControlPermission { get; set; } + public required S3Config? S3 { get; set; } @@ -36,6 +38,7 @@ public class Config //TODO: let IE choose from config files (Json) and connect t OperatingPriority = EssMode.LoadPriority, BatteriesCount = 0, ModbusProtcolNumber = 1.2, + ControlPermission = false, S3 = new() { Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d", diff --git a/csharp/App/KacoCommunication/DataLogging/LogFileConcatenator.cs b/csharp/App/KacoCommunication/DataLogging/LogFileConcatenator.cs new file mode 100644 index 000000000..4dfc03f7c --- /dev/null +++ b/csharp/App/KacoCommunication/DataLogging/LogFileConcatenator.cs @@ -0,0 +1,33 @@ +using System.Text; + +namespace InnovEnergy.App.KacoCommunication.DataLogging; + +public class LogFileConcatenator +{ + private readonly String _LogDirectory; + + public LogFileConcatenator(String logDirectory = "JsonLogDirectory/") + { + _LogDirectory = logDirectory; + } + + public String ConcatenateFiles(int numberOfFiles) + { + var logFiles = Directory + .GetFiles(_LogDirectory, "log_*.json") + .OrderByDescending(file => file) + .Take(numberOfFiles) + .OrderBy(file => file) + .ToList(); + + var concatenatedContent = new StringBuilder(); + + foreach (var fileContent in logFiles.Select(File.ReadAllText)) + { + concatenatedContent.AppendLine(fileContent); + //concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this + } + + return concatenatedContent.ToString(); + } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/DataLogging/Logfile.cs b/csharp/App/KacoCommunication/DataLogging/Logfile.cs new file mode 100644 index 000000000..2bd43df35 --- /dev/null +++ b/csharp/App/KacoCommunication/DataLogging/Logfile.cs @@ -0,0 +1,48 @@ +using InnovEnergy.Lib.Utils; +using Microsoft.Extensions.Logging; +namespace InnovEnergy.App.KacoCommunication.DataLogging; + +public class CustomLogger : ILogger +{ + private readonly String _LogFilePath; + //private readonly Int64 _maxFileSizeBytes; + private readonly Int32 _MaxLogFileCount; + private Int64 _CurrentFileSizeBytes; + + public CustomLogger(String logFilePath, Int32 maxLogFileCount) + { + _LogFilePath = logFilePath; + _MaxLogFileCount = maxLogFileCount; + _CurrentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0; + } + + public IDisposable? BeginScope(TState state) where TState : notnull => throw new NotImplementedException(); + + public Boolean IsEnabled(LogLevel logLevel) => 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 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()); + } + + var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1; + var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine; + + var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}"); + File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine); + } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/DataLogging/Logger.cs b/csharp/App/KacoCommunication/DataLogging/Logger.cs new file mode 100644 index 000000000..1894e016b --- /dev/null +++ b/csharp/App/KacoCommunication/DataLogging/Logger.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Logging; + +namespace InnovEnergy.App.KacoCommunication.DataLogging; + +public static class Logger +{ + // Specify the maximum log file size in bytes (e.g., 1 MB) + + //private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings + private const Int32 MaxLogFileCount = 5000; // TODO: move to settings + private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings + + // ReSharper disable once InconsistentNaming + private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxLogFileCount); + + public static T LogInfo(this T t) where T : notnull + { + _logger.LogInformation(t.ToString()); // TODO: check warning + return t; + } + + public static T LogDebug(this T t) where T : notnull + { + // _logger.LogDebug(t.ToString()); // TODO: check warning + return t; + } + + public static T LogError(this T t) where T : notnull + { + // _logger.LogError(t.ToString()); // TODO: check warning + return t; + } + + public static T LogWarning(this T t) where T : notnull + { + // _logger.LogWarning(t.ToString()); // TODO: check warning + return t; + } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/DataTypes/AlarmOrWarning.cs b/csharp/App/KacoCommunication/DataTypes/AlarmOrWarning.cs new file mode 100644 index 000000000..d7c1fdb47 --- /dev/null +++ b/csharp/App/KacoCommunication/DataTypes/AlarmOrWarning.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.App.KacoCommunication.DataTypes; + +public class AlarmOrWarning +{ + public String? Date { get; set; } + public String? Time { get; set; } + public String? Description { get; set; } + public String? CreatedBy { get; set; } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/DataTypes/Configuration.cs b/csharp/App/KacoCommunication/DataTypes/Configuration.cs new file mode 100644 index 000000000..01618e249 --- /dev/null +++ b/csharp/App/KacoCommunication/DataTypes/Configuration.cs @@ -0,0 +1,19 @@ + +namespace InnovEnergy.App.KacoCommunication.DataTypes; + +public class Configuration +{ + public Double MinimumSoC { get; set; } + public Double MaximumDischargingCurrent { get; set; } + public Double MaximumChargingCurrent { get; set; } + //public WorkingMode OperatingPriority { get; set; } + public Int16 BatteriesCount { get; set; } + public Int16 ClusterNumber { get; set; } + public Int16 PvNumber { get; set; } + public DateTime StartTimeChargeandDischargeDayandTime { get; set; } + public DateTime StopTimeChargeandDischargeDayandTime { get; set; } + public Single TimeChargeandDischargePower { get; set; } + public Boolean ControlPermission { get; set; } + +} + diff --git a/csharp/App/KacoCommunication/DataTypes/SodiStoreAlarmState.cs b/csharp/App/KacoCommunication/DataTypes/SodiStoreAlarmState.cs new file mode 100644 index 000000000..0e39dea0b --- /dev/null +++ b/csharp/App/KacoCommunication/DataTypes/SodiStoreAlarmState.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.App.KacoCommunication.DataTypes; + +public enum SodistoreAlarmState +{ + Green, + Orange, + Red +} diff --git a/csharp/App/KacoCommunication/DataTypes/StatusMessage.cs b/csharp/App/KacoCommunication/DataTypes/StatusMessage.cs new file mode 100644 index 000000000..ea1bac319 --- /dev/null +++ b/csharp/App/KacoCommunication/DataTypes/StatusMessage.cs @@ -0,0 +1,17 @@ +namespace InnovEnergy.App.KacoCommunication.DataTypes; + +public class StatusMessage +{ + public required Int32 InstallationId { get; set; } + public required Int32 Product { get; set; } + public required SodistoreAlarmState Status { get; set; } + public required MessageType Type { get; set; } + public List? Warnings { get; set; } + public List? Alarms { get; set; } +} + +public enum MessageType +{ + AlarmOrWarning, + Heartbit +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/Devices/DeviceState.cs b/csharp/App/KacoCommunication/Devices/DeviceState.cs new file mode 100644 index 000000000..5fe89e073 --- /dev/null +++ b/csharp/App/KacoCommunication/Devices/DeviceState.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.App.KacoCommunication.Devices; + +public enum DeviceState +{ + Disabled, + Measured, + Computed +} diff --git a/csharp/App/KacoCommunication/Devices/SalimaxDevice.cs b/csharp/App/KacoCommunication/Devices/SalimaxDevice.cs new file mode 100644 index 000000000..242d3ea05 --- /dev/null +++ b/csharp/App/KacoCommunication/Devices/SalimaxDevice.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.Utils.Net; + +namespace InnovEnergy.App.KacoCommunication.Devices; + +public class SalimaxDevice : Ip4Address +{ + public required DeviceState DeviceState { get; init; } +} + diff --git a/csharp/App/KacoCommunication/ESS/StatusRecord.cs b/csharp/App/KacoCommunication/ESS/StatusRecord.cs new file mode 100644 index 000000000..787d95d26 --- /dev/null +++ b/csharp/App/KacoCommunication/ESS/StatusRecord.cs @@ -0,0 +1,23 @@ +using InnovEnergy.App.KacoCommunication.SystemConfig; +using InnovEnergy.Lib.Devices.BatteryDeligreen; +using InnovEnergy.Lib.Devices.Kaco92L3; +using InnovEnergy.Lib.Devices.PLVario2Meter; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +namespace InnovEnergy.App.KacoCommunication.ESS; + +public class StatusRecord +{ + public required KacoRecord? InverterRecord { get; set; } + public required PlVarioMeterRecord? GridMeterRecord { get; set; } + public required DcDcDevicesRecord? DcDc { get; init; } + + public required BatteryDeligreenRecords? BatteryKabinet1 { get; set; } + public required BatteryDeligreenRecords? BatteryKabinet2 { get; set; } + public required BatteryDeligreenRecords? BatteryKabinet3 { get; set; } + + + + public required Config Config { get; set; } +} + diff --git a/csharp/App/KacoCommunication/Flow.cs b/csharp/App/KacoCommunication/Flow.cs new file mode 100644 index 000000000..45c1476b6 --- /dev/null +++ b/csharp/App/KacoCommunication/Flow.cs @@ -0,0 +1,55 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.KacoCommunication; + +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 = "^"; + private static readonly String UnknownArrowChar = "?"; + + public static TextBlock Horizontal(Unit? amount) => Horizontal(amount, 10); + + public static TextBlock Horizontal(Unit? amount, Int32 width) + { + var label = amount?.ToDisplayString() ?? ""; + + var arrowChar = amount switch + { + { Value: < 0 } => LeftArrowChar, + { Value: >= 0 } => RightArrowChar, + _ => UnknownArrowChar, + }; + + //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.AlignCenterHorizontal(label, arrow, ""); + } + + public static TextBlock Vertical(Unit? amount) => Vertical(amount, 4); + + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + [SuppressMessage("ReSharper", "CoVariantArrayConversion")] + public static TextBlock Vertical(Unit? amount, Int32 height) + { + var label = amount?.ToDisplayString() ?? UnknownArrowChar; + var arrowChar = amount switch + { + { Value: < 0 } => UpArrowChar, + { Value: >= 0 } => DownArrowChar, + _ => UnknownArrowChar, + }; + + // var arrowChar = amount is null ? UnknownArrowChar + // : amount.Value < 0 ? UpArrowChar + // : DownArrowChar; + + return TextBlock.AlignCenterHorizontal(arrowChar, arrowChar, label, arrowChar, arrowChar); + } +} diff --git a/csharp/App/KacoCommunication/KacoCommunication.csproj b/csharp/App/KacoCommunication/KacoCommunication.csproj new file mode 100644 index 000000000..cc9b562ed --- /dev/null +++ b/csharp/App/KacoCommunication/KacoCommunication.csproj @@ -0,0 +1,24 @@ + + + + + InnovEnergy.App.KacoCommunication + + + + + + + + + + + + + + + + + + + diff --git a/csharp/App/KacoCommunication/MiddlewareClasses/MiddlewareAgent.cs b/csharp/App/KacoCommunication/MiddlewareClasses/MiddlewareAgent.cs new file mode 100644 index 000000000..d14793ae4 --- /dev/null +++ b/csharp/App/KacoCommunication/MiddlewareClasses/MiddlewareAgent.cs @@ -0,0 +1,91 @@ +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using InnovEnergy.App.KacoCommunication.DataTypes; + +namespace InnovEnergy.App.KacoCommunication.MiddlewareClasses; + +public static class MiddlewareAgent +{ + private static UdpClient _udpListener = null!; + private static IPAddress? _controllerIpAddress; + private static EndPoint? _endPoint; + + public static void InitializeCommunicationToMiddleware() + { + _controllerIpAddress = FindVpnIp(); + if (Equals(IPAddress.None, _controllerIpAddress)) + { + Console.WriteLine("There is no VPN interface, exiting..."); + } + + const Int32 udpPort = 9000; + _endPoint = new IPEndPoint(_controllerIpAddress, udpPort); + + _udpListener = new UdpClient(); + _udpListener.Client.Blocking = false; + _udpListener.Client.Bind(_endPoint); + } + + private static IPAddress FindVpnIp() + { + const String interfaceName = "innovenergy"; + + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (var networkInterface in networkInterfaces) + { + if (networkInterface.Name == interfaceName) + { + var ipProps = networkInterface.GetIPProperties(); + var uniCastIPs = ipProps.UnicastAddresses; + var controllerIpAddress = uniCastIPs[0].Address; + + Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address); + return controllerIpAddress; + } + } + + return IPAddress.None; + } + + public static Configuration? SetConfigurationFile() + { + if (_udpListener.Available > 0) + { + IPEndPoint? serverEndpoint = null; + + var replyMessage = "ACK"; + var replyData = Encoding.UTF8.GetBytes(replyMessage); + + var udpMessage = _udpListener.Receive(ref serverEndpoint); + var message = Encoding.UTF8.GetString(udpMessage); + + var config = JsonSerializer.Deserialize(message); + + if (config != null) + { + Console.WriteLine($"Received a configuration message: " + + "MinimumSoC is " + config.MinimumSoC + "Number of batteries is " + config.BatteriesCount + + "Maximum Charging current is "+ config.MaximumChargingCurrent + "/n" + "Maximum Discharging current is " + config.MaximumDischargingCurrent + + "StartTimeChargeandDischargeDayandTime is" + config.StartTimeChargeandDischargeDayandTime + "StopTimeChargeandDischargeDayandTime is" + config.StopTimeChargeandDischargeDayandTime + + "TimeChargeandDischargePowert is " + config.TimeChargeandDischargePower + " Control permission is" + config.ControlPermission); + + // Send the reply to the sender's endpoint + _udpListener.Send(replyData, replyData.Length, serverEndpoint); + Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}"); + return config; + } + } + + if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint)) + { + Console.WriteLine("UDP address has changed, rebinding..."); + InitializeCommunicationToMiddleware(); + } + return null; + } + +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/MiddlewareClasses/RabbitMqManager.cs b/csharp/App/KacoCommunication/MiddlewareClasses/RabbitMqManager.cs new file mode 100644 index 000000000..ded8b6e1d --- /dev/null +++ b/csharp/App/KacoCommunication/MiddlewareClasses/RabbitMqManager.cs @@ -0,0 +1,64 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.Json; +using InnovEnergy.App.KacoCommunication.DataTypes; +using RabbitMQ.Client; + +namespace InnovEnergy.App.KacoCommunication.MiddlewareClasses; + + +[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")] +public static class RabbitMqManager +{ + public static ConnectionFactory? Factory ; + public static IConnection ? Connection; + public static IModel? Channel; + + public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp) + { + try + { + //_factory = new ConnectionFactory { HostName = VpnServerIp }; + + Factory = new ConnectionFactory + { + HostName = VpnServerIp, + Port = 5672, + VirtualHost = "/", + UserName = "producer", + Password = "b187ceaddb54d5485063ddc1d41af66f", + + }; + + Connection = Factory.CreateConnection(); + Channel = Connection.CreateModel(); + Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null); + + Console.WriteLine("The controller sends its status to the middleware for the first time"); + if (s3Bucket != null) InformMiddleware(currentSalimaxState); + + + } + catch (Exception ex) + { + Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message); + return false; + } + return true; + } + + public static void InformMiddleware(StatusMessage status) + { + var message = JsonSerializer.Serialize(status); + var body = Encoding.UTF8.GetBytes(message); + + Channel.BasicPublish(exchange: string.Empty, + routingKey: "statusQueue", + basicProperties: null, + body: body); + + Console.WriteLine($"Producer sent message: {message}"); + } + + +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/Program.cs b/csharp/App/KacoCommunication/Program.cs new file mode 100644 index 000000000..d17e6d3bc --- /dev/null +++ b/csharp/App/KacoCommunication/Program.cs @@ -0,0 +1,637 @@ +// See https://aka.ms/new-console-template for more information + +using System.IO.Compression; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using System.Text.Json; +using Flurl.Http; +using InnovEnergy.App.KacoCommunication.DataLogging; +using InnovEnergy.App.KacoCommunication.DataTypes; +using InnovEnergy.App.KacoCommunication.Devices; +using InnovEnergy.App.KacoCommunication.ESS; +using InnovEnergy.App.KacoCommunication.MiddlewareClasses; +using InnovEnergy.App.KacoCommunication.SystemConfig; +using InnovEnergy.Lib.Devices.BatteryDeligreen; +using InnovEnergy.Lib.Devices.Kaco92L3; +using InnovEnergy.Lib.Devices.Kaco92L3.DataType; +using InnovEnergy.Lib.Devices.PLVario2Meter; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; +using Newtonsoft.Json; +using Formatting = Newtonsoft.Json.Formatting; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace InnovEnergy.App.KacoCommunication; + +internal static class Program +{ + private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5); + private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file + private static UInt16 _fileCounter = 0; + + + + private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green; + private static SodistoreAlarmState _sodiAlarmState = SodistoreAlarmState.Green; + + + private static readonly IReadOnlyList BatteryNodes; + private static readonly Channel KacoChannel; + private static readonly Channel GridMeterChannel; + private static readonly Channel DcDcChannel; + + private const String Port1Cabinet = "/dev/ttyUSB0"; // move to a config file + private const String Port2Cabinet = "/dev/ttyUSB1"; // move to a config file + private const String Port3Cabinet = "/dev/ttyUSB2"; // move to a config file + + private static readonly String SwVersionNumber = " V1.00." + DateTime.Today; + private const String VpnServerIp = "10.2.0.11"; + public static Boolean _subscribedToQueue = false; + public static Boolean _subscribeToQueueForTheFirstTime = false; + + private static Int32 _failsCounter = 0; // move to a config file + // private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green; + // private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green; + + static Program() + { + var config = Config.Load(); + var d = config.Devices; + + Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled + ? new NullChannel() + : new TcpChannel(device); + + BatteryNodes = config + .Devices + .BatteryNodes + .Select(n => n.ConvertTo()) + .ToArray(config.Devices.BatteryNodes.Length); + + KacoChannel = CreateChannel(d.KacoIp); + GridMeterChannel = CreateChannel(d.GridMeterIp); + DcDcChannel = CreateChannel(d.DcDcIp); + + } + + + public static async Task Main(String[] args) + { + + while (true) + { + try + { + await Run(); + } + catch (Exception e) + { + // e.LogError(); + } + } + // ReSharper disable once FunctionNeverReturns + } + + private static async Task Run() + { + Watchdog.NotifyReady(); + + Console.WriteLine("Starting Kaco Communication"); + + var kacoDevice = new KacoDevice(KacoChannel); + var gridMeterDevice = new PlVarioMeterDevice(GridMeterChannel); + var dcDcDevices = new TruConvertDcDcDevices(DcDcChannel); + + var firstCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port1Cabinet, n)).ToList(); + var secondCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port2Cabinet, n)).ToList(); + var thirdCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port3Cabinet, n)).ToList(); + + var batteryDevices1 = new BatteryDeligreenDevices(firstCabinetBatteriesDevice); + var batteryDevices2 = new BatteryDeligreenDevices(secondCabinetBatteriesDevice); + var batteryDevices3 = new BatteryDeligreenDevices(thirdCabinetBatteriesDevice); + + StatusRecord? ReadStatus() + { + PlVarioMeterRecord? gridRecord = null; + var config = Config.Load(); + var kacoRecord = kacoDevice.Read(); + var gridrawRecord = gridMeterDevice.Read(); + var dcDcRecord = dcDcDevices.Read(); + if (gridrawRecord != null) + { + gridRecord = new PlVarioMeterRecord(gridrawRecord); + } + + var batteryKabinet1 = batteryDevices1.Read(); + var batteryKabinet2 = batteryDevices2.Read(); + var batteryKabinet3 = batteryDevices3.Read(); + + return new StatusRecord + { + InverterRecord = kacoRecord, + GridMeterRecord = gridRecord, + DcDc = dcDcRecord, + BatteryKabinet1 = batteryKabinet1, + BatteryKabinet2 = batteryKabinet2, + BatteryKabinet3 = batteryKabinet3, + Config = config // load from disk every iteration, so config can be changed while running + }; + } + + while (true) + { + await Observable + .Interval(UpdateInterval) + .Select(_ => RunIteration()) + .SelectMany(status => + DataLogging(status, DateTime.Now.Round(UpdateInterval)) + .ContinueWith(_ => status)) // back to StatusRecord + .SelectMany(SaveModbusTcpFile) + .SelectError() + .ToTask(); + } + + + StatusRecord? RunIteration() + { + try + { + Watchdog.NotifyAlive(); + + var startTime = DateTime.Now; + Console.WriteLine( + "***************************** Reading Kaco Data *********************************************"); + Console.WriteLine(startTime.ToString("HH:mm:ss.fff")); + // the order matter of the next three lines + var statusrecord = ReadStatus(); + statusrecord?.CreateSimpleTopologyTextBlock().WriteLine(); + + + // statusrecord?.DcDc?.Dc.Battery.Power .WriteLine(" Power"); + // statusrecord?.DcDc?.Dc.Battery.Voltage .WriteLine(" Voltage"); + // statusrecord?.DcDc?.Dc.Battery.Current .WriteLine(" Current"); + // statusrecord?.DcDc?.Dc.Link.Voltage .WriteLine(" Dc link Voltage"); + + + statusrecord?.GridMeterRecord?.Frequency .WriteLine(" Frequency"); + statusrecord?.GridMeterRecord?.VoltageU1 .WriteLine(" VoltageU1"); + statusrecord?.GridMeterRecord?.VoltageU2 .WriteLine(" VoltageU2"); + statusrecord?.GridMeterRecord?.VoltageU3 .WriteLine(" VoltageU3"); + + statusrecord?.GridMeterRecord?.CurrentI1 .WriteLine(" CurrentI1"); + statusrecord?.GridMeterRecord?.CurrentI2 .WriteLine(" CurrentI2"); + statusrecord?.GridMeterRecord?.CurrentI3 .WriteLine(" CurrentI3"); + + statusrecord?.GridMeterRecord?.ActivePowerL1 .WriteLine(" ActivePowerL1"); + statusrecord?.GridMeterRecord?.ActivePowerL2 .WriteLine(" ActivePowerL2"); + statusrecord?.GridMeterRecord?.ActivePowerL3 .WriteLine(" ActivePowerL3"); + statusrecord?.GridMeterRecord?.ActivePowerTotal .WriteLine(" ActivePowerTotal"); + + statusrecord?.InverterRecord?.CurrentState.WriteLine(" CurrentState"); + statusrecord?.InverterRecord?.RequestedState.WriteLine(" RequestedState"); + statusrecord?.InverterRecord?.PcuError.WriteLine(" PcuError"); + statusrecord?.InverterRecord?.PcuState.WriteLine(" PcuState"); + + statusrecord?.InverterRecord?.BattCharId.WriteLine(" _battCharId"); + statusrecord?.InverterRecord?.BattCharLength.WriteLine(" _battCharLength"); + + statusrecord?.InverterRecord?.MinDischargeVoltage.WriteLine(" MinDischargeVoltage"); + statusrecord?.InverterRecord?.MaxDischargeCurrent.WriteLine(" MaxDischargeCurrent"); + statusrecord?.InverterRecord?.DischargeCutoffCurrent.WriteLine(" DischargeCutoffCurrent"); + + statusrecord?.InverterRecord?.MaxChargeVoltage.WriteLine(" MaxChargeVoltage"); + statusrecord?.InverterRecord?.MaxChargeCurrent.WriteLine(" MaxChargeCurrent"); + statusrecord?.InverterRecord?.ChargeCutoffCurrent.WriteLine(" ChargeCutoffCurrent"); + + statusrecord?.InverterRecord?.ActivePowerSetPercent.WriteLine(" ActivePowerSetPercent"); + statusrecord?.InverterRecord?.ReactivePowerSetPercent.WriteLine(" ReactivePowerSetPercent"); + statusrecord?.InverterRecord?.WatchdogSeconds.WriteLine(" WatchdogSeconds"); + InitializeKacoStartup(statusrecord); + + Console.WriteLine( " ************************************ We are writing ************************************"); + + statusrecord?.Config.Save(); // save the config file + if (statusrecord?.InverterRecord != null) kacoDevice.Write(statusrecord.InverterRecord); + Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff")); + + return statusrecord; + } + catch (Exception e) + { + // Handle exception and print the error + Console.WriteLine(e); + return null; + } + } + } + + private static async Task SavingLocalCsvFile(Int64 timestamp, String csv) + { + const String directoryPath = "/home/inesco/salimax/csvFile"; + + // Ensure directory exists + if (!Directory.Exists(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + + // Get all .csv files ordered by creation time (oldest first) + var csvFiles = new DirectoryInfo(directoryPath) + .GetFiles("*.csv") + .OrderBy(f => f.CreationTimeUtc) + .ToList(); + + // If more than 5000 files, delete the oldest + if (csvFiles.Count >= 5000) + { + var oldestFile = csvFiles.First(); + try + { + oldestFile.Delete(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to delete file: {oldestFile.FullName}, Error: {ex.Message}"); + } + } + + // Prepare the filtered CSV content + var filteredCsv = csv + .SplitLines() + .Where(l => !l.Contains("Secret")) + .JoinLines(); + + // Save the new CSV file + var filePath = Path.Combine(directoryPath, timestamp + ".csv"); + await File.WriteAllTextAsync(filePath, filteredCsv); + } + + private static async Task DataLogging(StatusRecord status, DateTime timeStamp) + { + var csv = status.ToCsv(); + + // for debug, only to be deleted. + //foreach (var item in csv.SplitLines()) + //{ + // Console.WriteLine(item + ""); + //} + + await SavingLocalCsvFile(timeStamp.ToUnixTime(), csv); + + var jsonData = new Dictionary(); + + ConvertToJson(csv, jsonData).LogInfo(); + + var s3Config = status.Config.S3; + + if (s3Config is null) + return false; + + //Concatenating 15 files in one file + return await ConcatinatingAndCompressingFiles(timeStamp.ToUnixTime(), s3Config); + } + + private static String ConvertToJson(String csv, Dictionary jsonData) + { + foreach (var line in csv.Split('\n')) + { + if (string.IsNullOrWhiteSpace(line)) continue; + + var parts = line.Split(';'); + var keyPath = parts[0]; + var value = parts[1]; + var unit = parts.Length > 2 ? parts[2].Trim() : ""; + InsertIntoJson(jsonData, keyPath.Split('/'), value); + } + + var jsonOutput = JsonConvert.SerializeObject(jsonData, Formatting.None); + return jsonOutput; + } + + private static async Task ConcatinatingAndCompressingFiles(Int64 timeStamp, S3Config s3Config) + { + if (_fileCounter >= NbrOfFileToConcatenate) + { + _fileCounter = 0; + + var logFileConcatenator = new LogFileConcatenator(); + var jsontoSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate); + + var fileNameWithoutExtension = timeStamp.ToString(); // used for both S3 and local + var s3Path = fileNameWithoutExtension + ".json"; + + var request = s3Config.CreatePutRequest(s3Path); + + var compressedBytes = CompresseBytes(jsontoSend); + var base64String = Convert.ToBase64String(compressedBytes); + var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64"); + + var uploadSucceeded = false; + + try + { + var response = await request.PutAsync(stringContent); + + if (response.StatusCode != 200) + { + Console.WriteLine("ERROR: PUT"); + var error = await response.GetStringAsync(); + Console.WriteLine(error); + + await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); + Heartbit(); + return false; + } + + uploadSucceeded = true; + Console.WriteLine("✅ File uploaded to S3 successfully."); + + Console.WriteLine( + "---------------------------------------- Resending FailedUploadedFiles----------------------------------------"); + Heartbit(); + + await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files + } + catch (Exception ex) + { + Console.WriteLine("Upload exception: " + ex.Message); + + if (!uploadSucceeded) + { + await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); + } + + Heartbit(); + return false; + } + } + _fileCounter++; + return true; + } + + private static void Heartbit() + { + var s3Bucket = Config.Load().S3?.Bucket; + var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId); + if (tryParse) + { + var returnedStatus = new StatusMessage + { + InstallationId = installationId, + Product = 3, + Status = _sodiAlarmState, + Type = MessageType.Heartbit, + }; + if (s3Bucket != null) + RabbitMqManager.InformMiddleware(returnedStatus); + } + } + + private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension) + { + try + { + var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads"); + Directory.CreateDirectory(fallbackDir); + + var fileName = fileNameWithoutExtension + ".json"; // Save as .json, but still compressed + var fullPath = Path.Combine(fallbackDir, fileName); + + await File.WriteAllBytesAsync(fullPath, compressedData); // Compressed data + Console.WriteLine($"Saved compressed failed upload to: {fullPath}"); + } + catch (Exception ex) + { + Console.WriteLine("Failed to save compressed file locally: " + ex.Message); + } + } + + private static Byte[] CompresseBytes(String jsonToSend) + { + //Compress JSON data to a byte array + using var memoryStream = new MemoryStream(); + //Create a zip directory and put the compressed file inside + using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) + { + var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add JSON data to the ZIP archive + using (var entryStream = entry.Open()) + using (var writer = new StreamWriter(entryStream)) + { + writer.Write(jsonToSend); + } + } + + var compressedBytes = memoryStream.ToArray(); + + return compressedBytes; + } + + private static async Task ResendLocalFailedFilesAsync(S3Config s3Config) + { + var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads"); + + if (!Directory.Exists(fallbackDir)) + return; + + var files = Directory.GetFiles(fallbackDir, "*.json"); + files.Length.WriteLine(" Number of failed files, to upload"); + + foreach (var filePath in files) + { + var fileName = Path.GetFileName(filePath); // e.g., "1720023600.json" + + try + { + byte[] compressedBytes = await File.ReadAllBytesAsync(filePath); + var base64String = Convert.ToBase64String(compressedBytes); + var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64"); + + var request = s3Config.CreatePutRequest(fileName); + var response = await request.PutAsync(stringContent); + + if (response.StatusCode == 200) + { + File.Delete(filePath); + Console.WriteLine($"✅ Successfully resent and deleted: {fileName}"); + } + else + { + Console.WriteLine($"❌ Failed to resend {fileName}, status: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"⚠️ Exception while resending {fileName}: {ex.Message}"); + } + } + } + + private static async Task SaveModbusTcpFile(StatusRecord status) + { + var modbusData = new Dictionary(); + + // SYSTEM DATA + var result1 = ConvertToModbusRegisters((status.Config.MinSoc * 10), "UInt16", 30001); // this to be updated to modbusTCP version + var result2 = ConvertToModbusRegisters(status.InverterRecord!.PcuError, "UInt32", 30002); + + // Merge all results into one dictionary + + var allResults = new[] + { + result1,result2 + }; + + foreach (var result in allResults) + { + foreach (var entry in result) + { + modbusData[entry.Key] = entry.Value; + } + } + // Write to JSON + var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true }); + await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json); + + //Console.WriteLine("JSON file written successfully."); + //Console.WriteLine(json); + var stopTime = DateTime.Now; + Console.WriteLine(stopTime.ToString("HH:mm:ss.fff" )+ " Finish the loop"); + return true; + } + + private static Dictionary ConvertToModbusRegisters(Object value, String outputType, + Int32 startingAddress) + { + var registers = new Dictionary(); + + switch (outputType) + { + case "UInt16": + registers[startingAddress.ToString()] = Convert.ToUInt16(value); + break; + + case "Int16": + var int16Val = Convert.ToInt16(value); + registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort + break; + + case "UInt32": + var uint32Val = Convert.ToUInt32(value); + registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word + registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word + break; + + case "Int32": + var int32Val = Convert.ToInt32(value); + var raw = unchecked((UInt32)int32Val); // reinterprets signed int as unsigned + registers[startingAddress.ToString()] = (UInt16)(raw & 0xFFFF); + registers[(startingAddress + 1).ToString()] = (UInt16)(raw >> 16); + break; + + default: + throw new ArgumentException("Unsupported output type: " + outputType); + } + return registers; + } + + + private static void InitializeKacoStartup(StatusRecord? statusRecord) + { + // + // 1. Apply DC – This part is physical and cannot be done in software. + // We assume DC power is already present. + // + + // + // 2. Send valid battery limits (Model 64202) + // All values temporarily set to "1" as requested. + // You will replace them later with real values. + // + if (statusRecord?.InverterRecord != null) + { + statusRecord.InverterRecord.MinDischargeVoltage = 700f; // 64202.DisMinV + statusRecord.InverterRecord.MaxDischargeCurrent = 140f; // 64202.DisMaxA + statusRecord.InverterRecord.DischargeCutoffCurrent = 10f; // 64202.DisCutoffA + + statusRecord.InverterRecord.MaxChargeVoltage = 800f; // 64202.ChaMaxV + statusRecord.InverterRecord.MaxChargeCurrent = 140f; // 64202.ChaMaxA + statusRecord.InverterRecord.ChargeCutoffCurrent = 10f; // 64202.ChaCutoffA + + statusRecord.InverterRecord.WatchdogSeconds = 30; // this is additional from my seid + + // + // 3. Enable limits (EnLimit) + // + statusRecord.InverterRecord.BatteryLimitsEnable = EnableDisableEnum.Enabled; + + // + // After writing all values in software, send them to the inverter + // + // + // 4. Read model 64201 to observe CurrentState transition + // + // Expected sequence: + // - Before valid limits: CurrentState == 7 (ERROR) + // - After valid limits: CurrentState == 8 (STANDBY) + // - Then after grid/DC conditions: CurrentState == 1 (OFF) or 11 (GRID_CONNECTED) + // + + var state = statusRecord.InverterRecord.CurrentState; + + Console.WriteLine($"KACO 64201.CurrentState = {state}"); + + switch (state) + { + case CurrentState.Standby: + Console.WriteLine("Device is in STANDBY (8) — battery limits accepted."); + break; + + case CurrentState.Off: + Console.WriteLine("Device is OFF (1) — OK for non-battery operation."); + break; + + case CurrentState.GridConnected: + Console.WriteLine("Device is GRID CONNECTED (11)."); + break; + + default: + Console.WriteLine("Device in unexpected state: " + state); + break; + } + //Thread.Sleep(2000); + } + } + private static void InsertIntoJson(Dictionary jsonDict, String[] keys, String value) + { + var currentDict = jsonDict; + for (Int16 i = 1; i < keys.Length; i++) // Start at 1 to skip empty root + { + var key = keys[i]; + if (!currentDict.ContainsKey(key)) + { + currentDict[key] = new Dictionary(); + } + + if (i == keys.Length - 1) // Last key, store the value + { + + if (!value.Contains(",") && double.TryParse(value, out Double doubleValue)) // Try to parse value as a number + { + currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places + + } + else + { + currentDict[key] = value; // Store as string if not a number + } + } + else + { + currentDict = (Dictionary)currentDict[key]; + } + } + } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/SystemConfig/Config.cs b/csharp/App/KacoCommunication/SystemConfig/Config.cs new file mode 100644 index 000000000..84c6c6b89 --- /dev/null +++ b/csharp/App/KacoCommunication/SystemConfig/Config.cs @@ -0,0 +1,108 @@ +using System.Text.Json; +using InnovEnergy.App.KacoCommunication.Devices; +using InnovEnergy.App.KacoCommunication.ESS; +using InnovEnergy.Lib.Utils; +using static System.Text.Json.JsonSerializer; + +namespace InnovEnergy.App.KacoCommunication.SystemConfig; + +public class Config +{ + private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json"); + private static DateTime DefaultDatetime => new(2024, 03, 11, 09, 00, 00); + + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + public required Double MinSoc { get; set; } + + /* public required Double MaximumDischargingCurrent { get; set; } + public required Double MaximumChargingCurrent { get; set; } + public required Int16 BatteriesCount { get; set; } + public required Double ModbusProtcolNumber { get; set; }*/ + public required DeviceConfig Devices { get; set; } + + public required S3Config? S3 { get; set; } + + + private static String? LastSavedData { get; set; } + + public static Config Default => new() + { + MinSoc = 20, + /* MaximumChargingCurrent = 180, + MaximumDischargingCurrent = 180, + BatteriesCount = 0, + ModbusProtcolNumber = 1.2,*/ + Devices = new () + { + KacoIp = new() { Host = "10.0.3.1", Port = 502, DeviceState = DeviceState.Measured}, + DcDcIp = new() { Host = "10.0.2.1", Port = 502, DeviceState = DeviceState.Measured}, + GridMeterIp = new() { Host = "192.168.1.5", Port = 502, DeviceState = DeviceState.Measured}, + + BatteryNodes = new []{0,1, 2, 3, 4, 5 } + }, + + S3 = new() + { + Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d", + Region = "sos-ch-dk-2", + Provider = "exo.io", + Key = "EXObb5a49acb1061781761895e7", + Secret = "sKhln0w8ii3ezZ1SJFF33yeDo8NWR1V4w2H0D4-350I", + ContentType = "text/plain; charset=utf-8" + } + }; + + public void Save(String? path = null) + { + var configFilePath = path ?? DefaultConfigFilePath; + + try + { + var jsonString = Serialize(this, JsonOptions); + + if (LastSavedData == jsonString) + return; + + LastSavedData = jsonString; + + File.WriteAllText(configFilePath, jsonString); + } + catch (Exception e) + { + $"Failed to write config file {configFilePath}\n{e}".WriteLine(); + 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}".WriteLine(); + 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; + } + } +} diff --git a/csharp/App/KacoCommunication/SystemConfig/DeviceConfig.cs b/csharp/App/KacoCommunication/SystemConfig/DeviceConfig.cs new file mode 100644 index 000000000..6d4ef9e00 --- /dev/null +++ b/csharp/App/KacoCommunication/SystemConfig/DeviceConfig.cs @@ -0,0 +1,13 @@ +using InnovEnergy.App.KacoCommunication.Devices; + +namespace InnovEnergy.App.KacoCommunication.SystemConfig; + +public class DeviceConfig +{ + public required SalimaxDevice KacoIp { get; init; } + public required SalimaxDevice DcDcIp { get; init; } + public required SalimaxDevice GridMeterIp { get; init; } + + public required Int32[] BatteryNodes { get; init; } + +} diff --git a/csharp/App/KacoCommunication/SystemConfig/S3Config.cs b/csharp/App/KacoCommunication/SystemConfig/S3Config.cs new file mode 100644 index 000000000..702f774a6 --- /dev/null +++ b/csharp/App/KacoCommunication/SystemConfig/S3Config.cs @@ -0,0 +1,79 @@ +using System.Security.Cryptography; +using Flurl; +using Flurl.Http; +using InnovEnergy.Lib.Utils; +using static System.Text.Encoding; +using Convert = System.Convert; + +namespace InnovEnergy.App.KacoCommunication.SystemConfig; + +public record S3Config +{ + public required String Bucket { get; init; } + public required String Region { get; init; } + public required String Provider { get; init; } + public required String Key { get; init; } + public required String Secret { get; init; } + public required String ContentType { get; init; } + + private String Host => $"{Bucket}.{Region}.{Provider}"; + private String Url => $"https://{Host}"; + + public IFlurlRequest CreatePutRequest(String s3Path) => CreateRequest("PUT", s3Path); + public IFlurlRequest CreateGetRequest(String s3Path) => CreateRequest("GET", s3Path); + + private IFlurlRequest CreateRequest(String method, String s3Path) + { + var date = DateTime.UtcNow.ToString("r"); + var auth = CreateAuthorization(method, s3Path, date); + + return Url + .AppendPathSegment(s3Path) + .WithHeader("Host", Host) + .WithHeader("Date", date) + .WithHeader("Authorization", auth) + .AllowAnyHttpStatus(); + } + + private String CreateAuthorization(String method, + String s3Path, + String date) + { + return CreateAuthorization + ( + method : method, + bucket : Bucket, + s3Path : s3Path, + date : date, + s3Key : Key, + s3Secret : Secret, + contentType: ContentType + ); + } + + + + private static String CreateAuthorization(String method, + String bucket, + String s3Path, + String date, + String s3Key, + String s3Secret, + String contentType = "application/base64", + String md5Hash = "") + { + + contentType = "application/base64; charset=utf-8"; + //contentType = "text/plain; charset=utf-8"; //this to use when sending plain csv to S3 + + var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}"; + using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret)); + + var signature = UTF8 + .GetBytes(payload) + .Apply(hmacSha1.ComputeHash) + .Apply(Convert.ToBase64String); + + return $"AWS {s3Key}:{signature}"; + } +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/Topology.cs b/csharp/App/KacoCommunication/Topology.cs new file mode 100644 index 000000000..3f6dce2ad --- /dev/null +++ b/csharp/App/KacoCommunication/Topology.cs @@ -0,0 +1,265 @@ +using System.Globalization; +using InnovEnergy.App.KacoCommunication.Devices; +using InnovEnergy.App.KacoCommunication.ESS; +using InnovEnergy.Lib.Devices.BatteryDeligreen; +using InnovEnergy.Lib.Units.Power; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.KacoCommunication; + + +// +// ┌────┐ +// │ Pv │ +// └────┘ +// V +// V +// (i) 13.2 kW ┌────────────┐ ┌────────────┐ ┌────────────┐ +// V │ Battery K1│ │ Battery K2│ │ Battery K3│ +// ┌─────────┐ ┌─────────┐ V ├────────────┤ ├────────────┤ ├────────────┤ +// │ Grid │ │ AC/DC │ ┌────────┐ ┌───────┐ │ 52.3 V │ │ 52.3 V │ │ 52.3 V │ +// ├─────────┤ -10.3 kW├─────────┤ -11.7 kW │ Dc Bus │ 1008 W │ DC/DC │ 1008 W │ 99.1 % │ │ 99.1 % │ │ 99.1 % │ +// │ -3205 W │<<<<<<<<<│ -6646 W │<<<<<<<<<<├────────┤>>>>>>>>>>├───────┤>>>>>>>>>>│ 490 mA │ │ 490 mA │ │ 490 mA │ +// │ -3507 W │ (a) │ -5071 W │ (h) │ 776 V │ (k) │ 56 V │ (l) │ 250 °C │ │ 250 °C │ │ 250 °C │ +// │ -3605 W │ └─────────┘ └────────┘ └───────┘ │ 445 A │ │ 445 A │ │ 445 A │ +// └─────────┘ V │ │ │ │ │ │ +// V │ │ │ │ │ │ +// (j) 0 W └────────────┘ └────────────┘ └────────────┘ +// V +// V +// ┌──────┐ +// │ Load │ +// └──────┘ +// + +// New (simplified) topology: +// +// ┌────┐ +// │ PV │ +// └────┘ +// V +// V +// (i) 13.2 kW +// V +// V +// ┌─────────┐ ┌─────────┐ (h) ┌────────┐ (k) ┌───────┐ (l) ┌────────────┐ ┌────────────┐ ┌────────────┐ +// │ Grid │<<<│ AC/DC │<<<<<<<<<<<<<│ Dc Bus │>>>>>>>>│ DC/DC │>>>>>>>>│ Battery K1 │ │ Battery K2 │ │ Battery K3 │ +// ├─────────┤ ├─────────┤ ├────────┤ ├───────┤ ├────────────┤ ├────────────┤ ├────────────┤ +// │ -3205 W │ │ -6646 W │ │ 776 V │ │ 56 V │ │ 52.3 V ... │ │ 52.3 V ... │ │ 52.3 V ... │ +// │ -3507 W │ │ -5071 W │ └────────┘ └───────┘ └────────────┘ └────────────┘ └────────────┘ +// │ -3605 W │ +// └─────────┘ +// +// V +// V +// (j) 0 W +// V +// V +// ┌──────┐ +// │ Load │ +// └──────┘ +// +// Notes: +// - (a) is grid power (to/from grid) +// - (h) is AC/DC -> DC link power (or your chosen link variable) +// - (i) PV -> DC bus +// - (j) DC load +// - (k) DC bus -> DC/DC link +// - (l) DC/DC -> battery power (or total battery power) + +public static class SimpleTopology +{ + public static TextBlock CreateSimpleTopologyTextBlock(this StatusRecord status) + { + // Keep the same variable letters as your diagrams (where possible) + var a = status.GridMeterRecord?.ActivePowerTotal; + + // In your existing code, "AC/DC column" shows per-device AC power; + // and "h" is a separate link (AcDcToDcLink?.Power.Value). + var h = 0; + + var i = 0; + var j = 0; + + var k = 0; + + // You mentioned this changed: l is now equal total battery power + var l = status.BatteryKabinet1.Power; + + var grid = status.CreateGridColumn(a); + var acdc = status.CreateAcDcColumn(h); + var dcBus = status.CreateDcBusColumn(i, j, k); + var dcdc = status.CreateDcDcColumn(l); + var batteries = status.CreateBatteriesRow(); + + return TextBlock.AlignCenterVertical( + grid, + acdc, + dcBus, + dcdc, + batteries + ); + } + + private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a) + { + // ┌─────────┐ + // │ Grid │ + // ├─────────┤ + // │ L1 P │ + // │ L2 P │ + // │ L3 P │ + // └─────────┘ (a) flow to AC/DC + + var gridMeterAc = status.GridMeterRecord; + + var gridBox = TextBlock + .AlignLeft( + gridMeterAc?.ActivePowerL1.Value.ToString(CultureInfo.InvariantCulture) ?? "???", + gridMeterAc?.ActivePowerL2.Value.ToString(CultureInfo.InvariantCulture) ?? "???", + gridMeterAc?.ActivePowerL3.Value.ToString(CultureInfo.InvariantCulture) ?? "???" + ) + .TitleBox("Grid"); + + // Flow from Grid to AC/DC in the picture is horizontal (left -> right), using <<<<<< for export/import. + // Your Flow.Horizontal(power) already handles arrow direction by sign (based on your existing outputs). + var flow = Flow.Horizontal(a); + + return TextBlock.AlignCenterVertical(gridBox, flow); + } + + private static TextBlock CreateAcDcColumn(this StatusRecord status, ActivePower? h) + { + // ┌─────────┐ + // │ AC/DC │ + // ├─────────┤ + // │ dev1 P │ + // │ dev2 P │ + // └─────────┘ (h) flow to DC Bus + + var acdcBox = TextBlock + .AlignLeft(status.InverterRecord?.ActivePowerSetPercent.ToString() ?? "???") + .TitleBox("AC/DC"); + + var flowToDcBus = Flow.Horizontal(h); + + return TextBlock.AlignCenterVertical(acdcBox, flowToDcBus); + } + + private static TextBlock CreateDcBusColumn( + this StatusRecord status, + ActivePower? i, + ActivePower? j, + ActivePower? k) + { + // ┌────┐ + // │ PV │ + // └────┘ + // V + // (i) 13.2 kW + // V + // ┌────────┐ (k) >>>>>>>>> to DC/DC + // │ Dc Bus │>>>>>>>>>>>>>>>>>>> + // ├────────┤ + // │ 776 V │ + // └────────┘ + // V + // (j) 0 W + // V + // ┌──────┐ + // │ Load │ + // └──────┘ + + // PV box + vertical flow + var pvBox = TextBlock.FromString("PV").Box(); + var pvToBus = Flow.Vertical(i); + + // DC bus box (voltage from your DcDc record matches your existing code) + var dcBusVoltage = 0.0; + var dcBusBox = dcBusVoltage + .ToString(CultureInfo.InvariantCulture) + .Apply(TextBlock.FromString) + .TitleBox("Dc Bus"); + + // Horizontal flow from DC Bus to DC/DC + var busToDcDc = Flow.Horizontal(k); + + // Load box + vertical flow + var busToLoad = Flow.Vertical(j); + var loadBox = TextBlock.FromString("Load").Box(); + + // Assemble: put PV above DC Bus, Load below DC Bus, and the (k) flow beside the bus. + return TextBlock.AlignCenterVertical( + TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox), + busToDcDc + ); + } + + private static TextBlock CreateDcDcColumn(this StatusRecord status, ActivePower? l) + { + // ┌───────┐ + // │ DC/DC │ + // ├───────┤ + // │ 56 V │ + // └───────┘ (l) flow to batteries + + var dc48Voltage =0.0; + + var dcdcBox = TextBlock + .AlignLeft(dc48Voltage) + .TitleBox("DC/DC"); + + var flowToBattery = Flow.Horizontal(l); + + return TextBlock.AlignCenterVertical(dcdcBox, flowToBattery); + } + + private static TextBlock CreateBatteriesRow(this StatusRecord status) + { + // Battery K1 | Battery K2 | Battery K3 (side-by-side) + // Each box: voltage, soc, current, temp, etc. (you can tailor) + + var bat = status.BatteryKabinet1; + if (bat is null) + return TextBlock.AlignLeft("no battery").Box(); + + // If you actually have relay names K1/K2/K3 per battery, wire them here. + // For now we label by index as "Battery K{n}" to match your picture. + var boxes = bat.Devices + .Select((b, idx) => CreateBatteryKBox(b, idx)) + .ToReadOnlyList(); + + // Align horizontally to match the diagram + return boxes.Any() + ? TextBlock.AlignTop(boxes) + : TextBlock.AlignLeft("no battery devices").Box(); + } + + private static TextBlock CreateBatteryKBox(BatteryDeligreenRecord battery, int idx) + { + // Minimal “K-style” battery box matching your diagram fields + var data = battery.BatteryDeligreenDataRecord; + + // Some of your sample screen values look like: + // 52.3 V, 99.1 %, 490 mA, 250 °C, 445 A + // Map these to whatever fields you trust in your record. + var voltage = data.BusVoltage.ToDisplayString(); + var soc = data.Soc.ToDisplayString(); + var current = data.BusCurrent.ToDisplayString(); + var temp = data.TemperaturesList.PowerTemperature.ToDisplayString(); + + // If you have a better “pack current” field, replace this line. + // Keeping it as a separate line to mimic the picture’s extra current-like line. + var extraCurrent = data.BusCurrent.ToDisplayString(); + + return TextBlock + .AlignLeft( + voltage, + soc, + current, + temp, + extraCurrent + ) + .TitleBox($"Battery K{idx + 1}"); + } +} diff --git a/csharp/App/KacoCommunication/deploy.sh b/csharp/App/KacoCommunication/deploy.sh new file mode 100755 index 000000000..3e39a2a64 --- /dev/null +++ b/csharp/App/KacoCommunication/deploy.sh @@ -0,0 +1,21 @@ +#!/bin/bash +dotnet_version='net6.0' +salimax_ip="$1" +username='ie-entwicklung' +root_password='Salimax4x25' +set -e + +echo -e "\n============================ Build ============================\n" + +dotnet publish \ + ./KacoCommunication.csproj \ + -p:PublishTrimmed=false \ + -c Release \ + -r linux-x64 + +echo -e "\n============================ Deploy ============================\n" + +rsync -v \ + --exclude '*.pdb' \ + ./bin/Release/$dotnet_version/linux-x64/publish/* \ + $username@"$salimax_ip":~/salimax \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index 57862ec71..22ab4c22e 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -54,6 +54,7 @@ internal static class Program private static readonly Channel RelaysTsChannel; private static readonly Channel BatteriesChannel; + private static readonly String SwVersionNumber = " V1.00." + DateTime.Today; private static Boolean _curtailFlag = false; private const String VpnServerIp = "10.2.0.11"; private static Boolean _subscribedToQueue = false; @@ -247,34 +248,6 @@ internal static class Program Watchdog.NotifyAlive(); var record = ReadStatus(); - /* - if (record.Relays != null) - { - record.Relays.Do0StartPulse = true; - - record.Relays.PulseOut0HighTime = 20000; - record.Relays.PulseOut0LowTime = 20000; - record.Relays.DigitalOutput0Mode = 2; - - record.Relays.LedGreen = false; - - record.Relays.Do0StartPulse.WriteLine(" = start pulse 0"); - - record.Relays.PulseOut0HighTime.WriteLine(" = PulseOut0HighTime"); - - record.Relays.PulseOut0LowTime.WriteLine(" = PulseOut0LowTime"); - - record.Relays.DigitalOutput0Mode.WriteLine(" = DigitalOutput0Mode"); - - record.Relays.LedGreen.WriteLine(" = LedGreen"); - - record.Relays.LedRed.WriteLine(" = LedRed"); - - } - else - { - " Relays are null".WriteLine(); - }*/ SendSalimaxStateAlarm(GetSalimaxStateAlarm(record), record); // to improve @@ -518,12 +491,15 @@ internal static class Program private static void ControlConstants(this StatusRecord r) { + r.DcDc.ResetAlarms();// move this first to be sure it's executed + r.AcDc.ResetAlarms();// move this first to be sure it's executed var inverters = r.AcDc.Devices; var dcDevices = r.DcDc.Devices; var configFile = r.Config; var maxBatteryDischargingCurrentLive = 0.0; var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode + // This adapting the max discharging current to the current Active Strings if (r.Battery != null) { @@ -572,8 +548,7 @@ internal static class Program dcDevices.ForEach(d => d.Control.VoltageLimits.MinBatteryVoltage = devicesConfig.DcDc.MinDischargeBatteryVoltage); dcDevices.ForEach(d => d.Control.ControlMode = DcControlMode.VoltageDroop); - r.DcDc.ResetAlarms(); - r.AcDc.ResetAlarms(); + } // This will be used for provider throttling, this example is only for either 100% or 0 % diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs b/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs index 23f777169..d8466af49 100644 --- a/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs @@ -31,6 +31,8 @@ public class CombinedAdamRelaysRecord : IRelaysRecord set => _recordAdam6360D.K2ConnectIslandBusToGridBus = value; } + public Boolean OldK2ConnectIslandBusToGridBus { get; set; } + public Boolean Inverter1WagoStatus => _recordAdam6360D.Inverter1WagoStatus; public Boolean Inverter2WagoStatus => _recordAdam6360D.Inverter2WagoStatus; public Boolean Inverter3WagoStatus => _recordAdam6360D.Inverter3WagoStatus; diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs b/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs index 3e165b0ae..176277a49 100644 --- a/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs @@ -9,6 +9,7 @@ public interface IRelaysRecord Boolean FiWarning { get; } Boolean FiError { get; } Boolean K2ConnectIslandBusToGridBus { get; set; } + Boolean OldK2ConnectIslandBusToGridBus { get; set; } // Boolean Inverter1WagoRelay { get; set; } // to add in the future // Boolean Inverter2WagoRelay { get; set; } // to add in the future diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs index b840dbeb2..d8caeb259 100644 --- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs @@ -122,12 +122,17 @@ public class RelaysRecordAmax : IRelaysRecord public Boolean FiWarning => !_Regs.DigitalInput21; public Boolean FiError => !_Regs.DigitalInput23; - public Boolean K2ConnectIslandBusToGridBus + public Boolean K2ConnectIslandBusToGridBus // IMPORTANT: this should be changed to Relay22 for Tschirren installation { get => _Regs.Relay23; set => _Regs.Relay23 = value; } + public Boolean OldK2ConnectIslandBusToGridBus + { + get => _Regs.Relay23; + set => _Regs.Relay23 = value; + } public static implicit operator Amax5070Registers(RelaysRecordAmax d) => d._Regs; public static implicit operator RelaysRecordAmax(Amax5070Registers d) => new RelaysRecordAmax(d); diff --git a/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs b/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs index 705698450..ea5dea85d 100644 --- a/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs +++ b/csharp/App/SinexcelCommunication/DataTypes/Configuration.cs @@ -4,10 +4,17 @@ namespace InnovEnergy.App.SinexcelCommunication.DataTypes; public class Configuration { - public Double MinimumSoC { get; set; } - public Double MaximumDischargingCurrent { get; set; } - public Double MaximumChargingCurrent { get; set; } - public WorkingMode OperatingPriority { get; set; } - public Int16 BatteriesCount { get; set; } + public Double MinimumSoC { get; set; } + public Double MaximumDischargingCurrent { get; set; } + public Double MaximumChargingCurrent { get; set; } + public WorkingMode OperatingPriority { get; set; } + public Int16 BatteriesCount { get; set; } + public Int16 ClusterNumber { get; set; } + public Int16 PvNumber { get; set; } + public DateTime StartTimeChargeandDischargeDayandTime { get; set; } + public DateTime StopTimeChargeandDischargeDayandTime { get; set; } + public Single TimeChargeandDischargePower { get; set; } + public Boolean ControlPermission { get; set; } } + diff --git a/csharp/App/SinexcelCommunication/MiddlewareClasses/MiddlewareAgent.cs b/csharp/App/SinexcelCommunication/MiddlewareClasses/MiddlewareAgent.cs index f9faf5b43..a80093a4f 100644 --- a/csharp/App/SinexcelCommunication/MiddlewareClasses/MiddlewareAgent.cs +++ b/csharp/App/SinexcelCommunication/MiddlewareClasses/MiddlewareAgent.cs @@ -68,7 +68,10 @@ public static class MiddlewareAgent if (config != null) { Console.WriteLine($"Received a configuration message: " + - "MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + config.MaximumChargingCurrent + config.MaximumDischargingCurrent); + "MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + + "Maximum Charging current is "+ config.MaximumChargingCurrent + "/n" + "Maximum Discharging current is " + config.MaximumDischargingCurrent + + "StartTimeChargeandDischargeDayandTime is" + config.StartTimeChargeandDischargeDayandTime + "StopTimeChargeandDischargeDayandTime is" + config.StopTimeChargeandDischargeDayandTime + + "TimeChargeandDischargePowert is " + config.TimeChargeandDischargePower + " Control permission is" + config.ControlPermission); // Send the reply to the sender's endpoint _udpListener.Send(replyData, replyData.Length, serverEndpoint); diff --git a/csharp/App/SinexcelCommunication/MiddlewareClasses/RabbitMQManager.cs b/csharp/App/SinexcelCommunication/MiddlewareClasses/RabbitMQManager.cs index 759e08477..253cdc0f6 100644 --- a/csharp/App/SinexcelCommunication/MiddlewareClasses/RabbitMQManager.cs +++ b/csharp/App/SinexcelCommunication/MiddlewareClasses/RabbitMQManager.cs @@ -7,12 +7,11 @@ using RabbitMQ.Client; namespace InnovEnergy.App.SinexcelCommunication.MiddlewareClasses; -[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")] public static class RabbitMqManager { - public static ConnectionFactory? Factory ; - public static IConnection ? Connection; - public static IModel? Channel; + private static ConnectionFactory? _factory ; + private static IConnection ? _connection; + private static IModel? _channel; public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp) { @@ -20,19 +19,22 @@ public static class RabbitMqManager { //_factory = new ConnectionFactory { HostName = VpnServerIp }; - Factory = new ConnectionFactory + _factory = new ConnectionFactory { HostName = VpnServerIp, Port = 5672, VirtualHost = "/", UserName = "producer", Password = "b187ceaddb54d5485063ddc1d41af66f", - + AutomaticRecoveryEnabled = true, + NetworkRecoveryInterval = TimeSpan.FromSeconds(10), + RequestedHeartbeat = TimeSpan.FromSeconds(30) + }; - Connection = Factory.CreateConnection(); - Channel = Connection.CreateModel(); - Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null); + _connection = _factory.CreateConnection(); + _channel = _connection.CreateModel(); + _channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null); Console.WriteLine("The controller sends its status to the middleware for the first time"); if (s3Bucket != null) InformMiddleware(currentSalimaxState); @@ -52,13 +54,36 @@ public static class RabbitMqManager var message = JsonSerializer.Serialize(status); var body = Encoding.UTF8.GetBytes(message); - Channel.BasicPublish(exchange: string.Empty, + _channel.BasicPublish(exchange: string.Empty, routingKey: "statusQueue", basicProperties: null, body: body); Console.WriteLine($"Producer sent message: {message}"); } - + public static Boolean EnsureConnected(StatusMessage currentSalimaxState, string? s3Bucket, string vpnServerIp) + { + try + { + if (_connection == null || !_connection.IsOpen || + _channel == null || _channel.IsClosed) + { + Console.WriteLine("⚠ RabbitMQ connection lost. Reconnecting..."); + + _connection?.Dispose(); + _channel?.Dispose(); + + return SubscribeToQueue(currentSalimaxState, s3Bucket, vpnServerIp); + } + + return true; + } + catch (Exception ex) + { + Console.WriteLine("❌ Error while ensuring RabbitMQ connection: " + ex); + return false; + } + } + } \ No newline at end of file diff --git a/csharp/App/SinexcelCommunication/SystemConfig/Config.cs b/csharp/App/SinexcelCommunication/SystemConfig/Config.cs index b13920d0b..b1d244755 100644 --- a/csharp/App/SinexcelCommunication/SystemConfig/Config.cs +++ b/csharp/App/SinexcelCommunication/SystemConfig/Config.cs @@ -16,15 +16,23 @@ public class Config private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; - public required Double MinSoc { get; set; } - public required Double GridSetPoint { get; set; } - public required Double MaximumDischargingCurrent { get; set; } - public required Double MaximumChargingCurrent { get; set; } - public required WorkingMode OperatingPriority { get; set; } - public required Int16 BatteriesCount { get; set; } - public required Double ModbusProtcolNumber { get; set; } + public required Double MinSoc { get; set; } + public required Double GridSetPoint { get; set; } + public required Double MaximumDischargingCurrent { get; set; } + public required Double MaximumChargingCurrent { get; set; } + public required WorkingMode OperatingPriority { get; set; } + public required Int16 BatteriesCount { get; set; } + public required Int16 ClusterNumber { get; set; } + public required Int16 PvNumber { get; set; } + public required Double ModbusProtcolNumber { get; set; } + public required DateTime StartTimeChargeandDischargeDayandTime { get; set; } + public required DateTime StopTimeChargeandDischargeDayandTime { get; set; } - public required S3Config? S3 { get; set; } + public required Single TimeChargeandDischargePower { get; set; } + public required Boolean ControlPermission { get; set; } + + + public required S3Config? S3 { get; set; } private static String? LastSavedData { get; set; } @@ -36,7 +44,14 @@ public class Config MaximumDischargingCurrent = 180, OperatingPriority = WorkingMode.TimeChargeDischarge, BatteriesCount = 0, + ClusterNumber = 0, + PvNumber = 0, ModbusProtcolNumber = 1.2, + StartTimeChargeandDischargeDayandTime = DefaultDatetime, + StopTimeChargeandDischargeDayandTime = DefaultDatetime, + TimeChargeandDischargePower = 3, + ControlPermission = false, + S3 = new() { Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d", diff --git a/csharp/App/SinexcelCommunication/deploy.sh b/csharp/App/SinexcelCommunication/deploy.sh index 4fdb2df97..b471853c3 100755 --- a/csharp/App/SinexcelCommunication/deploy.sh +++ b/csharp/App/SinexcelCommunication/deploy.sh @@ -5,7 +5,7 @@ is_release="$2" # Pass --release if this is a real release username='inesco' root_password='Sodistore0918425' -release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag" + release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag" set -e @@ -28,5 +28,5 @@ rsync -v \ echo -e "\n✅ Real release. Triggering sync to server..." touch "$release_flag_file" else - echo -e "\n Test build. Not syncing to main release server." + echo -e "\n🚫 Test build. Not syncing to main release server." fi \ No newline at end of file diff --git a/csharp/App/SinexcelCommunication/sync-myRelease.sh b/csharp/App/SinexcelCommunication/sync-myRelease.sh new file mode 100755 index 000000000..dc4f1fc3a --- /dev/null +++ b/csharp/App/SinexcelCommunication/sync-myRelease.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +WATCHDIR="$HOME/sync/work/Code/CSharp/git_trunk/csharp/App/SinexcelCommunication/bin/Release/net6.0/linux-arm64/publish" +DEST="ubuntu@91.92.155.224:/home/ubuntu/SinexcelReleases" + +echo "👀 Watching for real releases in $WATCHDIR..." + +inotifywait -m -e close_write --format '%w%f' "$WATCHDIR" | while read file; do + filename="$(basename "$file")" + + if [[ "$filename" == ".release.flag" ]]; then + echo "🚀 Release flag detected. Syncing full release to $DEST..." + + rm "$file" + rsync -avz \ + --exclude '*.pdb' \ + "$WATCHDIR/" "$DEST/" + + echo "✅ Sync completed and flag cleared." + fi +done diff --git a/csharp/App/SodiStoreMax/src/Program.cs b/csharp/App/SodiStoreMax/src/Program.cs index d206362a1..e4a6c2700 100644 --- a/csharp/App/SodiStoreMax/src/Program.cs +++ b/csharp/App/SodiStoreMax/src/Program.cs @@ -58,7 +58,7 @@ internal static class Program // - private const String SwVersionNumber =" V1.35.220925 beta"; + private static readonly String SwVersionNumber = " V1.00." + DateTime.Today; private static Boolean _curtailFlag = false; private const String VpnServerIp = "10.2.0.11"; private static Boolean _subscribedToQueue = false; @@ -281,10 +281,10 @@ internal static class Program //record.PerformLed(); WriteControl(record); - record.Battery.Eoc.WriteLine(" Total EOC"); + record.Battery?.Eoc.WriteLine(" Total EOC"); $"{record.StateMachine.State}: {record.StateMachine.Message}".WriteLine(); - $"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {DateTime.Now.Round(UpdateInterval):dd/MM/yyyy HH:mm:ss} : {SwVersionNumber}".WriteLine(); + $"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {DateTime.Now.Round(UpdateInterval):dd/MM/yyyy HH:mm:ss} : { SwVersionNumber}".WriteLine(); record.CreateTopologyTextBlock().WriteLine(); diff --git a/csharp/Lib/Devices/Amax5070/Amax5070Registers.cs b/csharp/Lib/Devices/Amax5070/Amax5070Registers.cs index ca79babe0..63bbaafa4 100644 --- a/csharp/Lib/Devices/Amax5070/Amax5070Registers.cs +++ b/csharp/Lib/Devices/Amax5070/Amax5070Registers.cs @@ -32,8 +32,8 @@ namespace InnovEnergy.Lib.Devices.Amax5070 [Coil(25)] public Boolean Relay20 { get; set; } // Relay5060 2 [Coil(26)] public Boolean Relay21 { get; set; } // Relay5060 2 - [Coil(27)] public Boolean Relay22 { get; set; } // Relay5060 2 - [Coil(28)] public Boolean Relay23 { get; set; } // Relay5060 2 + [Coil(27, writable: true)] public Boolean Relay22 { get; set; } + [Coil(28)] public Boolean Relay23 { get; set; } [Coil(33)] public Boolean Relay30 { get; set; } // Relay5060 3 [Coil(34)] public Boolean Relay31 { get; set; } // Relay5060 3 diff --git a/csharp/Lib/Devices/Kaco92L3/DataType/ControlMode.cs b/csharp/Lib/Devices/Kaco92L3/DataType/ControlMode.cs new file mode 100644 index 000000000..72fea9a6d --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/DataType/ControlMode.cs @@ -0,0 +1,14 @@ +namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +public enum ControlMode +{ + RpcLocal = 0, + RpcRemote = 1 +} + + +public enum EnableDisableEnum : ushort +{ + Disabled = 0, + Enabled = 1 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/DataType/CurrentState.cs b/csharp/Lib/Devices/Kaco92L3/DataType/CurrentState.cs new file mode 100644 index 000000000..8d5d11d17 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/DataType/CurrentState.cs @@ -0,0 +1,17 @@ +namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +public enum CurrentState +{ + Off = 1, + Sleeping = 2, + Starting = 3, + Mppt = 4, + Throttled = 5, + ShuttingDown = 6, + Fault = 7, + Standby = 8, + Precharge = 9, + GridPreConnected = 10, + GridConnected = 11, + NoErrorPending = 12 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/DataType/ErrorPcu.cs b/csharp/Lib/Devices/Kaco92L3/DataType/ErrorPcu.cs new file mode 100644 index 000000000..77de80505 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/DataType/ErrorPcu.cs @@ -0,0 +1,16 @@ +namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +public enum ErrorPcu +{ + NoEvent = 0, + OverTemp = 1, + OverVolt = 2, + UnderVolt = 3, + BattPolIncorrect = 4, + CounterTooHigh = 5, + DuringPrecharge = 6, + BattVoltOutOfRange = 7, + I2CComm = 8, + CanComm = 9, + SwitchOffAcDsp = 10, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/DataType/ReuqestedState.cs b/csharp/Lib/Devices/Kaco92L3/DataType/ReuqestedState.cs new file mode 100644 index 000000000..8e4ab2639 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/DataType/ReuqestedState.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +public enum ReuqestedState +{ + Off = 1, + Standby = 8, + GridPreConnected = 10, + GridConnected = 11, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/DataType/StatePcu.cs b/csharp/Lib/Devices/Kaco92L3/DataType/StatePcu.cs new file mode 100644 index 000000000..a341ffd70 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/DataType/StatePcu.cs @@ -0,0 +1,14 @@ +namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +public enum StatePcu +{ + WaitForStartup = 1, + Standby = 2, + SwitchRelMinus = 3, + SwitchRelPrecharge = 4, + SwitchRelPlus = 5, + Running = 6, + Cooldown = 7, + Error = 8, + ClearError = 9, +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/Kaco92L3.csproj b/csharp/Lib/Devices/Kaco92L3/Kaco92L3.csproj new file mode 100644 index 000000000..889d35e56 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/Kaco92L3.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + InnovEnergy.Lib.Devices.Kaco92L3; + preview + + + + + + + + + diff --git a/csharp/Lib/Devices/Kaco92L3/KacoDevice.cs b/csharp/Lib/Devices/Kaco92L3/KacoDevice.cs new file mode 100644 index 000000000..22b13ba37 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/KacoDevice.cs @@ -0,0 +1,49 @@ +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.Kaco92L3; + + +public class KacoDevice: ModbusDevice +{ + public KacoDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) : this(new TcpChannel(hostname, port), slaveId) + { + } + + public KacoDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId)) + { + } + + public KacoDevice(ModbusClient client) : base(client) + { + } + + public new KacoRecord? Read() + { + try + { + return base.Read(); + } + catch + { + "Failed to read data from Kaco".WriteLine(); + return null; + } + } + + + public new void Write(KacoRecord registers) + { + try + { + base.Write(registers); + } + catch (Exception e) + { + // TODO: Log + Console.WriteLine(e); + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs b/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs new file mode 100644 index 000000000..c88766213 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs @@ -0,0 +1,255 @@ +using InnovEnergy.Lib.Devices.Kaco92L3.DataType; + +namespace InnovEnergy.Lib.Devices.Kaco92L3; + +public partial class KacoRecord +{ + + private static float ScaleSunspec(Int16 value, Int16 sf) + { + // -32768 is SunSpec "not implemented" + if (value == -32768) + return float.NaN; + + if (sf == 0) + return value; + + return (float)(value * Math.Pow(10, sf)); + } + + private static float ScaleSunspec(UInt16 value, Int16 sf) + { + // interpret unsigned as signed when applying SF; range is the same + return ScaleSunspec(unchecked((Int16)value), sf); + } + + private static Int16 UnscaleSunspecToInt16(float value, Int16 sf) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + return -32768; // "not implemented" / invalid + + if (sf == 0) + return (Int16)Math.Round(value); + + var raw = value / (float)Math.Pow(10, sf); + return (Int16)Math.Round(raw); + } + + private static UInt16 UnscaleSunspecToUInt16(float value, Int16 sf) + { + var raw = UnscaleSunspecToInt16(value, sf); + return unchecked((UInt16)raw); + } + + /****************************** High-level API for Model 64201 ****************************/ + + // ─────────────────────────────────────────────── + // States & control + // ─────────────────────────────────────────────── + + // Header + public UInt16 BattCharId => _battCharId; // ID = 64202 + public UInt16 BattCharLength => _battCharLength; // L = 6 + (RBCount * 8) + + public ReuqestedState RequestedState + { + get => (ReuqestedState)_requestedState; + set => _requestedState = (UInt16)value; + } + + public CurrentState CurrentState => (CurrentState)_currentState; + + public ControlMode ControlMode + { + get => (ControlMode)_controlMode; + set => _controlMode = (UInt16)value; + } + + public bool IsGridConnected => CurrentState == CurrentState.GridConnected; + public bool IsStandby => CurrentState == CurrentState.Standby; + public bool IsOff => CurrentState == CurrentState.Off; + + // Watchdog seconds (no scale factor) + public UInt16 WatchdogSeconds + { + get => _watchdog; + set => _watchdog = value; + } + + + // ─────────────────────────────────────────────── + // Setpoints (scaled) + // ─────────────────────────────────────────────── + + /// Active power setpoint in percent of WMax [%]. + public float ActivePowerSetPercent + { + get => ScaleSunspec(_wSetPct, _wSetPctSf); + set => _wSetPct = UnscaleSunspecToInt16(value, _wSetPctSf); + } + + /// Reactive power setpoint in percent of SMax [%]. + public float ReactivePowerSetPercent + { + get => ScaleSunspec(_varWMaxSetPct, _varSetPctSf); + set => _varWMaxSetPct = UnscaleSunspecToInt16(value, _varSetPctSf); + } + + // ─────────────────────────────────────────────── + // Ramp parameters (scaled) + // ─────────────────────────────────────────────── + + /// Active power PT1 ramp time [s]. + public float ActivePowerRampTimeSeconds + { + get => ScaleSunspec(_wParamRmpTms, _rmpTmsSf); + set => _wParamRmpTms = UnscaleSunspecToUInt16(value, _rmpTmsSf); + } + + /// Active power ramp-down rate [% ref / min]. + public float ActivePowerRampDownPercentPerMin + { + get => ScaleSunspec(_wParamRmpDecTmn, _rmpIncDecSf); + set => _wParamRmpDecTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf); + } + + /// Active power ramp-up rate [% ref / min]. + public float ActivePowerRampUpPercentPerMin + { + get => ScaleSunspec(_wParamRmpIncTmn, _rmpIncDecSf); + set => _wParamRmpIncTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf); + } + + /// Reactive power PT1 ramp time [s]. + public float ReactivePowerRampTimeSeconds + { + get => ScaleSunspec(_varParamRmpTms, _rmpTmsSf); + set => _varParamRmpTms = UnscaleSunspecToUInt16(value, _rmpTmsSf); + } + + /// Reactive power ramp-down rate [% ref / min]. + public float ReactivePowerRampDownPercentPerMin + { + get => ScaleSunspec(_varParamRmpDecTmn, _rmpIncDecSf); + set => _varParamRmpDecTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf); + } + + /// Reactive power ramp-up rate [% ref / min]. + public float ReactivePowerRampUpPercentPerMin + { + get => ScaleSunspec(_varParamRmpIncTmn, _rmpIncDecSf); + set => _varParamRmpIncTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf); + } + + // Ramp enable flags + public EnableDisableEnum ActivePowerRampEnable + { + get => (EnableDisableEnum)_wParamEna; + set => _wParamEna = (UInt16)value; + } + + public EnableDisableEnum ReactivePowerRampEnable + { + get => (EnableDisableEnum)_varParamEna; + set => _varParamEna = (UInt16)value; + } + + // ─────────────────────────────────────────────── + // Status / error (read-only enum views) + // ─────────────────────────────────────────────── + + //public VendorStateEnum VendorState => (VendorStateEnum)_stVnd; + //public PuStateEnum PuState => (PuStateEnum)_stPu; + public StatePcu PcuState => (StatePcu)_stPcu; + public ErrorPcu PcuError => (ErrorPcu)_errPcu; + + public UInt16 BatteryCharVersion => _battCharVersion; + public UInt16 BatteryCharMinorVersion => _battCharVerMinor; + + /// + /// Scale factor for battery voltages (V_SF). + /// + public Int16 BatteryVoltageScaleFactor => _battCharVSf; + + /// + /// Scale factor for battery currents (A_SF). + /// + public Int16 BatteryCurrentScaleFactor => _battCharASf; + + // Helper wrappers for scaled values + private float ScaleBattVoltage(UInt16 raw) => ScaleSunspec(raw, _battCharVSf); + private float ScaleBattCurrent(UInt16 raw) => ScaleSunspec(raw, _battCharASf); + private UInt16 UnscaleBattVoltage(float value) => UnscaleSunspecToUInt16(value, _battCharVSf); + private UInt16 UnscaleBattCurrent(float value) => UnscaleSunspecToUInt16(value, _battCharASf); + + // ─────────────────────────────────────────────── + // Battery discharge limits (scaled, RW) + // ─────────────────────────────────────────────── + + /// Minimum discharge voltage [V]. + public float MinDischargeVoltage + { + get => ScaleBattVoltage(_disMinVRaw); + set => _disMinVRaw = UnscaleBattVoltage(value); + } + + /// Maximum discharge current [A]. + public float MaxDischargeCurrent + { + get => ScaleBattCurrent(_disMaxARaw); + set => _disMaxARaw = UnscaleBattCurrent(value); + } + + /// Discharge cutoff current [A]. If discharge current falls below this, it disconnects (optional according to sheet). + public float DischargeCutoffCurrent + { + get => ScaleBattCurrent(_disCutoffARaw); + set => _disCutoffARaw = UnscaleBattCurrent(value); + } + + // ─────────────────────────────────────────────── + // Battery charge limits (scaled, RW) + // ─────────────────────────────────────────────── + + /// Maximum charge voltage [V]. + public float MaxChargeVoltage + { + get => ScaleBattVoltage(_chaMaxVRaw); + set => _chaMaxVRaw = UnscaleBattVoltage(value); + } + + /// Maximum charge current [A]. + public float MaxChargeCurrent + { + get => ScaleBattCurrent(_chaMaxARaw); + set => _chaMaxARaw = UnscaleBattCurrent(value); + } + + /// Charge cutoff current [A]. If charge current falls below this, it disconnects. + public float ChargeCutoffCurrent + { + get => ScaleBattCurrent(_chaCutoffARaw); + set => _chaCutoffARaw = UnscaleBattCurrent(value); + } + + // ─────────────────────────────────────────────── + // Limit enable flag + // ─────────────────────────────────────────────── + + /// + /// When EnLimit = 1, new battery limits are activated. + /// + public EnableDisableEnum BatteryLimitsEnable + { + get => (EnableDisableEnum)_enLimitRaw; + set => _enLimitRaw = (UInt16)value; + } + + /// Convenience bool wrapper for EnLimit. + public bool BatteryLimitsEnabled + { + get => BatteryLimitsEnable == EnableDisableEnum.Enabled; + set => BatteryLimitsEnable = value ? EnableDisableEnum.Enabled : EnableDisableEnum.Disabled; + } + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs b/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs new file mode 100644 index 000000000..f164ed577 --- /dev/null +++ b/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.Lib.Devices.Kaco92L3; + +[AddressOffset(-1)] +public partial class KacoRecord +{ + /****************************** Holding registers – SunSpec Model 64201 ****************************/ + + // Model 64201 header + [HoldingRegister(41061)] private UInt16 _model64201Id; // 0xA065 (DID = 64201) + [HoldingRegister(41062)] private UInt16 _model64201Length; // 0xA066 (L = 52) + + // Version info + [HoldingRegister(41063)] private UInt16 _version; // 0xA067 + [HoldingRegister(41064)] private UInt16 _versionMinor; // 0xA068 + + // State control + [HoldingRegister(41065, writable: true)] private UInt16 _requestedState; // 0xA069 – RequestedState (enum16, RW) + [HoldingRegister(41066)] private UInt16 _currentState; // 0xA06A – CurrentState (enum16, R) + [HoldingRegister(41067, writable: true)] private UInt16 _controlMode; // 0xA06B – ControlMode (enum16, RW) + [HoldingRegister(41068)] private UInt16 _reserved7; // 0xA06C – Reserved + + // Watchdog / setpoints + //Enable Watchdog countdown. Register must be filled with the desired watchdog timeout in seconds. 0 means watchdog is disabled. It is recommended to re-write the register at least 10 seconds before the timeout is activated. + [HoldingRegister(41069, writable: true)] private UInt16 _watchdog; // 0xA06D – Watchdog (uint16, RW, seconds) 0 to 600 + [HoldingRegister (41070, writable: true)] private Int16 _wSetPct; // 0xA06E – WSetPct (int16, RW, %WMax) unscaled: -100 to 100. Set power output to specified level. + [HoldingRegister (41071, writable: true)] private Int16 _varWMaxSetPct; // 0xA06F – VarWMaxSetPct (int16, RW, %SMax) unscaled: -100 to 100 + + // Reserved padding + [HoldingRegister(41072)] private UInt16 _reserved11; // 0xA070 + [HoldingRegister(41073)] private UInt16 _reserved12; // 0xA071 + [HoldingRegister(41074)] private UInt16 _reserved13; // 0xA072 + [HoldingRegister(41075)] private UInt16 _reserved14; // 0xA073 + [HoldingRegister(41076)] private UInt16 _reserved15; // 0xA074 + [HoldingRegister(41077)] private UInt16 _reserved16; // 0xA075 + + // Status / error + [HoldingRegister(41078)] private UInt16 _stVnd; // 0xA076 – StVnd (enum16, R) PrologState + [HoldingRegister(41079)] private UInt16 _stPu; // 0xA077 – StPu (enum16, R) Power Unit State (DSP) + [HoldingRegister(41080)] private UInt16 _stPcu; // 0xA078 – StPcu (enum16, R) pcu state + [HoldingRegister(41081)] private UInt16 _errPcu; // 0xA079 – ErrPcu (enum16, R) pcu error + + // Active power ramp parameters + [HoldingRegister(41082, writable: true)] private UInt16 _wParamRmpTms; // 0xA07A – WParamRmpTms (uint16, RW, s) + [HoldingRegister(41083, writable: true)] private UInt16 _wParamRmpDecTmn; // 0xA07B – WParamRmpDecTmn (uint16, RW, %ref/min) + [HoldingRegister(41084, writable: true)] private UInt16 _wParamRmpIncTmn; // 0xA07C – WParamRmpIncTmn (uint16, RW, %ref/min) + + [HoldingRegister(41085)] private UInt16 _reserved24; // 0xA07D – Reserved + [HoldingRegister(41086)] private UInt16 _reserved25; // 0xA07E – Reserved + + [HoldingRegister(41087, writable: true)] private UInt16 _wParamEna; // 0xA07F – WParam_Ena (enum16, RW) WSet_Ena control 0 or 1 + + // Reactive power ramp parameters + [HoldingRegister(41088, writable: true)] private UInt16 _varParamRmpTms; // 0xA080 – VarParamRmpTms (uint16, RW, s) + [HoldingRegister(41089, writable: true)] private UInt16 _varParamRmpDecTmn; // 0xA081 – VarParamRmpDecTmn (uint16, RW, %ref/min) + [HoldingRegister(41090, writable: true)] private UInt16 _varParamRmpIncTmn; // 0xA082 – VarParamRmpIncTmn (uint16, RW, %ref/min) + + [HoldingRegister(41091)] private UInt16 _reserved30; // 0xA083 – Reserved + [HoldingRegister(41092)] private UInt16 _reserved31; // 0xA084 – Reserved + + [HoldingRegister(41093, writable: true)] private UInt16 _varParamEna; // 0xA085 – VarParam_Ena (enum16, RW) Enumerated valued. Percent limit VAr enable/disable control. + + // Measurements (read-only) + [HoldingRegister(41094)] private UInt16 _phaseVoltageAN; // 0xA086 – PhVphA (uint16, R, V, V_SF) + [HoldingRegister(41095)] private UInt16 _phaseVoltageBN; // 0xA087 – PhVphB (uint16, R, V, V_SF) + [HoldingRegister(41096)] private UInt16 _phaseVoltageCN; // 0xA088 – PhVphC (uint16, R, V, V_SF) + + [HoldingRegister (41097)] private Int16 _activePowerW; // 0xA089 – W (int16, R, W, W_SF) + [HoldingRegister (41098)] private Int16 _reactivePowerVar; // 0xA08A – VAR (int16, R, var, Var_SF) + [HoldingRegister (41099)] private Int16 _lineFrequencyHz; // 0xA08B – Hz (int16, R, Hz, Hz_SF) + + // Scale factors (SunSpec sunsf) + // Scale factor for active power percent. + [HoldingRegister(41107)] private Int16 _wSetPctSf; // 0xA0F3 – WSetPct_SF + // Scale factor for reactive power percent. + [HoldingRegister(41108)] private Int16 _varSetPctSf; // 0xA0F4 – VarSetPct_SF + // Scale factor for PT1 (ramp time). + [HoldingRegister(41109)] private Int16 _rmpTmsSf; // 0xA0F5 – RmpTms_SF + // Scale factor for increment and decrement ramps. + [HoldingRegister(41110)] private Int16 _rmpIncDecSf; // 0xA0F6 – RmpIncDec_SF + + // Header + [HoldingRegister(41115)] private UInt16 _battCharId; // ID = 64202 + [HoldingRegister(41116)] private UInt16 _battCharLength; // L = 6 + (RBCount * 8) + + // Fixed block + [HoldingRegister(41117)] private UInt16 _battCharVersion; // Version (uint16, R) + [HoldingRegister(41118)] private UInt16 _battCharVerMinor; // VerMinor (uint16, R) + [HoldingRegister(41119)] private UInt16 _battCharRsrvd1; // Rsrvd_1 (pad) + [HoldingRegister(41120)] private UInt16 _battCharRsrvd2; // Rsrvd_2 (pad) + [HoldingRegister (41121)] private Int16 _battCharVSf; // V_SF (sunsf, R) + [HoldingRegister (41122)] private Int16 _battCharASf; // A_SF (sunsf, R) + + // Repeating block #0 (you said there is only one block) + [HoldingRegister(41123, writable: true)] private UInt16 _disMinVRaw; // DisMinV (uint16, V, V_SF, RW) min. discharge voltage + [HoldingRegister(41124, writable: true)] private UInt16 _disMaxARaw; // DisMaxA (uint16, A, A_SF, RW)max. discharge current + [HoldingRegister(41125, writable: true)] private UInt16 _disCutoffARaw; // DisCutoffA (uint16, A, A_SF, RW)Disconnect if discharge current lower than DisCutoffA + [HoldingRegister(41126, writable: true)] private UInt16 _chaMaxVRaw; // ChaMaxV (uint16, V, V_SF, RW)max. charge voltage + [HoldingRegister(41127, writable: true)] private UInt16 _chaMaxARaw; // ChaMaxA (uint16, A, A_SF, RW)max. charge current + [HoldingRegister(41128, writable: true)] private UInt16 _chaCutoffARaw; // ChaCutoffA (uint16, A, A_SF, RW)Disconnect if charge current lower than ChaCuttoffA + [HoldingRegister(41129)] private UInt16 _battCharPad; // Pad (pad, R) + [HoldingRegister(41130, writable: true)] private UInt16 _enLimitRaw; // EnLimit (uint16, RW)new battery limits are activated when EnLimit is 1 + + + +} diff --git a/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj b/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj new file mode 100644 index 000000000..5a9ec8ba5 --- /dev/null +++ b/csharp/Lib/Devices/PLVario2Meter/PLVario2Meter.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + InnovEnergy.Lib.Devices.PLVario2Meter + preview + + + + + + + + + diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs new file mode 100644 index 000000000..385180012 --- /dev/null +++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterDevice.cs @@ -0,0 +1,47 @@ +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.PLVario2Meter; + +public class PlVarioMeterDevice: ModbusDevice +{ + public PlVarioMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 2) : this(new TcpChannel(hostname, port), slaveId) + { + } + + public PlVarioMeterDevice(Channel channel, Byte slaveId = 2) : base(new ModbusTcpClient(channel, slaveId)) + { + } + + public PlVarioMeterDevice(ModbusClient client) : base(client) + { + } + + public new PlVarioMeterRegisters? Read() + { + try + { + return base.Read(); + } + catch + { + "Failed to read data from PLVario-II meter".WriteLine(); + return null; + } + } + + public new void Write(PlVarioMeterRegisters registers) + { + try + { + base.Write(registers); + } + catch (Exception e) + { + // TODO: Log + Console.WriteLine(e); + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs new file mode 100644 index 000000000..393948719 --- /dev/null +++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRecord.cs @@ -0,0 +1,133 @@ +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.Lib.Devices.PLVario2Meter; + +public class PlVarioMeterRecord +{ + private readonly PlVarioMeterRegisters _raw; + + public PlVarioMeterRecord(PlVarioMeterRegisters raw) + { + _raw = raw ?? throw new ArgumentNullException(nameof(raw)); + } + + + static ushort SwapBytes16(ushort x) + => (ushort)((x << 8) | (x >> 8)); + + static float DecodeFloat_BADC(ushort w0, ushort w1) + { + // Build bytes: [B A D C] = swap bytes in each word, and use W1 then W0 + ushort w0s = SwapBytes16(w0); // F1AA + ushort w1s = SwapBytes16(w1); // 4247 + uint raw = ((uint)w1s << 16) | w0s; // 0x4247F1AA + return BitConverter.Int32BitsToSingle(unchecked((int)raw)); + } + + + + // ------------------------------------------------------------------------- + // Decoders (DCBA) from raw UInt16 pairs + // ------------------------------------------------------------------------- + private static uint DecodeUInt32DCBA(ushort lowWord, ushort highWord) + => ((uint)highWord << 16) | (uint)lowWord; + + private static float DecodeFloatDCBA(ushort lowWord, ushort highWord) + { + uint raw = ((uint)highWord << 16) | (uint)lowWord; + return BitConverter.Int32BitsToSingle(unchecked((int)raw)); + } + + private static Double F(ushort w0, ushort w1) => DecodeFloat_BADC(w0, w1); + private static UInt32 U32(ushort w0, ushort w1) => DecodeUInt32DCBA(w0, w1); + + // ------------------------------------------------------------------------- + // Time + // ------------------------------------------------------------------------- + /// Timestamp in seconds (as provided by the device). + public uint TimestampSeconds => U32(_raw.Timestamp_W0, _raw.Timestamp_W1); + + /// Timestamp interpreted as Unix epoch seconds (UTC). + public DateTimeOffset TimestampUtc => DateTimeOffset.FromUnixTimeSeconds(TimestampSeconds); + + // ------------------------------------------------------------------------- + // Voltages (phase-neutral and line-line) + // ------------------------------------------------------------------------- + public VoltageRms VoltageU1 => new(F(_raw.VoltageU1_W0, _raw.VoltageU1_W1)); // U-1 + public VoltageRms VoltageU2 => new(F(_raw.VoltageU2_W0, _raw.VoltageU2_W1)); // U-2 + public VoltageRms VoltageU3 => new(F(_raw.VoltageU3_W0, _raw.VoltageU3_W1)); // U-3 + + // Friendly aliases (match your naming style) + public VoltageRms GridAbLineVoltage => (VoltageRms)new(F(_raw.VoltageUL1_W0, _raw.VoltageUL1_W1)); + public VoltageRms GridBcLineVoltage => (VoltageRms)new(F(_raw.VoltageUL2_W0, _raw.VoltageUL2_W1)); + public VoltageRms GridCaLineVoltage => (VoltageRms)new(F(_raw.VoltageUL3_W0, _raw.VoltageUL3_W1)); + + // ------------------------------------------------------------------------- + // Frequency + // ------------------------------------------------------------------------- + public Double Frequency => (F(_raw.Frequency_W0, _raw.Frequency_W1)); + + // ------------------------------------------------------------------------- + // Currents + // ------------------------------------------------------------------------- + public CurrentRms CurrentI1 => new(F(_raw.CurrentI1_W0, _raw.CurrentI1_W1)); + public CurrentRms CurrentI2 => new(F(_raw.CurrentI2_W0, _raw.CurrentI2_W1)); + public CurrentRms CurrentI3 => new(F(_raw.CurrentI3_W0, _raw.CurrentI3_W1)); + public CurrentRms CurrentI4 => new(F(_raw.CurrentI4_W0, _raw.CurrentI4_W1)); // optional channel + public CurrentRms CurrentTotal => new(F(_raw.CurrentTotal_W0, _raw.CurrentTotal_W1)); + + // ------------------------------------------------------------------------- + // Active Power (kW) + // ------------------------------------------------------------------------- + public ActivePower ActivePowerL1 => new(F(_raw.ActivePowerP1_W0, _raw.ActivePowerP1_W1) * 1000); + public ActivePower ActivePowerL2 => new(F(_raw.ActivePowerP2_W0, _raw.ActivePowerP2_W1) * 1000); + public ActivePower ActivePowerL3 => new(F(_raw.ActivePowerP3_W0, _raw.ActivePowerP3_W1) * 1000); + public ActivePower ActivePowerCh4 => new(F(_raw.ActivePowerP4_W0, _raw.ActivePowerP4_W1) * 1000); // optional channel + public ActivePower ActivePowerTotal => new(F(_raw.ActivePowerTotal_W0, _raw.ActivePowerTotal_W1) * 1000); + + // If you later map import/export separately, you can redefine these. + // For now, the table provides only total P (signed), so we expose it as "GridPower". + public ActivePower GridPower => ActivePowerTotal; + + // ------------------------------------------------------------------------- + // Reactive Power (kVAr) + // ------------------------------------------------------------------------- + public ReactivePower ReactivePowerL1 => new(F(_raw.ReactivePowerQ1_W0, _raw.ReactivePowerQ1_W1)); + public ReactivePower ReactivePowerL2 => new(F(_raw.ReactivePowerQ2_W0, _raw.ReactivePowerQ2_W1)); + public ReactivePower ReactivePowerL3 => new(F(_raw.ReactivePowerQ3_W0, _raw.ReactivePowerQ3_W1)); + public ReactivePower ReactivePowerCh4 => new(F(_raw.ReactivePowerQ4_W0, _raw.ReactivePowerQ4_W1)); + public ReactivePower ReactivePowerTotal => new(F(_raw.ReactivePowerTotal_W0, _raw.ReactivePowerTotal_W1)); + + // Friendly alias + public ReactivePower GridReactivePower => ReactivePowerTotal; + + // ------------------------------------------------------------------------- + // Apparent Power (kVA) + // ------------------------------------------------------------------------- + public ApparentPower ApparentPowerL1 => new(F(_raw.ApparentPowerS1_W0, _raw.ApparentPowerS1_W1)); + public ApparentPower ApparentPowerL2 => new(F(_raw.ApparentPowerS2_W0, _raw.ApparentPowerS2_W1)); + public ApparentPower ApparentPowerL3 => new(F(_raw.ApparentPowerS3_W0, _raw.ApparentPowerS3_W1)); + public ApparentPower ApparentPowerCh4 => new(F(_raw.ApparentPowerS4_W0, _raw.ApparentPowerS4_W1)); + public ApparentPower ApparentPowerTotal => new(F(_raw.ApparentPowerTotal_W0, _raw.ApparentPowerTotal_W1)); + + // ------------------------------------------------------------------------- + // Power Factor + // ------------------------------------------------------------------------- + public Double PowerFactorL1 => (F(_raw.PowerFactorPF1_W0, _raw.PowerFactorPF1_W1)); + public Double PowerFactorL2 => (F(_raw.PowerFactorPF2_W0, _raw.PowerFactorPF2_W1)); + public Double PowerFactorL3 => (F(_raw.PowerFactorPF3_W0, _raw.PowerFactorPF3_W1)); + public Double PowerFactorCh4 => (F(_raw.PowerFactorPF4_W0, _raw.PowerFactorPF4_W1)); + public Double PowerFactorTotal => (F(_raw.PowerFactorTotal_W0, _raw.PowerFactorTotal_W1)); + + // ------------------------------------------------------------------------- + // Repeated totals (table shows duplicates at offsets 66..74) + // Keep them exposed in case your installation populates them differently. + // ------------------------------------------------------------------------- + public CurrentRms CurrentTotalRepeat => new(F(_raw.CurrentTotalRepeat_W0, _raw.CurrentTotalRepeat_W1)); + public ActivePower ActivePowerTotalRepeat => new(F(_raw.ActivePowerTotalRepeat_W0, _raw.ActivePowerTotalRepeat_W1)); + public ReactivePower ReactivePowerTotalRepeat => new(F(_raw.ReactivePowerTotalRepeat_W0, _raw.ReactivePowerTotalRepeat_W1)); + public ApparentPower ApparentPowerTotalRepeat => new(F(_raw.ApparentPowerTotalRepeat_W0, _raw.ApparentPowerTotalRepeat_W1)); + public Double PowerFactorTotalRepeat => (F(_raw.PowerFactorTotalRepeat_W0, _raw.PowerFactorTotalRepeat_W1)); + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs new file mode 100644 index 000000000..556ff1d71 --- /dev/null +++ b/csharp/Lib/Devices/PLVario2Meter/PlVarioMeterRegisters.cs @@ -0,0 +1,168 @@ +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; +using InnovEnergy.Lib.StatusApi.DeviceTypes; +using InnovEnergy.Lib.Units.Composite; +// ReSharper disable InconsistentNaming + +namespace InnovEnergy.Lib.Devices.PLVario2Meter; + +using Float32 = Single; + +// If your project already defines Float32 as an alias/struct, keep that. +// Otherwise, you can just use float. Here we use float for simplicity. +public class PlVarioMeterRegisters : IAc3Meter +{ + // ------------------------------------------------------------------------- + // Timestamp (UINT32) -> 2 x UInt16 + // Offset 0: Timestamp [s] + // ------------------------------------------------------------------------- + [HoldingRegister(1000, writable: false)] public UInt16 Timestamp_W0; // low word + [HoldingRegister(1001, writable: false)] public UInt16 Timestamp_W1; // high word + + // ------------------------------------------------------------------------- + // Voltages phase-neutral (FLOAT32) -> 2 x UInt16 each + // Offsets 2,4,6 + // ------------------------------------------------------------------------- + [HoldingRegister(1002, writable: false)] public UInt16 VoltageU1_W0; + [HoldingRegister(1003, writable: false)] public UInt16 VoltageU1_W1; + + [HoldingRegister(1004, writable: false)] public UInt16 VoltageU2_W0; + [HoldingRegister(1005, writable: false)] public UInt16 VoltageU2_W1; + + [HoldingRegister(1006, writable: false)] public UInt16 VoltageU3_W0; + [HoldingRegister(1007, writable: false)] public UInt16 VoltageU3_W1; + + // ------------------------------------------------------------------------- + // Voltages line-line (FLOAT32) -> 2 x UInt16 each + // Offsets 8,10,12 + // ------------------------------------------------------------------------- + [HoldingRegister(1008, writable: false)] public UInt16 VoltageUL1_W0; + [HoldingRegister(1009, writable: false)] public UInt16 VoltageUL1_W1; + + [HoldingRegister(1010, writable: false)] public UInt16 VoltageUL2_W0; + [HoldingRegister(1011, writable: false)] public UInt16 VoltageUL2_W1; + + [HoldingRegister(1012, writable: false)] public UInt16 VoltageUL3_W0; + [HoldingRegister(1013, writable: false)] public UInt16 VoltageUL3_W1; + + // ------------------------------------------------------------------------- + // Frequency (FLOAT32) -> 2 x UInt16 + // Offset 14 + // ------------------------------------------------------------------------- + [HoldingRegister(1014, writable: false)] public UInt16 Frequency_W0; + [HoldingRegister(1015, writable: false)] public UInt16 Frequency_W1; + + // ------------------------------------------------------------------------- + // Currents (FLOAT32) -> 2 x UInt16 each + // Offsets 16..24 + // ------------------------------------------------------------------------- + [HoldingRegister(1016, writable: false)] public UInt16 CurrentI1_W0; + [HoldingRegister(1017, writable: false)] public UInt16 CurrentI1_W1; + + [HoldingRegister(1018, writable: false)] public UInt16 CurrentI2_W0; + [HoldingRegister(1019, writable: false)] public UInt16 CurrentI2_W1; + + [HoldingRegister(1020, writable: false)] public UInt16 CurrentI3_W0; + [HoldingRegister(1021, writable: false)] public UInt16 CurrentI3_W1; + + [HoldingRegister(1022, writable: false)] public UInt16 CurrentI4_W0; + [HoldingRegister(1023, writable: false)] public UInt16 CurrentI4_W1; + + [HoldingRegister(1024, writable: false)] public UInt16 CurrentTotal_W0; + [HoldingRegister(1025, writable: false)] public UInt16 CurrentTotal_W1; + + // ------------------------------------------------------------------------- + // Active power P (FLOAT32, kW) -> 2 x UInt16 each + // Offsets 26..34 + // ------------------------------------------------------------------------- + [HoldingRegister(1026, writable: false)] public UInt16 ActivePowerP1_W0; + [HoldingRegister(1027, writable: false)] public UInt16 ActivePowerP1_W1; + + [HoldingRegister(1028, writable: false)] public UInt16 ActivePowerP2_W0; + [HoldingRegister(1029, writable: false)] public UInt16 ActivePowerP2_W1; + + [HoldingRegister(1030, writable: false)] public UInt16 ActivePowerP3_W0; + [HoldingRegister(1031, writable: false)] public UInt16 ActivePowerP3_W1; + + [HoldingRegister(1032, writable: false)] public UInt16 ActivePowerP4_W0; + [HoldingRegister(1033, writable: false)] public UInt16 ActivePowerP4_W1; + + [HoldingRegister(1034, writable: false)] public UInt16 ActivePowerTotal_W0; + [HoldingRegister(1035, writable: false)] public UInt16 ActivePowerTotal_W1; + + // ------------------------------------------------------------------------- + // Reactive power Q (FLOAT32, kVAr) -> 2 x UInt16 each + // Offsets 36..44 + // ------------------------------------------------------------------------- + [HoldingRegister(1036, writable: false)] public UInt16 ReactivePowerQ1_W0; + [HoldingRegister(1037, writable: false)] public UInt16 ReactivePowerQ1_W1; + + [HoldingRegister(1038, writable: false)] public UInt16 ReactivePowerQ2_W0; + [HoldingRegister(1039, writable: false)] public UInt16 ReactivePowerQ2_W1; + + [HoldingRegister(1040, writable: false)] public UInt16 ReactivePowerQ3_W0; + [HoldingRegister(1041, writable: false)] public UInt16 ReactivePowerQ3_W1; + + [HoldingRegister(1042, writable: false)] public UInt16 ReactivePowerQ4_W0; + [HoldingRegister(1043, writable: false)] public UInt16 ReactivePowerQ4_W1; + + [HoldingRegister(1044, writable: false)] public UInt16 ReactivePowerTotal_W0; + [HoldingRegister(1045, writable: false)] public UInt16 ReactivePowerTotal_W1; + + // ------------------------------------------------------------------------- + // Apparent power S (FLOAT32, kVA) -> 2 x UInt16 each + // Offsets 46..54 + // ------------------------------------------------------------------------- + [HoldingRegister(1046, writable: false)] public UInt16 ApparentPowerS1_W0; + [HoldingRegister(1047, writable: false)] public UInt16 ApparentPowerS1_W1; + + [HoldingRegister(1048, writable: false)] public UInt16 ApparentPowerS2_W0; + [HoldingRegister(1049, writable: false)] public UInt16 ApparentPowerS2_W1; + + [HoldingRegister(1050, writable: false)] public UInt16 ApparentPowerS3_W0; + [HoldingRegister(1051, writable: false)] public UInt16 ApparentPowerS3_W1; + + [HoldingRegister(1052, writable: false)] public UInt16 ApparentPowerS4_W0; + [HoldingRegister(1053, writable: false)] public UInt16 ApparentPowerS4_W1; + + [HoldingRegister(1054, writable: false)] public UInt16 ApparentPowerTotal_W0; + [HoldingRegister(1055, writable: false)] public UInt16 ApparentPowerTotal_W1; + + // ------------------------------------------------------------------------- + // Power factor PF (FLOAT32) -> 2 x UInt16 each + // Offsets 56..64 + // ------------------------------------------------------------------------- + [HoldingRegister(1056, writable: false)] public UInt16 PowerFactorPF1_W0; + [HoldingRegister(1057, writable: false)] public UInt16 PowerFactorPF1_W1; + + [HoldingRegister(1058, writable: false)] public UInt16 PowerFactorPF2_W0; + [HoldingRegister(1059, writable: false)] public UInt16 PowerFactorPF2_W1; + + [HoldingRegister(1060, writable: false)] public UInt16 PowerFactorPF3_W0; + [HoldingRegister(1061, writable: false)] public UInt16 PowerFactorPF3_W1; + + [HoldingRegister(1062, writable: false)] public UInt16 PowerFactorPF4_W0; + [HoldingRegister(1063, writable: false)] public UInt16 PowerFactorPF4_W1; + + [HoldingRegister(1064, writable: false)] public UInt16 PowerFactorTotal_W0; + [HoldingRegister(1065, writable: false)] public UInt16 PowerFactorTotal_W1; + + // ------------------------------------------------------------------------- + // Repeated totals (as shown in the table) at offsets 66..74 + // ------------------------------------------------------------------------- + [HoldingRegister(1066, writable: false)] public UInt16 CurrentTotalRepeat_W0; + [HoldingRegister(1067, writable: false)] public UInt16 CurrentTotalRepeat_W1; + + [HoldingRegister(1068, writable: false)] public UInt16 ActivePowerTotalRepeat_W0; + [HoldingRegister(1069, writable: false)] public UInt16 ActivePowerTotalRepeat_W1; + + [HoldingRegister(1070, writable: false)] public UInt16 ReactivePowerTotalRepeat_W0; + [HoldingRegister(1071, writable: false)] public UInt16 ReactivePowerTotalRepeat_W1; + + [HoldingRegister(1072, writable: false)] public UInt16 ApparentPowerTotalRepeat_W0; + [HoldingRegister(1073, writable: false)] public UInt16 ApparentPowerTotalRepeat_W1; + + [HoldingRegister(1074, writable: false)] public UInt16 PowerFactorTotalRepeat_W0; + [HoldingRegister(1075, writable: false)] public UInt16 PowerFactorTotalRepeat_W1; + + public Ac3Bus Ac { get; } +} diff --git a/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattErrorCode.cs b/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattErrorCode.cs new file mode 100644 index 000000000..3a231e963 --- /dev/null +++ b/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattErrorCode.cs @@ -0,0 +1,58 @@ +namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType; + +public enum GrowattErrorCode +{ + NoAlarm = 0, + ExportLimitationFailSafe = 311, + + DcBiasAbnormal = 400, + HighDcComponentOutputCurrent = 402, + BusVoltageSamplingAbnormal = 404, + RelayFault = 405, + OverTemperature = 408, + BusVoltageAbnormal = 409, + + InternalCommunicationFailure = 411, + TemperatureSensorDisconnected = 412, + IgbtDriveFault = 413, + EepromError = 414, + AuxiliaryPowerAbnormal = 415, + DcAcOvercurrentProtection = 416, + + CommunicationProtocolMismatch = 417, + DspComFirmwareMismatch = 418, + DspSoftwareHardwareMismatch = 419, + CpldAbnormal = 421, + RedundancySamplingInconsistent = 422, + PwmPassThroughSignalFailure = 423, + AfciSelfTestFailure = 425, + PvCurrentSamplingAbnormal = 426, + AcCurrentSamplingAbnormal = 427, + BusSoftbootFailure = 429, + EpoFault = 430, + MonitoringChipBootVerificationFailed = 431, + + BmsCommunicationFailure = 500, + BmsChargeDischargeFailure = 501, + BatteryVoltageLow = 502, + BatteryVoltageHigh = 503, + BatteryTemperatureAbnormal = 504, + BatteryReversed = 505, + BatteryOpenCircuit = 506, + BatteryOverloadProtection = 507, + Bus2VoltageAbnormal = 508, + + BatteryChargeOcp = 509, + BatteryDischargeOcp = 510, + BatterySoftStartFailed = 511, + + EpsOutputShortCircuited = 600, + OffGridBusVoltageLow = 601, + OffGridTerminalVoltageAbnormal = 602, + SoftStartFailed = 603, + OffGridOutputVoltageAbnormal = 604, + BalancedCircuitSelfTestFailed = 605, + HighDcComponentOutputVoltage = 606, + OffGridOutputOverload = 607, + OffGridParallelSignalAbnormal = 608 +} \ No newline at end of file diff --git a/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattWarningCode.cs b/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattWarningCode.cs new file mode 100644 index 000000000..97e0fd8ac --- /dev/null +++ b/csharp/Lib/Devices/WITGrowatt4-15K/DataType/GrowattWarningCode.cs @@ -0,0 +1,58 @@ +namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType; + +public enum GrowattWarningCode : UInt16 +{ + NoAlarm =0, + StringFault = 200, + PvStringPidQuickConnectAbnormal = 201, + DcSpdFunctionAbnormal = 202, + PvShortCircuited = 203, + PvBoostDriverAbnormal = 204, + AcSpdFunctionAbnormal = 205, + DcFuseBlown = 208, + + DcInputVoltageTooHigh = 209, + PvReversed = 210, + PidFunctionAbnormal = 219, + PvStringDisconnected = 220, + PvStringCurrentUnbalanced = 221, + + NoUtilityGrid = 300, + GridVoltageOutOfRange = 301, + GridFrequencyOutOfRange = 302, + Overload = 303, + MeterDisconnected = 308, + MeterReverselyConnected = 309, + LinePeVoltageAbnormal = 310, + PhaseSequenceError = 311, + + FanFailure = 400, + MeterAbnormal = 401, + OptimizerCommunicationAbnormal = 402, + OverTemperature = 407, + OverTemperatureAlarm = 408, + NtcTemperatureSensorBroken = 409, + SyncSignalAbnormal = 411, + GridStartupConditionsNotMet = 412, + + BatteryCommunicationFailure = 500, + BatteryDisconnected = 501, + BatteryVoltageTooHigh = 502, + BatteryVoltageTooLow = 503, + BatteryReverseConnected = 504, + LeadAcidTempSensorDisconnected = 505, + BatteryTemperatureOutOfRange = 506, + BmsFault = 507, + LithiumBatteryOverload = 508, + BmsCommunicationAbnormal = 509, + BatterySpdAbnormal = 510, + + OutputDcComponentBiasAbnormal = 600, + DcComponentOverHighOutputVoltage = 601, + OffGridOutputVoltageTooLow = 602, + OffGridOutputVoltageTooHigh = 603, + OffGridOutputOverCurrent = 604, + OffGridBusVoltageTooLow = 605, + OffGridOutputOverload = 606, + BalancedCircuitAbnormal = 609 +} diff --git a/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Api.cs b/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Api.cs index 4fd1ab729..4d29fc83d 100644 --- a/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Api.cs +++ b/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Api.cs @@ -42,10 +42,10 @@ public partial class WITGrowatRecord public BatteryoperatinStatus BatteryOperatingMode => (BatteryoperatinStatus) _batteryOperatingMode; public OperatingPriority OperatingMode => (OperatingPriority)_operatingPriority; - public UInt16 FaultMainCode => _faultMainCode; // need to pre proceesed - public UInt16 FaultSubCode => _faultSubCode; // need to pre proceesed - public UInt16 WarningMainCode => _warningMainCode; // need to pre proceesed - public UInt16 WarningSubCode => _warningSubCode; // need to pre proceesed + public GrowattErrorCode FaultMainCode => _faultMainCode; // need to pre proceesed + public UInt16 FaultSubCode => _faultSubCode; // need to pre proceesed + public GrowattWarningCode WarningMainCode => _warningMainCode; // need to pre proceesed + public UInt16 WarningSubCode => _warningSubCode; // need to pre proceesed public Voltage Pv1Voltage => _pv1Voltage; public Current Pv1Current => _pv1Current; @@ -96,10 +96,10 @@ public partial class WITGrowatRecord get => _VppProtocolVerNumber; } - public Boolean ControlPermession + public Boolean ControlPermission { - get => _ControlPermession; - set => _ControlPermession = value; + get => _ControlPermission; + set => _ControlPermission = value; } public Boolean EnableCommand @@ -172,16 +172,16 @@ public partial class WITGrowatRecord set => _BatteryMaxDischargePower = value; } - public Percent ChargeCutoffSocVoltage + public Voltage ChargeCutoffSocVoltage { - get => _BatteryChargeCutoffVoltage; - set => _BatteryChargeCutoffVoltage = (UInt16)value; + get => _batteryChargeCutoffVoltage; + set => _batteryChargeCutoffVoltage = (UInt16)value; } - public Percent DischargeCutoffVoltage + public Voltage DischargeCutoffVoltage { - get => _BatteryDischargeCutoffVoltage; - set => _BatteryDischargeCutoffVoltage = (UInt16)value; + get => _batteryDischargeCutoffVoltage; + set => _batteryDischargeCutoffVoltage = (UInt16)value; } public Percent LoadPriorityDischargeCutoffSoc @@ -249,9 +249,8 @@ public partial class WITGrowatRecord set => _batteryMaxDischargingCurrent = (UInt16) value; } - public Double Battery1MaxDischargingCurrent => _batteryMaxDischargingCurrent; - - public Double Battery1MaxChargingCurrent => _batteryMaxChargingCurrent; + public Double Battery1MaxDischargingCurrent => _batteryMaxDischargingCurrent; + public Double Battery1MaxChargingCurrent => _batteryMaxChargingCurrent; public Percent Battery1Soc => _BatterySoc1; public Percent Battery1Soh => _BatterySoh1; diff --git a/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Modbus.cs b/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Modbus.cs index 63ad4b751..b31a63090 100644 --- a/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Modbus.cs +++ b/csharp/Lib/Devices/WITGrowatt4-15K/WITGrowatRecord.Modbus.cs @@ -1,3 +1,4 @@ +using InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType; using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K; @@ -19,9 +20,9 @@ public partial class WITGrowatRecord [InputRegister(31002)] private UInt16 _operatingPriority; [InputRegister(31003)] private UInt16 _reserved1; [InputRegister(31004)] private UInt16 _reserved2; - [InputRegister(31005)] private UInt16 _faultMainCode;// Can we change this to warning? + [InputRegister(31005)] private GrowattErrorCode _faultMainCode;// Can we change this to warning? [InputRegister(31006)] private UInt16 _faultSubCode; // Can we change this to warning? - [InputRegister(31007)] private UInt16 _warningMainCode; + [InputRegister(31007)] private GrowattWarningCode _warningMainCode; [InputRegister(31008)] private UInt16 _warningSubCode; [InputRegister(31009)] private UInt16 _reserved3; // 31010–31099 — PV Parameters @@ -144,7 +145,7 @@ public partial class WITGrowatRecord [HoldingRegister(30030)] private UInt16 _BatteryType; [HoldingRegister(30099)] private UInt16 _VppProtocolVerNumber; - [HoldingRegister(30100, writable: true)] private Boolean _ControlPermession; // 0 Disabled, 1 enabled + [HoldingRegister(30100, writable: true)] private Boolean _ControlPermission; // 0 Disabled, 1 enabled [HoldingRegister(30101, writable: true)] private Boolean _EnableCommand; // 0: Off, 1: On; Defaut is 1; not stored, must enable this register to control inverter [HoldingRegister(30102)] private UInt16 _CountryRegionCode; [HoldingRegister(30103)] private UInt16 _Reserved8; @@ -166,7 +167,7 @@ public partial class WITGrowatRecord [HoldingRegister(30162)] private UInt16 _PowerFactor; // [0, 2000] ∪ [18000, 20000]; Default: 20000; Actual PF = (Register Value - 10000) [HoldingRegister(30203, writable : true)] private UInt16 _EmsCommunicationFailureTime; // [1,300] TODO to 30 - [HoldingRegister(30204, writable : true)] private Boolean _EnableEmsCommunicationFailureTime; // 0: disabled, 1 = enabled we should enable this TODO + [HoldingRegister(30204, writable : true)] private Boolean _EnableEmsCommunicationFailureTime; // 0: disabled, 1 = enabled we should not enable this the naming is not correct [HoldingRegister(30300)] private UInt16 _BatteryClusterIndex; // [0..3] [HoldingRegister(30400 , writable: true)] private UInt32 _BatteryMaxChargePower; // @@ -182,8 +183,8 @@ public partial class WITGrowatRecord [HoldingRegister(30474)] private UInt16 _ActualChargeDischargePowerControlValue; // [HoldingRegister(30475)] private UInt16 _OffGridDischargeCutoffSoc; // - [HoldingRegister(30496, writable: true, Scale = 0.1)] private UInt16 _BatteryChargeCutoffVoltage; // - [HoldingRegister(30497, writable: true, Scale = 0.1)] private UInt16 _BatteryDischargeCutoffVoltage; // + [HoldingRegister(30496, writable: true, Scale = 0.1)] private UInt16 _batteryChargeCutoffVoltage; // + [HoldingRegister(30497, writable: true, Scale = 0.1)] private UInt16 _batteryDischargeCutoffVoltage; // [HoldingRegister(30498, writable: true, Scale = 0.1)] private UInt16 _batteryMaxChargingCurrent; // [HoldingRegister(30499, writable: true, Scale = 0.1)] private UInt16 _batteryMaxDischargingCurrent; //*/ } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Channels/Channel.cs b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs index 676025c16..b229a3c35 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/Channel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs @@ -15,5 +15,6 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Channels { public abstract IReadOnlyList Read(int nBytes); public abstract void Write(IReadOnlyList bytes); + public virtual void ClearBuffers() { } } } diff --git a/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs index bafbe947c..1502ed0ee 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/ConnectionChannel.cs @@ -22,7 +22,11 @@ public abstract class ConnectionChannel : Channel, IDisposable _CloseAfterSuccessfulWrite = closeAfterSuccessfulWrite; _CloseAfterException = closeAfterException ?? (_ => true); } - + // 👇 NEW: optional per-connection buffer clear + protected virtual void ClearBuffers(T connection) + { + // default: do nothing + } public override IReadOnlyList Read(Int32 nBytes) { try @@ -66,7 +70,7 @@ public abstract class ConnectionChannel : Channel, IDisposable private T Connection => _Connection ??= Open(); - + private void Close() { diff --git a/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs index ed2b28e6b..3e98db65c 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/SerialPortChannel.cs @@ -36,7 +36,23 @@ public class SerialPortChannel : ConnectionChannel return serialPort; }; } - + + // 👇 NEW: this is where we actually flush the serial buffers + protected override void ClearBuffers(SerialPort serialPort) + { + try + { + serialPort.DiscardInBuffer(); + serialPort.DiscardOutBuffer(); + Console.WriteLine(" Buffer in and out discarded."); + } + catch + { + Console.WriteLine(" couldn't clear the Buffer ."); + // ignore if port is closed / disposed + } + } + protected override SerialPort Open() => _Open(); protected override void Close(SerialPort serialPort) => serialPort.Dispose(); diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs index acb13d413..35dfe08f5 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs @@ -36,4 +36,5 @@ public abstract class ModbusClient SlaveId = slaveId; Endian = endian; } + } \ 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 index 1a73e8142..df30304b8 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/Attributes/Coil.cs @@ -4,7 +4,11 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; public class Coil : ModbusBoolean { - public Coil(UInt16 address) : base(address, ModbusKind.Coil) + public bool Writable { get; } + + public Coil(ushort address, bool writable = true) + : base(address, ModbusKind.Coil) { + Writable = writable; } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs index 7e7907c0b..dbaa1f75e 100644 --- a/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs +++ b/csharp/Lib/Protocols/Modbus/Reflection/Batch.cs @@ -37,7 +37,7 @@ public static class Batches { if (CloseBatch(member)) { - //PrintBatchStart(batchMembers); + PrintBatchStart(batchMembers); yield return MakeBatch(mb, batchMembers); batchMembers = new List(); } @@ -47,7 +47,6 @@ public static class Batches if (batchMembers.Count > 0) { - //PrintBatchStart(batchMembers); yield return MakeBatch(mb, batchMembers); } @@ -74,8 +73,16 @@ public static class Batches : $"{first.StartAddress}-{last.EndAddress - 1}"; var writable = first.IsWritable ? "Writable" : "ReadOnly"; - Console.WriteLine($"📦 New Batch: {first.Kind} [{range}] ({writable})"); + + foreach (var member in members) + { + if (member.Kind == ModbusKind.Coil) + { + Console.WriteLine($"👀 Coil: {member.StartAddress}, Name: {member.Member.Name}, Writable: {member.IsWritable}"); + } + } } + private static Batch MakeBatch(ModbusClient modbusClient, IReadOnlyList members) { @@ -86,7 +93,7 @@ public static class Batches //var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil; var debugString = $"{kind}: {startAddress}" + (endAddress - 1 == startAddress ? "" : $"-{endAddress - 1}"); - var isWritable = members.Any(m => m.IsWritable); + var isWritable = kind == ModbusKind.Coil || members.Any(m => m.IsWritable); //foreach (var m in members) // Console.WriteLine($"🧪 Address {m.StartAddress} Writable: {m.IsWritable}"); diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs index b8c332943..d8ba2cabc 100644 --- a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs @@ -8,10 +8,12 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull { private readonly IReadOnlyList> _Batches; + private readonly ModbusClient _modbusClient; public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0) { _Batches = modbusClient.MakeBatchesFor(addressOffset); + _modbusClient = modbusClient; } public R Read() @@ -40,6 +42,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull //Console.WriteLine($"Reading { _Batches.Count } Modbus batches."); foreach (var batch in _Batches) { + _modbusClient.Channel.ClearBuffers(); batch.Read(record); //Thread.Sleep(30); // this added mainly for Growatt reading } @@ -50,6 +53,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull { foreach (var batch in _Batches) { + _modbusClient.Channel.ClearBuffers(); batch.Write(record); //Thread.Sleep(50); // this added mainly for Growatt reading } diff --git a/csharp/Sinexcel 12K TL/DataType/AlarmAndWarning.cs b/csharp/Sinexcel 12K TL/DataType/AlarmAndWarning.cs new file mode 100644 index 000000000..c78124ba2 --- /dev/null +++ b/csharp/Sinexcel 12K TL/DataType/AlarmAndWarning.cs @@ -0,0 +1,48 @@ +namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType; + +public enum SinexcelAbnormalFlag : ushort +{ + Normal = 0, + Abnormal = 1 +} + +public enum SinexcelInverterOperatingState : ushort +{ + Standby = 0, + Ready = 1, + OffGrid = 2, + OnGrid = 3 +} + +public enum SinexcelPvOperatingState : ushort +{ + Standby = 0, + Run = 1 +} + +public enum SinexcelBatteryOperatingState : ushort +{ + Standby = 0, + Charging = 1, + Discharging = 2, + FullyCharged = 3, + SelfInspection = 4 +} + +public enum SinexcelDcdcState : ushort +{ + Shutdown = 0, + Booting = 1 +} + +public enum SinexcelPowerGridStatus : ushort +{ + Normal = 0, + Abnormal = 1 +} + +public enum SinexcelOredState : ushort +{ + State0 = 0, State1, State2, State3, State4, + State5, State6, State7, State8, State9 +} \ No newline at end of file diff --git a/csharp/Sinexcel 12K TL/SinexcelRecord.Api.cs b/csharp/Sinexcel 12K TL/SinexcelRecord.Api.cs index 9e97fa23c..735bb1545 100644 --- a/csharp/Sinexcel 12K TL/SinexcelRecord.Api.cs +++ b/csharp/Sinexcel 12K TL/SinexcelRecord.Api.cs @@ -6,7 +6,193 @@ namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL; public partial class SinexcelRecord { - + + /* + // -------------------------------------------------------- + // Helpers + // -------------------------------------------------------- + private static ushort Bit(ushort v) => (ushort)(v & 0x01); + + private static ushort Compose3(ushort b0, ushort b1, ushort b2) + => (ushort)(Bit(b0) | (Bit(b1) << 1) | (Bit(b2) << 2)); + + private static ushort Compose4(ushort b0, ushort b1, ushort b2, ushort b3) + => (ushort)(Bit(b0) | (Bit(b1) << 1) | (Bit(b2) << 2) | (Bit(b3) << 3)); + + // ---- Operating states + public SinexcelInverterOperatingState InverterOperatingState + => (SinexcelInverterOperatingState)(_inverterOperatingState & 0x07); + + public SinexcelPvOperatingState Pv1OperatingState + => (SinexcelPvOperatingState)Bit(_pv1OperatingState); + + public SinexcelPvOperatingState Pv2OperatingState + => (SinexcelPvOperatingState)Bit(_pv2OperatingState); + + public SinexcelPvOperatingState Pv3OperatingState + => (SinexcelPvOperatingState)Bit(_pv3OperatingState); + + public SinexcelPvOperatingState Pv4OperatingState + => (SinexcelPvOperatingState)Bit(_pv4OperatingState); + + public SinexcelBatteryOperatingState Battery1OperatingState + => (SinexcelBatteryOperatingState)(_battery1OperatingState & 0x07); + + public SinexcelBatteryOperatingState Battery2OperatingState + => (SinexcelBatteryOperatingState)Compose3(_bat2StateBit0, _bat2StateBit1, _bat2StateBit2); + + public SinexcelOredState OredState + => (SinexcelOredState)Compose4(_oredStateBit0, _oredStateBit1, _oredStateBit2, _oredStateBit3); + + public SinexcelDcdcState DcdcStateDc + => (SinexcelDcdcState)Bit(_dcdcStateDc); + + public SinexcelDcdcState DcdcStateAc + => (SinexcelDcdcState)Bit(_dcdcStateAc); + + public SinexcelPowerGridStatus PowerGridStatus + => (SinexcelPowerGridStatus)Bit(_powerGridStatus); + + // ---- Alarm / fault flags + public SinexcelAbnormalFlag AbnormalGridVoltage => (SinexcelAbnormalFlag)Bit(_abnormalGridVoltage); + public SinexcelAbnormalFlag AbnormalGridFrequency => (SinexcelAbnormalFlag)Bit(_abnormalGridFrequency); + public SinexcelAbnormalFlag InvertedSequenceOfGridVoltage => (SinexcelAbnormalFlag)Bit(_invertedSequenceOfGridVoltage); + public SinexcelAbnormalFlag GridVoltagePhaseLoss => (SinexcelAbnormalFlag)Bit(_gridVoltagePhaseLoss); + public SinexcelAbnormalFlag AbnormalOutputVoltage => (SinexcelAbnormalFlag)Bit(_abnormalOutputVoltage); + public SinexcelAbnormalFlag AbnormalOutputFrequency => (SinexcelAbnormalFlag)Bit(_abnormalOutputFrequency); + public SinexcelAbnormalFlag AbnormalNullLine => (SinexcelAbnormalFlag)Bit(_abnormalNullLine); + public SinexcelAbnormalFlag ExcessivelyHighAmbientTemperature => (SinexcelAbnormalFlag)Bit(_excessivelyHighAmbientTemperature); + public SinexcelAbnormalFlag ExcessiveRadiatorTemperature => (SinexcelAbnormalFlag)Bit(_excessiveRadiatorTemperature); + public SinexcelAbnormalFlag InsulationFault => (SinexcelAbnormalFlag)Bit(_insulationFault); + public SinexcelAbnormalFlag LeakageProtectionFault => (SinexcelAbnormalFlag)Bit(_leakageProtectionFault); + public SinexcelAbnormalFlag AuxiliaryPowerFault => (SinexcelAbnormalFlag)Bit(_auxiliaryPowerFault); + public SinexcelAbnormalFlag FanFault => (SinexcelAbnormalFlag)Bit(_fanFault); + public SinexcelAbnormalFlag ModelCapacityFault => (SinexcelAbnormalFlag)Bit(_modelCapacityFault); + public SinexcelAbnormalFlag AbnormalLightningArrester => (SinexcelAbnormalFlag)Bit(_abnormalLightningArrester); + public SinexcelAbnormalFlag IslandProtection => (SinexcelAbnormalFlag)Bit(_islandProtection); + public SinexcelAbnormalFlag Battery1NotConnected => (SinexcelAbnormalFlag)Bit(_battery1NotConnected); + public SinexcelAbnormalFlag Battery1Overvoltage => (SinexcelAbnormalFlag)Bit(_battery1Overvoltage); + public SinexcelAbnormalFlag Battery1Undervoltage => (SinexcelAbnormalFlag)Bit(_battery1Undervoltage); + public SinexcelAbnormalFlag Battery1DischargeEnd => (SinexcelAbnormalFlag)Bit(_battery1DischargeEnd); + public SinexcelAbnormalFlag Battery1Inverted => (SinexcelAbnormalFlag)Bit(_battery1Inverted); + public SinexcelAbnormalFlag Battery2NotConnected => (SinexcelAbnormalFlag)Bit(_battery2NotConnected); + public SinexcelAbnormalFlag Battery2Overvoltage => (SinexcelAbnormalFlag)Bit(_battery2Overvoltage); + public SinexcelAbnormalFlag Battery2Undervoltage => (SinexcelAbnormalFlag)Bit(_battery2Undervoltage); + public SinexcelAbnormalFlag Battery2DischargeEnd => (SinexcelAbnormalFlag)Bit(_battery2DischargeEnd); + public SinexcelAbnormalFlag Battery2Inverted => (SinexcelAbnormalFlag)Bit(_battery2Inverted); + public SinexcelAbnormalFlag Pv1NotAccessed => (SinexcelAbnormalFlag)Bit(_pv1NotAccessed); + public SinexcelAbnormalFlag Pv1Overvoltage => (SinexcelAbnormalFlag)Bit(_pv1Overvoltage); + public SinexcelAbnormalFlag AbnormalPv1CurrentSharing => (SinexcelAbnormalFlag)Bit(_abnormalPv1CurrentSharing); + public SinexcelAbnormalFlag Pv2NotAccessed => (SinexcelAbnormalFlag)Bit(_pv2NotAccessed); + public SinexcelAbnormalFlag Pv2Overvoltage => (SinexcelAbnormalFlag)Bit(_pv2Overvoltage); + public SinexcelAbnormalFlag AbnormalPv2CurrentSharing => (SinexcelAbnormalFlag)Bit(_abnormalPv2CurrentSharing); + public SinexcelAbnormalFlag DcBusOvervoltage => (SinexcelAbnormalFlag)Bit(_dcBusOvervoltage); + public SinexcelAbnormalFlag DcBusUndervoltage => (SinexcelAbnormalFlag)Bit(_dcBusUndervoltage); + public SinexcelAbnormalFlag DcBusVoltageUnbalance => (SinexcelAbnormalFlag)Bit(_dcBusVoltageUnbalance); + public SinexcelAbnormalFlag Pv1PowerTubeFault => (SinexcelAbnormalFlag)Bit(_pv1PowerTubeFault); + public SinexcelAbnormalFlag Pv2PowerTubeFault => (SinexcelAbnormalFlag)Bit(_pv2PowerTubeFault); + public SinexcelAbnormalFlag Battery1PowerTubeFault => (SinexcelAbnormalFlag)Bit(_battery1PowerTubeFault); + public SinexcelAbnormalFlag Battery2PowerTubeFault => (SinexcelAbnormalFlag)Bit(_battery2PowerTubeFault); + public SinexcelAbnormalFlag InverterPowerTubeFault => (SinexcelAbnormalFlag)Bit(_inverterPowerTubeFault); + public SinexcelAbnormalFlag SystemOutputOverload => (SinexcelAbnormalFlag)Bit(_systemOutputOverload); + public SinexcelAbnormalFlag InverterOverload => (SinexcelAbnormalFlag)Bit(_inverterOverload); + public SinexcelAbnormalFlag InverterOverloadTimeout => (SinexcelAbnormalFlag)Bit(_inverterOverloadTimeout); + public SinexcelAbnormalFlag Battery1OverloadTimeout => (SinexcelAbnormalFlag)Bit(_battery1OverloadTimeout); + public SinexcelAbnormalFlag Battery2OverloadTimeout => (SinexcelAbnormalFlag)Bit(_battery2OverloadTimeout); + public SinexcelAbnormalFlag InverterSoftStartFailure => (SinexcelAbnormalFlag)Bit(_inverterSoftStartFailure); + public SinexcelAbnormalFlag Battery1SoftStartFailure => (SinexcelAbnormalFlag)Bit(_battery1SoftStartFailure); + public SinexcelAbnormalFlag Battery2SoftStartFailure => (SinexcelAbnormalFlag)Bit(_battery2SoftStartFailure); + public SinexcelAbnormalFlag Dsp1ParameterSettingFault => (SinexcelAbnormalFlag)Bit(_dsp1ParameterSettingFault); + public SinexcelAbnormalFlag Dsp2ParameterSettingFault => (SinexcelAbnormalFlag)Bit(_dsp2ParameterSettingFault); + public SinexcelAbnormalFlag DspVersionCompatibilityFault => (SinexcelAbnormalFlag)Bit(_dspVersionCompatibilityFault); + public SinexcelAbnormalFlag CpldVersionCompatibilityFault => (SinexcelAbnormalFlag)Bit(_cpldVersionCompatibilityFault); + public SinexcelAbnormalFlag CpldCommunicationFault => (SinexcelAbnormalFlag)Bit(_cpldCommunicationFault); + public SinexcelAbnormalFlag DspCommunicationFault => (SinexcelAbnormalFlag)Bit(_dspCommunicationFault); + public SinexcelAbnormalFlag OutputVoltageDcOverlimit => (SinexcelAbnormalFlag)Bit(_outputVoltageDcOverlimit); + public SinexcelAbnormalFlag OutputCurrentDcOverlimit => (SinexcelAbnormalFlag)Bit(_outputCurrentDcOverlimit); + public SinexcelAbnormalFlag RelaySelfCheckFails => (SinexcelAbnormalFlag)Bit(_relaySelfCheckFails); + public SinexcelAbnormalFlag AbnormalInverter => (SinexcelAbnormalFlag)Bit(_abnormalInverter); + public SinexcelAbnormalFlag PoorGrounding => (SinexcelAbnormalFlag)Bit(_poorGrounding); + public SinexcelAbnormalFlag Pv1SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv1SoftStartFailure); + public SinexcelAbnormalFlag Pv2SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv2SoftStartFailure2); + public SinexcelAbnormalFlag BalancedCircuitOverloadTimeout => (SinexcelAbnormalFlag)Bit(_balancedCircuitOverloadTimeout); + public SinexcelAbnormalFlag Pv1OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv1OverloadTimeout); + public SinexcelAbnormalFlag Pv2OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv2OverloadTimeout); + public SinexcelAbnormalFlag PcbOvertemperature => (SinexcelAbnormalFlag)Bit(_pcbOvertemperature); + public SinexcelAbnormalFlag DcConverterOvertemperature => (SinexcelAbnormalFlag)Bit(_dcConverterOvertemperature); + public SinexcelAbnormalFlag BusSlowOvervoltage => (SinexcelAbnormalFlag)Bit(_busSlowOvervoltage); + public SinexcelAbnormalFlag AbnormalOffGridOutputVoltage => (SinexcelAbnormalFlag)Bit(_abnormalOffGridOutputVoltage); + public SinexcelAbnormalFlag HardwareBusOvervoltage => (SinexcelAbnormalFlag)Bit(_hardwareBusOvervoltage); + public SinexcelAbnormalFlag HardwareOvercurrent => (SinexcelAbnormalFlag)Bit(_hardwareOvercurrent); + public SinexcelAbnormalFlag DcConverterOvervoltage => (SinexcelAbnormalFlag)Bit(_dcConverterOvervoltage); + public SinexcelAbnormalFlag DcConverterHardwareOvervoltage => (SinexcelAbnormalFlag)Bit(_dcConverterHardwareOvervoltage); + public SinexcelAbnormalFlag DcConverterOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterOvercurrent); + public SinexcelAbnormalFlag DcConverterHardwareOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterHardwareOvercurrent); + public SinexcelAbnormalFlag DcConverterResonatorOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterResonatorOvercurrent); + public SinexcelAbnormalFlag Pv1InsufficientPower => (SinexcelAbnormalFlag)Bit(_pv1InsufficientPower); + public SinexcelAbnormalFlag Pv2InsufficientPower => (SinexcelAbnormalFlag)Bit(_pv2InsufficientPower); + public SinexcelAbnormalFlag Battery1InsufficientPower => (SinexcelAbnormalFlag)Bit(_battery1InsufficientPower); + public SinexcelAbnormalFlag Battery2InsufficientPower => (SinexcelAbnormalFlag)Bit(_battery2InsufficientPower); + public SinexcelAbnormalFlag LithiumBattery1ChargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery1ChargeForbidden); + public SinexcelAbnormalFlag LithiumBattery1DischargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery1DischargeForbidden); + public SinexcelAbnormalFlag LithiumBattery2ChargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery2ChargeForbidden); + public SinexcelAbnormalFlag LithiumBattery2DischargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery2DischargeForbidden); + public SinexcelAbnormalFlag LithiumBattery1Full => (SinexcelAbnormalFlag)Bit(_lithiumBattery1Full); + public SinexcelAbnormalFlag LithiumBattery1DischargeEnd => (SinexcelAbnormalFlag)Bit(_lithiumBattery1DischargeEnd); + public SinexcelAbnormalFlag LithiumBattery2Full => (SinexcelAbnormalFlag)Bit(_lithiumBattery2Full); + public SinexcelAbnormalFlag LithiumBattery2DischargeEnd => (SinexcelAbnormalFlag)Bit(_lithiumBattery2DischargeEnd); + public SinexcelAbnormalFlag LoadPowerOverload => (SinexcelAbnormalFlag)Bit(_loadPowerOverload); + public SinexcelAbnormalFlag AbnormalLeakageSelfCheck => (SinexcelAbnormalFlag)Bit(_abnormalLeakageSelfCheck); + public SinexcelAbnormalFlag InverterOvertemperatureAlarm => (SinexcelAbnormalFlag)Bit(_inverterOvertemperatureAlarm); + public SinexcelAbnormalFlag InverterOvertemperature => (SinexcelAbnormalFlag)Bit(_inverterOvertemperature); + public SinexcelAbnormalFlag DcConverterOvertemperatureAlarm => (SinexcelAbnormalFlag)Bit(_dcConverterOvertemperatureAlarm); + public SinexcelAbnormalFlag ParallelCommunicationAlarm => (SinexcelAbnormalFlag)Bit(_parallelCommunicationAlarm); + public SinexcelAbnormalFlag SystemDerating => (SinexcelAbnormalFlag)Bit(_systemDerating); + public SinexcelAbnormalFlag InverterRelayOpen => (SinexcelAbnormalFlag)Bit(_inverterRelayOpen); + public SinexcelAbnormalFlag InverterRelayShortCircuit => (SinexcelAbnormalFlag)Bit(_inverterRelayShortCircuit); + public SinexcelAbnormalFlag PvAccessMethodErrorAlarm => (SinexcelAbnormalFlag)Bit(_pvAccessMethodErrorAlarm); + public SinexcelAbnormalFlag ParallelModuleMissing => (SinexcelAbnormalFlag)Bit(_parallelModuleMissing); + public SinexcelAbnormalFlag DuplicateMachineNumbersForParallelModules=> (SinexcelAbnormalFlag)Bit(_duplicateMachineNumbersForParallelModules); + public SinexcelAbnormalFlag ParameterConflictInParallelModule => (SinexcelAbnormalFlag)Bit(_parameterConflictInParallelModule); + public SinexcelAbnormalFlag ReservedAlarms4 => (SinexcelAbnormalFlag)Bit(_reservedAlarms4); + public SinexcelAbnormalFlag ReverseMeterConnection => (SinexcelAbnormalFlag)Bit(_reverseMeterConnection); + public SinexcelAbnormalFlag InverterSealPulse => (SinexcelAbnormalFlag)Bit(_inverterSealPulse); + public SinexcelAbnormalFlag Pv3NotConnected => (SinexcelAbnormalFlag)Bit(_pv3NotConnected); + public SinexcelAbnormalFlag Pv3Overvoltage => (SinexcelAbnormalFlag)Bit(_pv3Overvoltage); + public SinexcelAbnormalFlag Pv3AverageCurrentAnomaly => (SinexcelAbnormalFlag)Bit(_pv3AverageCurrentAnomaly); + public SinexcelAbnormalFlag Pv4NotConnected => (SinexcelAbnormalFlag)Bit(_pv4NotConnected); + public SinexcelAbnormalFlag Pv4Overvoltage => (SinexcelAbnormalFlag)Bit(_pv4Overvoltage); + public SinexcelAbnormalFlag Pv4AverageCurrentAnomaly => (SinexcelAbnormalFlag)Bit(_pv4AverageCurrentAnomaly); + public SinexcelAbnormalFlag Pv3PowerTubeFailure => (SinexcelAbnormalFlag)Bit(_pv3PowerTubeFailure); + public SinexcelAbnormalFlag Pv4PowerTubeFailure => (SinexcelAbnormalFlag)Bit(_pv4PowerTubeFailure); + public SinexcelAbnormalFlag Pv3SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv3SoftStartFailure); + public SinexcelAbnormalFlag Pv4SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv4SoftStartFailure); + public SinexcelAbnormalFlag Pv3OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv3OverloadTimeout); + public SinexcelAbnormalFlag Pv4OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv4OverloadTimeout); + public SinexcelAbnormalFlag Pv3ReverseConnection => (SinexcelAbnormalFlag)Bit(_pv3ReverseConnection); + public SinexcelAbnormalFlag Pv4ReverseConnection => (SinexcelAbnormalFlag)Bit(_pv4ReverseConnection); + public SinexcelAbnormalFlag AbnormalDieselGeneratorVoltage => (SinexcelAbnormalFlag)Bit(_abnormalDieselGeneratorVoltage); + public SinexcelAbnormalFlag AbnormalDieselGeneratorFrequency => (SinexcelAbnormalFlag)Bit(_abnormalDieselGeneratorFrequency); + public SinexcelAbnormalFlag DieselGeneratorVoltageReverseSequence => (SinexcelAbnormalFlag)Bit(_dieselGeneratorVoltageReverseSequence); + public SinexcelAbnormalFlag DieselGeneratorVoltageOutOfPhase => (SinexcelAbnormalFlag)Bit(_dieselGeneratorVoltageOutOfPhase); + public SinexcelAbnormalFlag LeadBatteryTemperatureAbnormality => (SinexcelAbnormalFlag)Bit(_leadBatteryTemperatureAbnormality); + public SinexcelAbnormalFlag BatteryAccessMethodError => (SinexcelAbnormalFlag)Bit(_batteryAccessMethodError); + public SinexcelAbnormalFlag ReservedAlarms5 => (SinexcelAbnormalFlag)Bit(_reservedAlarms5); + public SinexcelAbnormalFlag Battery1BackupProhibited => (SinexcelAbnormalFlag)Bit(_battery1BackupProhibited); + public SinexcelAbnormalFlag Battery2BackupProhibited => (SinexcelAbnormalFlag)Bit(_battery2BackupProhibited); + public SinexcelAbnormalFlag AbnormalGridCurrent => (SinexcelAbnormalFlag)Bit(_abnormalGridCurrent); + public SinexcelAbnormalFlag GeneratorOverload => (SinexcelAbnormalFlag)Bit(_generatorOverload); + public SinexcelAbnormalFlag BusSoftStartFailure => (SinexcelAbnormalFlag)Bit(_busSoftStartFailure); + public SinexcelAbnormalFlag OpenCircuitOfPowerGridRelay => (SinexcelAbnormalFlag)Bit(_openCircuitOfPowerGridRelay); + public SinexcelAbnormalFlag ShortCircuitOfPowerGridRelay => (SinexcelAbnormalFlag)Bit(_shortCircuitOfPowerGridRelay); + public SinexcelAbnormalFlag GeneratorRelayOpenCircuit => (SinexcelAbnormalFlag)Bit(_generatorRelayOpenCircuit); + public SinexcelAbnormalFlag GeneratorRelayShortCircuit => (SinexcelAbnormalFlag)Bit(_generatorRelayShortCircuit); + public SinexcelAbnormalFlag InsufficientPhotovoltaicPower => (SinexcelAbnormalFlag)Bit(_insufficientPhotovoltaicPower); + public SinexcelAbnormalFlag Photovoltaic1Overcurrent => (SinexcelAbnormalFlag)Bit(_photovoltaic1Overcurrent); + */ + + + private Int16 _factorFromKwtoW = 1000; // ─────────────────────────────────────────────── // Public API — Decoded Float Values @@ -153,7 +339,7 @@ public partial class SinexcelRecord public Voltage Battery1Voltage => ConvertBitPatternToFloat(_batteryVoltage1); public Current Battery1Current => ConvertBitPatternToFloat(_batteryCurrent1); - public ActivePower Battery1Power => ConvertBitPatternToFloat(_batteryPower1) * _factorFromKwtoW; + public ActivePower Battery1Power => ConvertBitPatternToFloat(_batteryPower1) * _factorFromKwtoW; public Percent Battery1Soc => ConvertBitPatternToFloat(_batterySoc1); public float BatteryFullLoadDuration1 => ConvertBitPatternToFloat(_batteryFullLoadDuration1); // this is in hour @@ -190,14 +376,15 @@ public partial class SinexcelRecord public ActivePower TotalGridPower => ConvertBitPatternToFloat(_totalGridPower) * _factorFromKwtoW ; public ActivePower ImportantLoadTotalPower => ConvertBitPatternToFloat(_importantLoadTotalPower)* _factorFromKwtoW; public ActivePower GeneralLoadTotalPower => ConvertBitPatternToFloat(_generalLoadTotalPower)* _factorFromKwtoW; - public Voltage Pv3Voltage => ConvertBitPatternToFloat(_pv3Voltage); - public Current Pv3Current => ConvertBitPatternToFloat(_pv3Current); - public ActivePower Pv3Power => ConvertBitPatternToFloat(_pv3Power) * _factorFromKwtoW; - public Voltage Pv4Voltage => ConvertBitPatternToFloat(_pv4Voltage); - public Current Pv4Current => ConvertBitPatternToFloat(_pv4Current); - public ActivePower Pv4Power => ConvertBitPatternToFloat(_pv4Power) * _factorFromKwtoW; + public Voltage PvVoltage3 => ConvertBitPatternToFloat(_pv3Voltage); + public Current PvCurrent3 => ConvertBitPatternToFloat(_pv3Current); + public ActivePower PvPower3 => ConvertBitPatternToFloat(_pv3Power) * _factorFromKwtoW; + public Voltage PvVoltage4 => ConvertBitPatternToFloat(_pv4Voltage); + public Current PvCurrent4 => ConvertBitPatternToFloat(_pv4Current); + public ActivePower PvPower4 => ConvertBitPatternToFloat(_pv4Power) * _factorFromKwtoW; public ActivePower GeneratorTotalPower => ConvertBitPatternToFloat(_generatorTotalPower); + public ActivePower PvTotalPower => PvPower1 + PvPower2 + PvPower3 + PvPower4; // ─────────────────────────────────────────────── // Manufacturer Information & Software Versions // ─────────────────────────────────────────────── @@ -215,269 +402,292 @@ public partial class SinexcelRecord public ApparentPower RatedPowerKva => _ratedPower / 100f; // 0x2008 (value / 100) public SinexcelModel Model => (SinexcelModel)_model; // 0x200D - //public ThreePhaseWireSystem ThreePhaseWireSystem - //{ - // get => (ThreePhaseWireSystem)_threePhaseWireSystem; - // //set => _threePhaseWireSystem = (UInt32)value; - //} + public ThreePhaseWireSystem ThreePhaseWireSystem + { + get => (ThreePhaseWireSystem)_threePhaseWireSystem; + set => _threePhaseWireSystem = (UInt32)value; + } // - //public InputFrequency InputFrequencyClass - //{ - // get => (InputFrequency)_inputFrequencyClass; - // //set => _inputFrequencyClass = (UInt32)value; - //} + public InputFrequency InputFrequencyClass + { + get => (InputFrequency)_inputFrequencyClass; + set => _inputFrequencyClass = (UInt32)value; + } // public WorkingMode WorkingMode { - get => (WorkingMode)(int)BitConverter.Int32BitsToSingle(unchecked((int)_workingMode)); - set => _workingMode = BitConverter.ToUInt32(BitConverter.GetBytes((float)value), 0); + get => (WorkingMode)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_workingMode)); + set => _workingMode = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0); } + + public GridSwitchMethod GridSwitchMethod + { + get => (GridSwitchMethod)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_methodSwitchMode)); + set => _methodSwitchMode = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0); + } + + // ─────────────────────────────────────────────── + // Inverter Control Configuration + // ─────────────────────────────────────────────── + public SinexcelUnbalanceCompensation EnableOnGridUnbalanceCompensation + { + get => (SinexcelUnbalanceCompensation)_enableOnGridUnbalanceCompensation; + set => _enableOnGridUnbalanceCompensation = (UInt32)value; + } +/* + public SinexcelTemperatureDrop TemperatureDrop + { + get => (SinexcelTemperatureDrop)ConvertBitPatternToFloat(_temperatureDrop); + //set => _temperatureDrop = (UInt32)value; + } // - //public GridSwitchMethod GridSwitchMethod - //{ - // get => (GridSwitchMethod)_methodSwitchMode; - // //set => _methodSwitchMode = (UInt32)value; - //} + public SinexcelHvrt Hvrt + { + get => (SinexcelHvrt)_hvrt; + // set => _hvrt = (UInt32)value; + } // - //// ─────────────────────────────────────────────── - //// Inverter Control Configuration - //// ─────────────────────────────────────────────── - //public SinexcelUnbalanceCompensation EnableOnGridUnbalanceCompensation - //{ - // get => (SinexcelUnbalanceCompensation)_enableOnGridUnbalanceCompensation; - // //set => _enableOnGridUnbalanceCompensation = (UInt32)value; - //} -// - //public SinexcelTemperatureDrop TemperatureDrop - //{ - // get => (SinexcelTemperatureDrop)ConvertBitPatternToFloat(_temperatureDrop); - // //set => _temperatureDrop = (UInt32)value; - //} -// - //public SinexcelHvrt Hvrt - //{ - // get => (SinexcelHvrt)_hvrt; - // //set => _hvrt = (UInt32)value; - //} -// - //public SinexcelLvrt Lvrt - //{ - // get => (SinexcelLvrt)_lvrt; - // //set => _lvrt = (UInt32)value; - //} + public SinexcelLvrt Lvrt + { + get => (SinexcelLvrt)_lvrt; + // set => _lvrt = (UInt32)value; + } // // F//an Gear — numeric [0.5 ~ 1.0], default 1.0 - //public float FanGear - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_fanGear)); - // //set => _fanGear = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} -// + public float FanGear + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_fanGear)); + // set => _fanGear = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } +*/ //// ─────────────────────────────────────────────── //// Battery Configuration //// ─────────────────────────────────────────────── - //public SinexcelBatteryAccessMethod BatteryAccessMethod - //{ - // get => (SinexcelBatteryAccessMethod)_batteryAccessMethod; - // //set => _batteryAccessMethod = (UInt32)value; - //} - // - //public SinexcelMeterAccessEnable MeterAccessEnable - //{ - // get => (SinexcelMeterAccessEnable)ConvertBitPatternToFloat(_meterAccessEnable); - // //set => _meterAccessEnable = (UInt32)value; - //} - // - //public SinexcelBatteryEnable EnableBattery1 - //{ - // get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery1); - // //set => _enableBattery1 = (UInt32)value; - //} - // - //public SinexcelBatteryEnable EnableBattery2 - //{ - // get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery2); - // //set => _enableBattery2 = (UInt32)value; - //} - // - //public SinexcelPvEnable EnablePv1 - //{ - // get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv1); - // //set => _enablePv1 = (UInt32)value; - //} - // - //public SinexcelPvEnable EnablePv2 - //{ - // get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv2); - // //set => _enablePv2 = (UInt32)value; - //} - // - //public SinexcelBatteryType BatteryType - //{ - // get => (SinexcelBatteryType)_batteryType; - // //set => _batteryType = (UInt32)value; - //} - // - //public float BatteryCapacity1 - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCapacity1)); - // //set => _batteryCapacity1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery1MaxChargingCurrent - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxChargingCurrentBattery1)); - // //set => _maxChargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery1MaxDischargingCurrent - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxDischargingCurrentBattery1)); - // //set => _maxDischargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float RatedBatteryVoltage1 - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_ratedBatteryVoltage1)); - // //set => _ratedBatteryVoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery1MinSoc - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_minSocBattery1)); - // //set => _minSocBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float SetValueBattery1 - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_setValueBattery1)); - // //set => _setValueBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public SinexcelActiveChargeDischarge ActiveChargeDischarge - //{ - // get => (SinexcelActiveChargeDischarge)ConvertBitPatternToFloat(_activeChargeDischarge); - // //set => _activeChargeDischarge = (UInt32)value; - //} - // - //public float ActiveChargeDischargePower - //{ - // get => ConvertBitPatternToFloat(_activeChargeDischargePower) * _factorFromKwtoW; - // //set => _activeChargeDischargePower = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); // we should check this may need to convert from W to KW - //} - // - //// ─────────────────────────────────────────────── - //// Protection & PV Mode - //// ─────────────────────────────────────────────── - //public SinexcelIslandProtection EnableIslandProtection - //{ - // get => (SinexcelIslandProtection)_enableIslandProtection; - // //set => _enableIslandProtection = (UInt32)value; - //} - // - //public SinexcelPvAccessMode PvAccessMode - //{ - // get => (SinexcelPvAccessMode)_pvAccessMode; - // //set => _pvAccessMode = (UInt32)value; - //} - // - //// ─────────────────────────────────────────────── - //// System-Level Parameters - //// ─────────────────────────────────────────────── - //public float OutputVoltageAdjustmentFactor - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_outputVoltageAdjustmentFactor)); - // //set => _outputVoltageAdjustmentFactor = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float SetValueBatteryUndervoltage1 - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_setValueBatteryUndervoltage1)); - // //set => _setValueBatteryUndervoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float InverterPowerLimit - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_inverterPowerLimit)); - // //set => _inverterPowerLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //// ─────────────────────────────────────────────── - //// Battery 2 Parameters - //// ─────────────────────────────────────────────── - //public float Battery2Capacity - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2Capacity)); - // //set => _battery2Capacity = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} -// - //public float Battery2MaxChargingCurrent - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_maxChargingCurrentBattery2)); - // //set => _maxChargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery2MaxDischargingCurrent - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_maxDischargingCurrentBattery2)); - // //set => _maxDischargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery2RatedVoltage - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2RatedVoltage)); - // //set => _battery2RatedVoltage = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery2MinSoc - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2MinSoc)); - // //set => _battery2MinSoc = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery2OverVoltageSetting - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2OverVoltageSetting)); - // //set => _battery2OverVoltageSetting = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //public float Battery2UnderVoltageSetpoint - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2UnderVoltageSetpoint)); - // //set => _battery2UnderVoltageSetpoint = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // - //// ─────────────────────────────────────────────── - //// Parallel / System Settings - //// ─────────────────────────────────────────────── - //public SinexcelMachineMode MachineMode - //{ - // get => (SinexcelMachineMode)ConvertBitPatternToFloat(_singleOrParallelMachine); - // //set => _singleOrParallelMachine = (UInt32)value; - //} - // - //public UInt32 NumberOfSystemModules - //{ - // get => (UInt32)ConvertBitPatternToFloat(_numberOfSystemModules); - // //set => _numberOfSystemModules = value; - //} - // - //public UInt32 ParallelModuleMachineNumber - //{ - // get => (UInt32)ConvertBitPatternToFloat(_parallelModuleMachineNumber); - // //set => _parallelModuleMachineNumber = value; - //} - // - //public AccreditedCountry AccreditedCountry - //{ - // get => (AccreditedCountry)ConvertBitPatternToFloat(_accreditedCountries); - // //set => _accreditedCountries = (UInt32)value; - //} - // - //// ─────────────────────────────────────────────── - //// Control Commands - //// ─────────────────────────────────────────────── + public SinexcelBatteryAccessMethod BatteryAccessMethod + { + get => (SinexcelBatteryAccessMethod)_batteryAccessMethod; + set => _batteryAccessMethod = (UInt32)value; + } + + public SinexcelMeterAccessEnable MeterAccessEnable + { + get => (SinexcelMeterAccessEnable)ConvertBitPatternToFloat(_meterAccessEnable); + set => _meterAccessEnable = (UInt32)value; + } + + public SinexcelBatteryEnable EnableBattery1 + { + get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery1); + set => _enableBattery1 = (UInt32)value; + } + + public SinexcelBatteryEnable EnableBattery2 + { + get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery2); + set => _enableBattery2 = (UInt32)value; + } + + public SinexcelPvEnable EnablePv1 + { + get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv1); + set => _enablePv1 = (UInt32)value; + } + + public SinexcelPvEnable EnablePv2 + { + get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv2); + set => _enablePv2 = (UInt32)value; + } + + public SinexcelBatteryType BatteryType + { + get => (SinexcelBatteryType)_batteryType; + set => _batteryType = (UInt32)value; + } + + public float BatteryCapacity1 + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCapacity1)); + set => _batteryCapacity1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery1MaxChargingCurrent + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxChargingCurrentBattery1)); + set => _maxChargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery1MaxDischargingCurrent + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxDischargingCurrentBattery1)); + set => _maxDischargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float RatedBatteryVoltage1 + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_ratedBatteryVoltage1)); + set => _ratedBatteryVoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery1MinSoc + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_minSocBattery1)); + set => _minSocBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float SetValueBattery1 + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_setValueBattery1)); + set => _setValueBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + /* public SinexcelActiveChargeDischarge ActiveChargeDischarge + { + get => (SinexcelActiveChargeDischarge)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_activeChargeDischarge)); + set => _activeChargeDischarge = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0); + }*/ + + public float ActiveChargeDischargePower + { + get => ConvertBitPatternToFloat(_activeChargeDischargePower) * _factorFromKwtoW; + set => _activeChargeDischargePower = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); // we should check this may need to convert from W to KW + } + // ─────────────────────────────────────────────── + // Protection & PV Mode + // ─────────────────────────────────────────────── + public SinexcelIslandProtection EnableIslandProtection + { + get => (SinexcelIslandProtection)_enableIslandProtection; + set => _enableIslandProtection = (UInt32)value; + } + + public SinexcelPvAccessMode PvAccessMode + { + get => (SinexcelPvAccessMode)_pvAccessMode; + set => _pvAccessMode = (UInt32)value; + } + + // ─────────────────────────────────────────────── + // System-Level Parameters + // ─────────────────────────────────────────────── + public float OutputVoltageAdjustmentFactor + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_outputVoltageAdjustmentFactor)); + set => _outputVoltageAdjustmentFactor = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float SetValueBatteryUndervoltage1 + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_setValueBatteryUndervoltage1)); + set => _setValueBatteryUndervoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float InverterPowerLimit + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_inverterPowerLimit)); + set => _inverterPowerLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + // ─────────────────────────────────────────────── + // Battery 2 Parameters + // ─────────────────────────────────────────────── + public float Battery2Capacity + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2Capacity)); + set => _battery2Capacity = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + public float Battery2MaxChargingCurrent + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_maxChargingCurrentBattery2)); + set => _maxChargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2MaxDischargingCurrent + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_maxDischargingCurrentBattery2)); + set => _maxDischargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2RatedVoltage + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2RatedVoltage)); + set => _battery2RatedVoltage = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2MinSoc + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2MinSoc)); + set => _battery2MinSoc = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2OverVoltageSetting + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2OverVoltageSetting)); + set => _battery2OverVoltageSetting = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2UnderVoltageSetpoint + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2UnderVoltageSetpoint)); + set => _battery2UnderVoltageSetpoint = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery1BackupSoc + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery1BackupSOC)); + set => _battery1BackupSOC = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float Battery2BackupSoc + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2BackupSOC)); + set => _battery2BackupSOC = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + // to be tested + public float EnableGridExport + + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_enableGridExport)); + set => _enableGridExport = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + public float PowerGridExportLimit + { + get => BitConverter.Int32BitsToSingle(unchecked((int)_powerGridExportLimit)); + set => _powerGridExportLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + // ─────────────────────────────────────────────── + // Parallel / System Settings + // ─────────────────────────────────────────────── + public SinexcelMachineMode MachineMode + { + get => (SinexcelMachineMode)ConvertBitPatternToFloat(_singleOrParallelMachine); + set => _singleOrParallelMachine = (UInt32)value; + } + + public UInt32 NumberOfSystemModules + { + get => (UInt32)ConvertBitPatternToFloat(_numberOfSystemModules); + set => _numberOfSystemModules = value; + } + + public UInt32 ParallelModuleMachineNumber + { + get => (UInt32)ConvertBitPatternToFloat(_parallelModuleMachineNumber); + set => _parallelModuleMachineNumber = value; + } + + public AccreditedCountry AccreditedCountry + { + get => (AccreditedCountry)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_accreditedCountries)); + set => _accreditedCountries = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0); + } + + // ─────────────────────────────────────────────── + // Control Commands + // ─────────────────────────────────────────────── public float PowerOn { get => BitConverter.Int32BitsToSingle(unchecked((Int32)_powerOn)); @@ -489,39 +699,40 @@ public partial class SinexcelRecord get => BitConverter.Int32BitsToSingle(unchecked((Int32)_powerOff)); set => _powerOff = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); } + + public float FaultClearing + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_faultClearing)); + set => _faultClearing = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } // - //public float FaultClearingf - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_faultClearing)); - // // //set => _faultClearing = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} + // ─────────────────────────────────────────────── + // Meter & Battery Control + // ─────────────────────────────────────────────── + public float MeterReverseManualDetection + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_meterReverseManualDetection)); + set => _meterReverseManualDetection = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } + + // This causing the inverter to switch off, please consider testing it first before uncommenting + /*public SinexcelBatteryRating SinexcelBatteryRating + { + get => (SinexcelBatteryRating)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryVoltageRating)); + set => _batteryVoltageRating = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0); + }*/ + + public float Battery1Activation + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery1Activation)); + set => _battery1Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } // - //// ─────────────────────────────────────────────── - //// Meter & Battery Control - //// ─────────────────────────────────────────────── - //public float MeterReverseManualDetection - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_meterReverseManualDetection)); - // //set => _meterReverseManualDetection = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} -// - //public SinexcelBatteryRating BatteryVoltageRating - //{ - // get => (SinexcelBatteryRating)ConvertBitPatternToFloat(_batteryVoltageRating); - // //set => _batteryVoltageRating = (UInt32)value; - //} -// - //public float Battery1Activation - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery1Activation)); - // //set => _battery1Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} -// - //public float Battery2Activation - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery2Activation)); - // //set => _battery2Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} + public float Battery2Activation + { + get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery2Activation)); + set => _battery2Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); + } // ─────────────────────────────────────────────── // Electric Meter Operating State @@ -609,44 +820,30 @@ public partial class SinexcelRecord // Pack Voltage / Current / Temperature public Voltage Battery2PackTotalVoltage => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalVoltage)); // 0xB200 (0.01 V resolution) public Current Battery2PackTotalCurrent => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalCurrent)); // 0xB202 (0.01 A resolution) - public Temperature Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 % + public Percent Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 % public Percent Battery2Soh => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soh)); // 0xB208 % - // Repetitive-week mask (bit-mapped 0–6 = Sun–Sat) - //public float TimedChargeAndDischargeOff - //{ - // get => BitConverter.Int32BitsToSingle(unchecked((Int32)_Timed_Charge_and_Discharge_Off)) ; // only 7 bits used - // //set => _Timed_Charge_and_Discharge_Off = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); - //} - // Repetitive-week mask (bit-mapped 0–6 = Sun–Sat) - /* public float OtherTimePeriodMode - { - get => _Other_time_period_mode ; // only 7 bits used - //set => _Other_time_period_mode = (UInt32)value; - }*/ - - - + // Repetitive-week mask (bit-mapped 0–6 = Sun–Sat) public SinexcelWeekDays RepetitiveWeeks { get => (SinexcelWeekDays)(_repetitiveWeeks & 0x7F); // only 7 bits used set => _repetitiveWeeks = (UInt32)value; } -// + // Effective start / end as UNIX timestamps public DateTime EffectiveStartDate { get => DateTimeOffset.FromUnixTimeSeconds(_effectiveStartDate).DateTime; - set => _effectiveStartDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds(); + set => _effectiveStartDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds(); } -// + public DateTime EffectiveEndDate { get => DateTimeOffset.FromUnixTimeSeconds(_effectiveEndDate).DateTime; set => _effectiveEndDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds(); } -// // Charging power during time period 1 (kW) + // Charging power during time period 1 (kW) public float ChargingPowerPeriod1 { get => BitConverter.Int32BitsToSingle(unchecked((Int32)_chargingPowerPeriod1)); diff --git a/csharp/Sinexcel 12K TL/SinexcelRecord.Modbus.cs b/csharp/Sinexcel 12K TL/SinexcelRecord.Modbus.cs index 39197e941..ceeab2844 100644 --- a/csharp/Sinexcel 12K TL/SinexcelRecord.Modbus.cs +++ b/csharp/Sinexcel 12K TL/SinexcelRecord.Modbus.cs @@ -11,7 +11,178 @@ namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL; [BigEndian] public partial class SinexcelRecord { - /****************************** Input registers ****************************/ +/* + // ======================================================== + // Register declarations + // ======================================================== + // to test by block first + // ---- Operating states (0x1000–0x1014) + [HoldingRegister(4096, writable: false)] private UInt16 _notUsedRemain; // 0x1000 + [HoldingRegister(4098, writable: false)] private UInt16 _inverterOperatingState; // 0x1002 (3 bits) + [HoldingRegister(4100, writable: false)] private UInt16 _pv1OperatingState; // 0x1004 (1 bit) + [HoldingRegister(4101, writable: false)] private UInt16 _pv2OperatingState; // 0x1005 (1 bit) + [HoldingRegister(4103, writable: false)] private UInt16 _battery1OperatingState; // 0x1007 (3 bits) + + // Battery 2 operating state bits (0x1009–0x100B) + [HoldingRegister(4105, writable: false)] private UInt16 _bat2StateBit0; // 0x1009 + [HoldingRegister(4106, writable: false)] private UInt16 _bat2StateBit1; // 0x100A + [HoldingRegister(4107, writable: false)] private UInt16 _bat2StateBit2; // 0x100B + + // ORED state bits (0x100C–0x100F) + [HoldingRegister(4108, writable: false)] private UInt16 _oredStateBit0; // 0x100C + [HoldingRegister(4109, writable: false)] private UInt16 _oredStateBit1; // 0x100D + [HoldingRegister(4110, writable: false)] private UInt16 _oredStateBit2; // 0x100E + [HoldingRegister(4111, writable: false)] private UInt16 _oredStateBit3; // 0x100F + + // DCDC state (separate bits for DC/AC variants) + [HoldingRegister(4112, writable: false)] private UInt16 _dcdcStateDc; // 0x1010 + [HoldingRegister(4113, writable: false)] private UInt16 _dcdcStateAc; // 0x1011 + + // PV3 / PV4 operational state + [HoldingRegister(4114, writable: false)] private UInt16 _pv3OperatingState; // 0x1012 + [HoldingRegister(4115, writable: false)] private UInt16 _pv4OperatingState; // 0x1013 + + // Power grid status + [HoldingRegister(4116, writable: false)] private UInt16 _powerGridStatus; // 0x1014 + + // ---- Alarm / fault flags (0x1048–0x10CE) (0: Normal, 1: Abnormal) + [HoldingRegister(4168, writable: false)] private UInt16 _abnormalGridVoltage; // 0x1048 Abnormal grid voltage + [HoldingRegister(4169, writable: false)] private UInt16 _abnormalGridFrequency; // 0x1049 Abnormal grid frequency + [HoldingRegister(4170, writable: false)] private UInt16 _invertedSequenceOfGridVoltage; // 0x104A Inverted sequence of grid voltage + [HoldingRegister(4171, writable: false)] private UInt16 _gridVoltagePhaseLoss; // 0x104B Grid voltage phase loss + [HoldingRegister(4172, writable: false)] private UInt16 _abnormalOutputVoltage; // 0x104C Abnormal output voltage + [HoldingRegister(4173, writable: false)] private UInt16 _abnormalOutputFrequency; // 0x104D Abnormal output frequency + [HoldingRegister(4174, writable: false)] private UInt16 _abnormalNullLine; // 0x104E Abnormal null line + [HoldingRegister(4175, writable: false)] private UInt16 _excessivelyHighAmbientTemperature; // 0x104F Excessively high ambient temperature + [HoldingRegister(4176, writable: false)] private UInt16 _excessiveRadiatorTemperature; // 0x1050 Excessive radiator temperature + [HoldingRegister(4177, writable: false)] private UInt16 _insulationFault; // 0x1051 Insulation fault + [HoldingRegister(4178, writable: false)] private UInt16 _leakageProtectionFault; // 0x1052 Leakage protection fault + [HoldingRegister(4179, writable: false)] private UInt16 _auxiliaryPowerFault; // 0x1053 Auxiliary power fault + [HoldingRegister(4180, writable: false)] private UInt16 _fanFault; // 0x1054 Fan fault + [HoldingRegister(4181, writable: false)] private UInt16 _modelCapacityFault; // 0x1055 Model capacity fault + [HoldingRegister(4182, writable: false)] private UInt16 _abnormalLightningArrester; // 0x1056 Abnormal lightning arrester + [HoldingRegister(4183, writable: false)] private UInt16 _islandProtection; // 0x1057 Island protection + [HoldingRegister(4184, writable: false)] private UInt16 _battery1NotConnected; // 0x1058 Battery 1 not connected + [HoldingRegister(4185, writable: false)] private UInt16 _battery1Overvoltage; // 0x1059 Battery 1 overvoltage + [HoldingRegister(4186, writable: false)] private UInt16 _battery1Undervoltage; // 0x105A Battery 1 undervoltage + [HoldingRegister(4187, writable: false)] private UInt16 _battery1DischargeEnd; // 0x105B Battery 1 discharge end + [HoldingRegister(4188, writable: false)] private UInt16 _battery1Inverted; // 0x105C Battery 1 inverted + [HoldingRegister(4189, writable: false)] private UInt16 _battery2NotConnected; // 0x105D Battery 2 not connected + [HoldingRegister(4190, writable: false)] private UInt16 _battery2Overvoltage; // 0x105E Battery 2 overvoltage + [HoldingRegister(4191, writable: false)] private UInt16 _battery2Undervoltage; // 0x105F Battery 2 undervoltage + [HoldingRegister(4192, writable: false)] private UInt16 _battery2DischargeEnd; // 0x1060 Battery 2 discharge end + [HoldingRegister(4193, writable: false)] private UInt16 _battery2Inverted; // 0x1061 Battery 2 inverted + [HoldingRegister(4194, writable: false)] private UInt16 _pv1NotAccessed; // 0x1062 PV1 not accessed + [HoldingRegister(4195, writable: false)] private UInt16 _pv1Overvoltage; // 0x1063 PV1 overvoltage + [HoldingRegister(4196, writable: false)] private UInt16 _abnormalPv1CurrentSharing; // 0x1064 Abnormal PV1 current sharing + [HoldingRegister(4197, writable: false)] private UInt16 _pv2NotAccessed; // 0x1065 PV2 not accessed + [HoldingRegister(4198, writable: false)] private UInt16 _pv2Overvoltage; // 0x1066 PV2 overvoltage + [HoldingRegister(4199, writable: false)] private UInt16 _abnormalPv2CurrentSharing; // 0x1067 Abnormal PV2 current sharing + [HoldingRegister(4200, writable: false)] private UInt16 _dcBusOvervoltage; // 0x1068 DC bus overvoltage + [HoldingRegister(4201, writable: false)] private UInt16 _dcBusUndervoltage; // 0x1069 DC bus undervoltage + [HoldingRegister(4202, writable: false)] private UInt16 _dcBusVoltageUnbalance; // 0x106A DC bus voltage unbalance + [HoldingRegister(4203, writable: false)] private UInt16 _pv1PowerTubeFault; // 0x106B PV1 power tube fault + [HoldingRegister(4204, writable: false)] private UInt16 _pv2PowerTubeFault; // 0x106C PV2 power tube fault + [HoldingRegister(4205, writable: false)] private UInt16 _battery1PowerTubeFault; // 0x106D Battery 1 power tube fault + [HoldingRegister(4206, writable: false)] private UInt16 _battery2PowerTubeFault; // 0x106E Battery 2 power tube fault + [HoldingRegister(4207, writable: false)] private UInt16 _inverterPowerTubeFault; // 0x106F Inverter power tube fault + [HoldingRegister(4208, writable: false)] private UInt16 _systemOutputOverload; // 0x1070 System output overload + [HoldingRegister(4209, writable: false)] private UInt16 _inverterOverload; // 0x1071 Inverter overload + [HoldingRegister(4210, writable: false)] private UInt16 _inverterOverloadTimeout; // 0x1072 Inverter overload timeout + [HoldingRegister(4211, writable: false)] private UInt16 _battery1OverloadTimeout; // 0x1073 Battery 1 overload timeout + [HoldingRegister(4212, writable: false)] private UInt16 _battery2OverloadTimeout; // 0x1074 Battery 2 overload timeout + [HoldingRegister(4213, writable: false)] private UInt16 _inverterSoftStartFailure; // 0x1075 Inverter soft start failure + [HoldingRegister(4214, writable: false)] private UInt16 _battery1SoftStartFailure; // 0x1076 Battery 1 soft start failure + [HoldingRegister(4215, writable: false)] private UInt16 _battery2SoftStartFailure; // 0x1077 Battery 2 soft start failure + [HoldingRegister(4216, writable: false)] private UInt16 _dsp1ParameterSettingFault; // 0x1078 DSP1 parameter setting fault + [HoldingRegister(4217, writable: false)] private UInt16 _dsp2ParameterSettingFault; // 0x1079 DSP2 parameter setting fault + [HoldingRegister(4218, writable: false)] private UInt16 _dspVersionCompatibilityFault; // 0x107A DSP version compatibility fault + [HoldingRegister(4219, writable: false)] private UInt16 _cpldVersionCompatibilityFault; // 0x107B CPLD version compatibility fault + [HoldingRegister(4220, writable: false)] private UInt16 _cpldCommunicationFault; // 0x107C CPLD communication fault + [HoldingRegister(4221, writable: false)] private UInt16 _dspCommunicationFault; // 0x107D DSP communication fault + [HoldingRegister(4222, writable: false)] private UInt16 _outputVoltageDcOverlimit; // 0x107E Output voltage DC overlimit + [HoldingRegister(4223, writable: false)] private UInt16 _outputCurrentDcOverlimit; // 0x107F Output current DC overlimit + [HoldingRegister(4224, writable: false)] private UInt16 _relaySelfCheckFails; // 0x1080 Relay self-check fails + [HoldingRegister(4225, writable: false)] private UInt16 _abnormalInverter; // 0x1081 Abnormal inverter + [HoldingRegister(4226, writable: false)] private UInt16 _poorGrounding; // 0x1082 Poor grounding + [HoldingRegister(4227, writable: false)] private UInt16 _pv1SoftStartFailure; // 0x1083 PV1 soft start failure + [HoldingRegister(4228, writable: false)] private UInt16 _pv2SoftStartFailure2; // 0x1084 PV2 soft start failure + [HoldingRegister(4229, writable: false)] private UInt16 _balancedCircuitOverloadTimeout; // 0x1085 Balanced circuit overload timeout + [HoldingRegister(4230, writable: false)] private UInt16 _pv1OverloadTimeout; // 0x1086 PV1 overload timeout + [HoldingRegister(4231, writable: false)] private UInt16 _pv2OverloadTimeout; // 0x1087 PV2 overload timeout + [HoldingRegister(4232, writable: false)] private UInt16 _pcbOvertemperature; // 0x1088 PCB overtemperature + [HoldingRegister(4233, writable: false)] private UInt16 _dcConverterOvertemperature; // 0x1089 DC converter overtemperature + [HoldingRegister(4234, writable: false)] private UInt16 _busSlowOvervoltage; // 0x108A Bus slow overvoltage + [HoldingRegister(4235, writable: false)] private UInt16 _abnormalOffGridOutputVoltage; // 0x108B Abnormal off-grid output voltage + [HoldingRegister(4236, writable: false)] private UInt16 _hardwareBusOvervoltage; // 0x108C Hardware bus overvoltage + [HoldingRegister(4237, writable: false)] private UInt16 _hardwareOvercurrent; // 0x108D Hardware overcurrent + [HoldingRegister(4238, writable: false)] private UInt16 _dcConverterOvervoltage; // 0x108E DC converter overvoltage + [HoldingRegister(4239, writable: false)] private UInt16 _dcConverterHardwareOvervoltage; // 0x108F DC converter hardware overvoltage + [HoldingRegister(4240, writable: false)] private UInt16 _dcConverterOvercurrent; // 0x1090 DC converter overcurrent + [HoldingRegister(4241, writable: false)] private UInt16 _dcConverterHardwareOvercurrent; // 0x1091 DC converter hardware overcurrent + [HoldingRegister(4242, writable: false)] private UInt16 _dcConverterResonatorOvercurrent; // 0x1092 DC converter resonator overcurrent + [HoldingRegister(4243, writable: false)] private UInt16 _pv1InsufficientPower; // 0x1093 PV1 insufficient power + [HoldingRegister(4244, writable: false)] private UInt16 _pv2InsufficientPower; // 0x1094 PV2 insufficient power + [HoldingRegister(4245, writable: false)] private UInt16 _battery1InsufficientPower; // 0x1095 Battery 1 insufficient power + [HoldingRegister(4246, writable: false)] private UInt16 _battery2InsufficientPower; // 0x1096 Battery 2 insufficient power + [HoldingRegister(4247, writable: false)] private UInt16 _lithiumBattery1ChargeForbidden; // 0x1097 Lithium battery 1 charge forbidden + [HoldingRegister(4248, writable: false)] private UInt16 _lithiumBattery1DischargeForbidden; // 0x1098 Lithium battery 1 discharge forbidden + [HoldingRegister(4249, writable: false)] private UInt16 _lithiumBattery2ChargeForbidden; // 0x1099 Lithium battery 2 charge forbidden + [HoldingRegister(4250, writable: false)] private UInt16 _lithiumBattery2DischargeForbidden; // 0x109A Lithium battery 2 discharge forbidden + [HoldingRegister(4251, writable: false)] private UInt16 _lithiumBattery1Full; // 0x109B Lithium battery 1 full + [HoldingRegister(4252, writable: false)] private UInt16 _lithiumBattery1DischargeEnd; // 0x109C Lithium battery 1 discharge end + [HoldingRegister(4253, writable: false)] private UInt16 _lithiumBattery2Full; // 0x109D LithiumBattery2 full + [HoldingRegister(4254, writable: false)] private UInt16 _lithiumBattery2DischargeEnd; // 0x109E Lithium battery 2 discharge end + [HoldingRegister(4255, writable: false)] private UInt16 _loadPowerOverload; // 0x109F Load power overload + [HoldingRegister(4256, writable: false)] private UInt16 _abnormalLeakageSelfCheck; // 0x10A0 Abnormal leakage self-check + [HoldingRegister(4257, writable: false)] private UInt16 _inverterOvertemperatureAlarm; // 0x10A1 Inverter overtemperature alarm + [HoldingRegister(4258, writable: false)] private UInt16 _inverterOvertemperature; // 0x10A2 Inverter overtemperature + [HoldingRegister(4259, writable: false)] private UInt16 _dcConverterOvertemperatureAlarm; // 0x10A3 DC converter overtemperature alarm + [HoldingRegister(4260, writable: false)] private UInt16 _parallelCommunicationAlarm; // 0x10A4 Parallel communication alarm + [HoldingRegister(4261, writable: false)] private UInt16 _systemDerating; // 0x10A5 System derating + [HoldingRegister(4262, writable: false)] private UInt16 _inverterRelayOpen; // 0x10A6 Inverter relay open + [HoldingRegister(4263, writable: false)] private UInt16 _inverterRelayShortCircuit; // 0x10A7 Inverter relay short circuit + [HoldingRegister(4264, writable: false)] private UInt16 _pvAccessMethodErrorAlarm; // 0x10A8 PV access method error alarm + [HoldingRegister(4265, writable: false)] private UInt16 _parallelModuleMissing; // 0x10A9 Parallel module missing + [HoldingRegister(4266, writable: false)] private UInt16 _duplicateMachineNumbersForParallelModules; // 0x10AA Duplicate machine numbers for parallel modules + [HoldingRegister(4267, writable: false)] private UInt16 _parameterConflictInParallelModule; // 0x10AB Parameter conflict in parallel module + [HoldingRegister(4268, writable: false)] private UInt16 _reservedAlarms4; // 0x10AC Reserved alarms4 + [HoldingRegister(4269, writable: false)] private UInt16 _reverseMeterConnection; // 0x10AD Reverse meter connection + [HoldingRegister(4270, writable: false)] private UInt16 _inverterSealPulse; // 0x10AE Inverter Seal Pulse + [HoldingRegister(4271, writable: false)] private UInt16 _pv3NotConnected; // 0x10AF PV3 not connected + [HoldingRegister(4272, writable: false)] private UInt16 _pv3Overvoltage; // 0x10B0 PV3 overvoltage + [HoldingRegister(4273, writable: false)] private UInt16 _pv3AverageCurrentAnomaly; // 0x10B1 PV3 average current anomaly + [HoldingRegister(4274, writable: false)] private UInt16 _pv4NotConnected; // 0x10B2 PV4 not connected + [HoldingRegister(4275, writable: false)] private UInt16 _pv4Overvoltage; // 0x10B3 PV4 overvoltage + [HoldingRegister(4276, writable: false)] private UInt16 _pv4AverageCurrentAnomaly; // 0x10B4 PV4 average current anomaly + [HoldingRegister(4277, writable: false)] private UInt16 _pv3PowerTubeFailure; // 0x10B5 PV3 power tube failure + [HoldingRegister(4278, writable: false)] private UInt16 _pv4PowerTubeFailure; // 0x10B6 PV4 power tube failure + [HoldingRegister(4279, writable: false)] private UInt16 _pv3SoftStartFailure; // 0x10B7 PV3 soft start failure + [HoldingRegister(4280, writable: false)] private UInt16 _pv4SoftStartFailure; // 0x10B8 PV4 soft start failure + [HoldingRegister(4281, writable: false)] private UInt16 _pv3OverloadTimeout; // 0x10B9 PV3 overload timeout + [HoldingRegister(4282, writable: false)] private UInt16 _pv4OverloadTimeout; // 0x10BA PV4 overload timeout + [HoldingRegister(4283, writable: false)] private UInt16 _pv3ReverseConnection; // 0x10BB PV3 reverse connection + [HoldingRegister(4284, writable: false)] private UInt16 _pv4ReverseConnection; // 0x10BC PV4 reverse connection + [HoldingRegister(4285, writable: false)] private UInt16 _abnormalDieselGeneratorVoltage; // 0x10BD Abnormal diesel generator voltage + [HoldingRegister(4286, writable: false)] private UInt16 _abnormalDieselGeneratorFrequency; // 0x10BE Abnormal diesel generator frequency + [HoldingRegister(4287, writable: false)] private UInt16 _dieselGeneratorVoltageReverseSequence; // 0x10BF Diesel generator voltage reverse sequence + [HoldingRegister(4288, writable: false)] private UInt16 _dieselGeneratorVoltageOutOfPhase; // 0x10C0 Diesel generator voltage out of phase + [HoldingRegister(4289, writable: false)] private UInt16 _leadBatteryTemperatureAbnormality; // 0x10C1 Lead battery temperature abnormality + [HoldingRegister(4290, writable: false)] private UInt16 _batteryAccessMethodError; // 0x10C2 Battery access method error + [HoldingRegister(4291, writable: false)] private UInt16 _reservedAlarms5; // 0x10C3 Reserved alarms5 + [HoldingRegister(4292, writable: false)] private UInt16 _battery1BackupProhibited; // 0x10C4 Battery 1 backup prohibited + [HoldingRegister(4293, writable: false)] private UInt16 _battery2BackupProhibited; // 0x10C5 Battery 2 backup prohibited + [HoldingRegister(4294, writable: false)] private UInt16 _abnormalGridCurrent; // 0x10C6 Abnormal grid current + [HoldingRegister(4295, writable: false)] private UInt16 _generatorOverload; // 0x10C7 Generator overload + [HoldingRegister(4296, writable: false)] private UInt16 _busSoftStartFailure; // 0x10C8 Bus soft start failure + [HoldingRegister(4297, writable: false)] private UInt16 _openCircuitOfPowerGridRelay; // 0x10C9 Open circuit of power grid relay + [HoldingRegister(4298, writable: false)] private UInt16 _shortCircuitOfPowerGridRelay; // 0x10CA Short circuit of power grid relay + [HoldingRegister(4299, writable: false)] private UInt16 _generatorRelayOpenCircuit; // 0x10CB Generator relay open circuit + [HoldingRegister(4300, writable: false)] private UInt16 _generatorRelayShortCircuit; // 0x10CC Generator relay short circuit + [HoldingRegister(4301, writable: false)] private UInt16 _insufficientPhotovoltaicPower; // 0x10CD Insufficient photovoltaic power + [HoldingRegister(4302, writable: false)] private UInt16 _photovoltaic1Overcurrent; // 0x10CE Photovoltaic 1 overcurrent*/ + + /****************************** Holding registers ****************************/ // Voltages [HoldingRegister(4096)] public UInt32 _gridAPhaseVoltage; // 0x1000 [HoldingRegister(4098)] private UInt32 _grid_B_Phase_Voltage; // 0x1002 @@ -206,6 +377,7 @@ public partial class SinexcelRecord [HoldingRegister(4740)] private UInt32 _pv4Current; // 0x1284 [HoldingRegister(4742)] private UInt32 _pv4Power; // 0x1286 [HoldingRegister(4744)] private UInt32 _generatorTotalPower; // 0x128C + // ─────────────────────────────────────────────── // Manufacturer Information & Software Versions // ─────────────────────────────────────────────── @@ -226,15 +398,15 @@ public partial class SinexcelRecord // ─────────────────────────────────────────────── // System configuration / operation registers // ─────────────────────────────────────────────── - //[HoldingRegister(12294, writable: true)] private UInt32 _threePhaseWireSystem; // 0x3006 + [HoldingRegister(12294, writable: true)] private UInt32 _threePhaseWireSystem; // 0x3006 //[HoldingRegister(12296, writable: true)] private UInt32 _remainnotused; // 0x3008 - //[HoldingRegister(12298, writable: true)] private UInt32 _inputFrequencyClass; // 0x300A + [HoldingRegister(12298, writable: true)] private UInt32 _inputFrequencyClass; // 0x300A [HoldingRegister(12300, writable: true)] private UInt32 _workingMode; // 0x300C - //[HoldingRegister(12302, writable: true)] private UInt32 _methodSwitchMode; // 0x300E + [HoldingRegister(12302, writable: true)] private UInt32 _methodSwitchMode; // 0x300E // ─────────────────────────────────────────────── // Inverter Control and Protection Configuration // ─────────────────────────────────────────────── - //[HoldingRegister(12304, writable: true)] private UInt32 _enableOnGridUnbalanceCompensation; // 0x3010 + [HoldingRegister(12304, writable: true)] private UInt32 _enableOnGridUnbalanceCompensation; // 0x3010 //[HoldingRegister(12306, writable: true)] private UInt32 _temperatureDrop; // 0x3012 //[HoldingRegister(12308, writable: true)] private UInt32 _hvrt; // 0x3014 //[HoldingRegister(12310, writable: true)] private UInt32 _lvrt; // 0x3016 @@ -243,53 +415,60 @@ public partial class SinexcelRecord // ─────────────────────────────────────────────── // Battery & PV Configuration // ─────────────────────────────────────────────── - //[HoldingRegister(12336, writable: true)] private UInt32 _batteryAccessMethod; // 0x3030 - //[HoldingRegister(12338, writable: true)] private UInt32 _meterAccessEnable; // 0x3032 - //[HoldingRegister(12340, writable: true)] private UInt32 _enableBattery1; // 0x3034 - //[HoldingRegister(12342, writable: true)] private UInt32 _enableBattery2; // 0x3036 - //[HoldingRegister(12344, writable: true)] private UInt32 _enablePv1; // 0x3038 - //[HoldingRegister(12346, writable: true)] private UInt32 _enablePv2; // 0x303A - //[HoldingRegister(12348, writable: true)] private UInt32 _batteryType; // 0x303C - //[HoldingRegister(12350, writable: true)] private UInt32 _batteryCapacity1; // 0x303E - //[HoldingRegister(12352, writable: true)] private UInt32 _maxChargingCurrentBattery1; // 0x3040 - //[HoldingRegister(12354, writable: true)] private UInt32 _maxDischargingCurrentBattery1; // 0x3042 - //[HoldingRegister(12362, writable: true)] private UInt32 _ratedBatteryVoltage1; // 0x304A - //[HoldingRegister(12358, writable: true)] private UInt32 _minSocBattery1; // 0x3046 - //[HoldingRegister(12360, writable: true)] private UInt32 _setValueBattery1; // 0x3048 - //[HoldingRegister(12364, writable: true)] private UInt32 _activeChargeDischarge; // 0x304A - //[HoldingRegister(12366, writable: true)] private UInt32 _activeChargeDischargePower; // 0x304C - //[HoldingRegister(12380, writable: true)] private UInt32 _enableIslandProtection; // 0x305C - //[HoldingRegister(12382, writable: true)] private UInt32 _pvAccessMode; // 0x305E + [HoldingRegister(12336, writable: true)] private UInt32 _batteryAccessMethod; // 0x3030 + [HoldingRegister(12338, writable: true)] private UInt32 _meterAccessEnable; // 0x3032 + [HoldingRegister(12340, writable: true)] private UInt32 _enableBattery1; // 0x3034 + [HoldingRegister(12342, writable: true)] private UInt32 _enableBattery2; // 0x3036 + [HoldingRegister(12344, writable: true)] private UInt32 _enablePv1; // 0x3038 + [HoldingRegister(12346, writable: true)] private UInt32 _enablePv2; // 0x303A + [HoldingRegister(12348, writable: true)] private UInt32 _batteryType; // 0x303C + [HoldingRegister(12350, writable: true)] private UInt32 _batteryCapacity1; // 0x303E + [HoldingRegister(12352, writable: true)] private UInt32 _maxChargingCurrentBattery1; // 0x3040 + [HoldingRegister(12354, writable: true)] private UInt32 _maxDischargingCurrentBattery1; // 0x3042 + [HoldingRegister(12356, writable: true)] private UInt32 _ratedBatteryVoltage1; // 0x304A + [HoldingRegister(12358, writable: true)] private UInt32 _minSocBattery1; // 0x3046 + [HoldingRegister(12360, writable: true)] private UInt32 _setValueBattery1; // 0x3048 + //[HoldingRegister(12362, writable: true)] private UInt32 _activeChargeDischarge; // 0x304A // this + [HoldingRegister(12364, writable: true)] private UInt32 _activeChargeDischargePower; // 0x304C + [HoldingRegister(12380, writable: true)] private UInt32 _enableIslandProtection; // 0x305C + [HoldingRegister(12382, writable: true)] private UInt32 _pvAccessMode; // 0x305E // ─────────────────────────────────────────────── // System & Battery-2 Configuration // ─────────────────────────────────────────────── - //[HoldingRegister(12384, writable: true)] private UInt32 _outputVoltageAdjustmentFactor; // 0x3060 - //[HoldingRegister(12386, writable: true)] private UInt32 _setValueBatteryUndervoltage1; // 0x3062 - //[HoldingRegister(12388, writable: true)] private UInt32 _inverterPowerLimit; // 0x3064 - //[HoldingRegister(12400, writable: true)] private UInt32 _battery2Capacity; // 0x30B0 - //[HoldingRegister(12402, writable: true)] private UInt32 _maxChargingCurrentBattery2; // 0x30B2 - //[HoldingRegister(12404, writable: true)] private UInt32 _maxDischargingCurrentBattery2; // 0x30B4 - //[HoldingRegister(12406, writable: true)] private UInt32 _battery2RatedVoltage; // 0x30B6 - //[HoldingRegister(12408, writable: true)] private UInt32 _battery2MinSoc; // 0x30B8 - //[HoldingRegister(12410, writable: true)] private UInt32 _battery2OverVoltageSetting; // 0x30BA - //[HoldingRegister(12412, writable: true)] private UInt32 _battery2UnderVoltageSetpoint; // 0x30BC + [HoldingRegister(12384, writable: true)] private UInt32 _outputVoltageAdjustmentFactor; // 0x3060 + [HoldingRegister(12386, writable: true)] private UInt32 _setValueBatteryUndervoltage1; // 0x3062 + [HoldingRegister(12388, writable: true)] private UInt32 _inverterPowerLimit; // 0x3064 + [HoldingRegister(12400, writable: true)] private UInt32 _battery2Capacity; // 0x30B0 + [HoldingRegister(12402, writable: true)] private UInt32 _maxChargingCurrentBattery2; // 0x30B2 + [HoldingRegister(12404, writable: true)] private UInt32 _maxDischargingCurrentBattery2; // 0x30B4 + [HoldingRegister(12406, writable: true)] private UInt32 _battery2RatedVoltage; // 0x30B6 + [HoldingRegister(12408, writable: true)] private UInt32 _battery2MinSoc; // 0x30B8 + [HoldingRegister(12410, writable: true)] private UInt32 _battery2OverVoltageSetting; // 0x30BA + [HoldingRegister(12412, writable: true)] private UInt32 _battery2UnderVoltageSetpoint; // 0x30BC // - //[HoldingRegister(12414, writable: true)] private UInt32 _singleOrParallelMachine; // 0x30BE - //[HoldingRegister(12416, writable: true)] private UInt32 _numberOfSystemModules; // 0x30C0 - //[HoldingRegister(12418, writable: true)] private UInt32 _parallelModuleMachineNumber; // 0x30C2 - //[HoldingRegister(12420, writable: true)] private UInt32 _accreditedCountries; // 0x30C4 - + [HoldingRegister(12414, writable: true)] private UInt32 _singleOrParallelMachine; // 0x30BE + [HoldingRegister(12416, writable: true)] private UInt32 _numberOfSystemModules; // 0x30C0 + [HoldingRegister(12418, writable: true)] private UInt32 _parallelModuleMachineNumber; // 0x30C2 + [HoldingRegister(12420, writable: true)] private UInt32 _accreditedCountries; // 0x30C4 + + [HoldingRegister(12618, writable: true)] private UInt32 _battery1BackupSOC; // 0x314A + [HoldingRegister(12620, writable: true)] private UInt32 _battery2BackupSOC; // 0x314C + + [HoldingRegister(12746, writable: true)] private UInt32 _enableGridExport; // 0x314A + [HoldingRegister(12748, writable: true)] private UInt32 _powerGridExportLimit; // 0x314C + + // ─────────────────────────────────────────────── // System Control & Diagnostic Registers // ─────────────────────────────────────────────── [HoldingRegister(15426, writable: true)] private UInt32 _powerOn; // 0x3C42 [HoldingRegister(15428, writable: true)] private UInt32 _powerOff; // 0x3C44 - //[HoldingRegister(15430, writable: true)] private UInt32 _faultClearing; // 0x3C46 - //[HoldingRegister(15518, writable: true)] private UInt32 _meterReverseManualDetection; // 0x3C9E - //[HoldingRegister(15520, writable: true)] private UInt32 _batteryVoltageRating; // 0x3CA0 - //[HoldingRegister(15524, writable: true)] private UInt32 _battery1Activation; // 0x3CA4 - //[HoldingRegister(15526, writable: true)] private UInt32 _battery2Activation; // 0x3CA6 + [HoldingRegister(15430, writable: true)] private UInt32 _faultClearing; // 0x3C46 + [HoldingRegister(15518, writable: true)] private UInt32 _meterReverseManualDetection; // 0x3C9E + //[HoldingRegister(15520, writable: true)] private UInt32 _batteryVoltageRating; // 0x3CA0 This causing the inverter to switch off, please consider testing it first before uncommenting + [HoldingRegister(15522, writable: true)] private UInt32 _battery1Activation; // 0x3CA2 + [HoldingRegister(15524, writable: true)] private UInt32 _battery2Activation; // 0x3CA4 // ─────────────────────────────────────────────── @@ -337,8 +516,6 @@ public partial class SinexcelRecord // Time-based scheduling (period 1) configuration // ─────────────────────────────────────────────── - //[HoldingRegister(49328, writable: true)] private UInt32 _Timed_Charge_and_Discharge_Off; // 0xC0B4, bit flags Sun–Sat - //[HoldingRegister(49330, writable: true)] private UInt32 _Other_time_period_mode; // 0xC0B4, bit flags Sun–Sat [HoldingRegister(49332, writable: true)] private UInt32 _repetitiveWeeks; // 0xC0B4, bit flags Sun–Sat [HoldingRegister(49334, writable: true)] private UInt32 _effectiveStartDate; // 0xC0B6, UNIX timestamp (UTC+offset) [HoldingRegister(49336, writable: true)] private UInt32 _effectiveEndDate; // 0xC0B8, UNIX timestamp (UTC+offset)