diff --git a/csharp/App/KacoCommunication/ESS/EssMode.cs b/csharp/App/KacoCommunication/ESS/EssMode.cs new file mode 100644 index 000000000..c33112305 --- /dev/null +++ b/csharp/App/KacoCommunication/ESS/EssMode.cs @@ -0,0 +1,6 @@ +namespace InnovEnergy.App.KacoCommunication.ESS; + +public enum EssMode +{ + +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/ESS/StatusRecord.cs b/csharp/App/KacoCommunication/ESS/StatusRecord.cs index 787d95d26..6a9720404 100644 --- a/csharp/App/KacoCommunication/ESS/StatusRecord.cs +++ b/csharp/App/KacoCommunication/ESS/StatusRecord.cs @@ -1,3 +1,4 @@ +using InnovEnergy.App.KacoCommunication.System; using InnovEnergy.App.KacoCommunication.SystemConfig; using InnovEnergy.Lib.Devices.BatteryDeligreen; using InnovEnergy.Lib.Devices.Kaco92L3; @@ -5,19 +6,19 @@ using InnovEnergy.Lib.Devices.PLVario2Meter; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; namespace InnovEnergy.App.KacoCommunication.ESS; - +using ListOfBatteriesRecord = List; public class StatusRecord { - public required KacoRecord? InverterRecord { get; set; } - public required PlVarioMeterRecord? GridMeterRecord { get; set; } - public required DcDcDevicesRecord? DcDc { get; init; } + public required KacoRecord? InverterRecord { get; set; } + public required PlVarioMeterRecord? GridMeterRecord { get; set; } + public required DcDcDevicesRecord? DcDc { get; init; } + public required ListOfBatteriesRecord? ListOfBatteriesRecord { get; set; } + // public required BatteryDeligreenRecords? BatteryKabinet1 { get; set; } + // public required BatteryDeligreenRecords? BatteryKabinet2 { get; set; } + // public required BatteryDeligreenRecords? BatteryKabinet3 { get; set; } - public required BatteryDeligreenRecords? BatteryKabinet1 { get; set; } - public required BatteryDeligreenRecords? BatteryKabinet2 { get; set; } - public required BatteryDeligreenRecords? BatteryKabinet3 { get; set; } - - - - public required Config Config { get; set; } + public required Config Config { get; set; } + public required StateMachine StateMachine { get; init; } + } diff --git a/csharp/App/KacoCommunication/Program.cs b/csharp/App/KacoCommunication/Program.cs index d17e6d3bc..6f9c3b956 100644 --- a/csharp/App/KacoCommunication/Program.cs +++ b/csharp/App/KacoCommunication/Program.cs @@ -1,5 +1,4 @@ // See https://aka.ms/new-console-template for more information - using System.IO.Compression; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; @@ -11,16 +10,21 @@ using InnovEnergy.App.KacoCommunication.DataTypes; using InnovEnergy.App.KacoCommunication.Devices; using InnovEnergy.App.KacoCommunication.ESS; using InnovEnergy.App.KacoCommunication.MiddlewareClasses; +using InnovEnergy.App.KacoCommunication.System; 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.SystemControl; +using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control; using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Units; using InnovEnergy.Lib.Utils; using Newtonsoft.Json; +using DeviceState = InnovEnergy.App.KacoCommunication.Devices.DeviceState; using Formatting = Newtonsoft.Json.Formatting; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -30,26 +34,25 @@ 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 UInt16 _fileCounter = 0; + + + private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green; + private static SodistoreAlarmState _sodiAlarmState = SodistoreAlarmState.Green; - - 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 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; + 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 @@ -65,22 +68,20 @@ internal static class Program ? new NullChannel() : new TcpChannel(device); - BatteryNodes = config + 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 @@ -89,7 +90,7 @@ internal static class Program } catch (Exception e) { - // e.LogError(); + e.LogError(); } } // ReSharper disable once FunctionNeverReturns @@ -105,39 +106,49 @@ internal static class Program 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); + 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(); + 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(); + + var batteryKabinet1 = batteryDevices1.Read(); + var batteryKabinet2 = batteryDevices2.Read(); + var batteryKabinet3 = batteryDevices3.Read(); + + var listOfBatteriesRecord = new List(); + if (batteryKabinet1 != null) listOfBatteriesRecord.Add(batteryKabinet1); + if (batteryKabinet2 != null) listOfBatteriesRecord.Add(batteryKabinet2); + if (batteryKabinet3 != null) listOfBatteriesRecord.Add(batteryKabinet3); + return new StatusRecord { - InverterRecord = kacoRecord, + 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 + DcDc = dcDcRecord, + ListOfBatteriesRecord = listOfBatteriesRecord, + StateMachine = StateMachine.Default, + + Config = config // load from disk every iteration, so config can be changed while running }; } @@ -153,7 +164,7 @@ internal static class Program .SelectError() .ToTask(); } - + StatusRecord? RunIteration() { @@ -167,56 +178,60 @@ internal static class Program Console.WriteLine(startTime.ToString("HH:mm:ss.fff")); // the order matter of the next three lines var statusrecord = ReadStatus(); - statusrecord?.CreateSimpleTopologyTextBlock().WriteLine(); + statusrecord?.CreateSimpleTopologyTextBlock().WriteLine(); + + statusrecord?.StateMachine.State.WriteLine(" state"); + statusrecord?.StateMachine.Message.WriteLine(" Message"); - - // 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?.BatteryLimitsEnable.WriteLine(" BatteryLimitsEnable"); + + Console.WriteLine(" **************** DcDc **********************"); + statusrecord?.DcDc?.Dc.Battery.Power .WriteLine(" DC Battery Power"); + statusrecord?.DcDc?.Dc.Battery.Voltage .WriteLine("DC Battery Voltage"); + statusrecord?.DcDc?.Dc.Battery.Current .WriteLine("DC Battery Current"); + statusrecord?.DcDc?.Dc.Link.Voltage .WriteLine(" Dc link Voltage"); + + Console.WriteLine(" ********************************* Kaco Inverter *********************************"); + + statusrecord?.InverterRecord?.ActivePowerW.WriteLine(" Inverter Power"); 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?.PcuState.WriteLine(" PcuState"); 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?.ActivePowerSetPercent.WriteLine(" ActivePowerSetPercent"); - statusrecord?.InverterRecord?.ReactivePowerSetPercent.WriteLine(" ReactivePowerSetPercent"); - statusrecord?.InverterRecord?.WatchdogSeconds.WriteLine(" WatchdogSeconds"); + statusrecord?.ControlSystemState(); + var i = 0; + foreach (var d in statusrecord.DcDc.Devices) + { + i++; + Console.WriteLine("before DcDc is " + i + d.Control.PowerStageEnable); + d.Control.ResetAlarmsAndWarnings = true; + d.Control.PowerStageEnable = true; + } + + + statusrecord?.DcDc?.SystemControl.ApplyDcDcDefaultSettings(); InitializeKacoStartup(statusrecord); + foreach (var d in statusrecord.DcDc.Devices) + { + Console.WriteLine("After DcDc is " + d.Control.PowerStageEnable); + } - Console.WriteLine( " ************************************ We are writing ************************************"); + + 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) @@ -227,11 +242,10 @@ internal static class Program } } } - + private static async Task SavingLocalCsvFile(Int64 timestamp, String csv) { - const String directoryPath = "/home/inesco/salimax/csvFile"; - + var directoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "csvFiles"); // Ensure directory exists if (!Directory.Exists(directoryPath)) { @@ -268,49 +282,49 @@ internal static class Program 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() : ""; + + 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) @@ -368,12 +382,13 @@ internal static class Program 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) @@ -381,15 +396,15 @@ internal static class Program var returnedStatus = new StatusMessage { InstallationId = installationId, - Product = 3, - Status = _sodiAlarmState, - Type = MessageType.Heartbit, + Product = 3, + Status = _sodiAlarmState, + Type = MessageType.Heartbit, }; if (s3Bucket != null) RabbitMqManager.InformMiddleware(returnedStatus); } } - + private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension) { try @@ -408,7 +423,7 @@ internal static class Program Console.WriteLine("Failed to save compressed file locally: " + ex.Message); } } - + private static Byte[] CompresseBytes(String jsonToSend) { //Compress JSON data to a byte array @@ -416,19 +431,20 @@ internal static class Program //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 + 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"); @@ -472,18 +488,20 @@ internal static class Program 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 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 + result1, result2 }; - + foreach (var result in allResults) { foreach (var entry in result) @@ -491,17 +509,18 @@ internal static class Program 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"); + 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) { @@ -515,13 +534,13 @@ internal static class Program case "Int16": var int16Val = Convert.ToInt16(value); - registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort + 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 + registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word + registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word break; case "Int32": @@ -534,9 +553,15 @@ internal static class Program default: throw new ArgumentException("Unsupported output type: " + outputType); } + return registers; } - + + private static void ForAll(this IEnumerable ts, Action action) + { + foreach (var t in ts) + action(t); + } private static void InitializeKacoStartup(StatusRecord? statusRecord) { @@ -544,12 +569,23 @@ internal static class Program // 1. Apply DC – This part is physical and cannot be done in software. // We assume DC power is already present. // - + Console.WriteLine("1. Apply DC"); // - // 2. Send valid battery limits (Model 64202) + statusRecord?.DcDc?.Devices + .Select(d => d.Control) + .ForAll(c => c.PowerStageEnable = true); + + statusRecord?.DcDc?.Devices + .Select(d => d.Control ) + .ForAll(c => c.ControlMode = DcControlMode.VoltageDroop); + // + // // 2. Send valid battery limits (Model 64202) // All values temporarily set to "1" as requested. // You will replace them later with real values. // + + Console.WriteLine("2. Send real value"); + if (statusRecord?.InverterRecord != null) { statusRecord.InverterRecord.MinDischargeVoltage = 700f; // 64202.DisMinV @@ -559,12 +595,13 @@ internal static class Program 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) - // + + Console.WriteLine("3. Enable limits "); statusRecord.InverterRecord.BatteryLimitsEnable = EnableDisableEnum.Enabled; // @@ -578,6 +615,9 @@ internal static class Program // - After valid limits: CurrentState == 8 (STANDBY) // - Then after grid/DC conditions: CurrentState == 1 (OFF) or 11 (GRID_CONNECTED) // + + Console.WriteLine("3. Read current state"); + var state = statusRecord.InverterRecord.CurrentState; @@ -586,6 +626,7 @@ internal static class Program switch (state) { case CurrentState.Standby: + Console.WriteLine("Device is in STANDBY (8) — battery limits accepted."); break; @@ -601,9 +642,26 @@ internal static class Program Console.WriteLine("Device in unexpected state: " + state); break; } - //Thread.Sleep(2000); } } + + private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc) + { + + if (sc is null) + return; + + sc.SystemConfig = Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig.DcDcOnly; + sc.CommunicationTimeout = TimeSpan.FromSeconds(20); + + sc.PowerSetPointActivation = PowerSetPointActivation.Immediate; + sc.UseSlaveIdForAddressing = true; + sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; + sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; + sc.TargetSlave = 0; + sc.ResetAlarmsAndWarnings = true; + } + private static void InsertIntoJson(Dictionary jsonDict, String[] keys, String value) { var currentDict = jsonDict; @@ -617,11 +675,10 @@ internal static class Program 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 + 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 { diff --git a/csharp/App/KacoCommunication/System/StateMachine.cs b/csharp/App/KacoCommunication/System/StateMachine.cs new file mode 100644 index 000000000..987b34432 --- /dev/null +++ b/csharp/App/KacoCommunication/System/StateMachine.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.App.KacoCommunication.System; + +public record StateMachine +{ + public required String Message { get; set; } // TODO: init only + public required Int32 State { get; set; } // TODO: init only + + public static StateMachine Default { get; } = new StateMachine { State = 100, Message = "Unknown State" }; +} \ No newline at end of file diff --git a/csharp/App/KacoCommunication/SystemConfig/Config.cs b/csharp/App/KacoCommunication/SystemConfig/Config.cs index 84c6c6b89..3e20f7174 100644 --- a/csharp/App/KacoCommunication/SystemConfig/Config.cs +++ b/csharp/App/KacoCommunication/SystemConfig/Config.cs @@ -14,7 +14,8 @@ public class Config private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; public required Double MinSoc { get; set; } - + public required float ActivePowerPercent { get; set; } + /* public required Double MaximumDischargingCurrent { get; set; } public required Double MaximumChargingCurrent { get; set; } public required Int16 BatteriesCount { get; set; } @@ -29,8 +30,8 @@ public class Config public static Config Default => new() { MinSoc = 20, - /* MaximumChargingCurrent = 180, - MaximumDischargingCurrent = 180, + ActivePowerPercent = 0f, + /* MaximumDischargingCurrent = 180, BatteriesCount = 0, ModbusProtcolNumber = 1.2,*/ Devices = new () diff --git a/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs b/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs index c88766213..2118b7941 100644 --- a/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs +++ b/csharp/Lib/Devices/Kaco92L3/KacoRecord.Api.cs @@ -176,6 +176,11 @@ public partial class KacoRecord /// public Int16 BatteryCurrentScaleFactor => _battCharASf; + + public Int16 ActivePowerW => _activePowerW; + public Int16 ReactivePowerVar => _reactivePowerVar; + public Int16 LineFrequencyHz => _lineFrequencyHz; + // Helper wrappers for scaled values private float ScaleBattVoltage(UInt16 raw) => ScaleSunspec(raw, _battCharVSf); private float ScaleBattCurrent(UInt16 raw) => ScaleSunspec(raw, _battCharASf); diff --git a/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs b/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs index f164ed577..16e382af1 100644 --- a/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs +++ b/csharp/Lib/Devices/Kaco92L3/KacoRecord.modbus.cs @@ -21,7 +21,7 @@ public partial class KacoRecord // 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(41067, writable: true)] private UInt16 _controlMode; // 0xA06B – ControlMode (enum16, RW) Power Control mode (zunächst 0=Q RPC_Local / 1 RPC_Remote Qfix 64201) [HoldingRegister(41068)] private UInt16 _reserved7; // 0xA06C – Reserved // Watchdog / setpoints