Compare commits
3 Commits
7476c939c3
...
d81ef73bcf
| Author | SHA1 | Date |
|---|---|---|
|
|
d81ef73bcf | |
|
|
cbd4801568 | |
|
|
da9d2b1f40 |
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace InnovEnergy.App.KacoCommunication.ESS;
|
||||||
|
|
||||||
|
public enum EssMode
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using InnovEnergy.App.KacoCommunication.System;
|
||||||
using InnovEnergy.App.KacoCommunication.SystemConfig;
|
using InnovEnergy.App.KacoCommunication.SystemConfig;
|
||||||
using InnovEnergy.Lib.Devices.BatteryDeligreen;
|
using InnovEnergy.Lib.Devices.BatteryDeligreen;
|
||||||
using InnovEnergy.Lib.Devices.Kaco92L3;
|
using InnovEnergy.Lib.Devices.Kaco92L3;
|
||||||
|
|
@ -5,19 +6,19 @@ using InnovEnergy.Lib.Devices.PLVario2Meter;
|
||||||
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
|
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
|
||||||
|
|
||||||
namespace InnovEnergy.App.KacoCommunication.ESS;
|
namespace InnovEnergy.App.KacoCommunication.ESS;
|
||||||
|
using ListOfBatteriesRecord = List<BatteryDeligreenRecords>;
|
||||||
public class StatusRecord
|
public class StatusRecord
|
||||||
{
|
{
|
||||||
public required KacoRecord? InverterRecord { get; set; }
|
public required KacoRecord? InverterRecord { get; set; }
|
||||||
public required PlVarioMeterRecord? GridMeterRecord { get; set; }
|
public required PlVarioMeterRecord? GridMeterRecord { get; set; }
|
||||||
public required DcDcDevicesRecord? DcDc { get; init; }
|
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 Config Config { get; set; }
|
||||||
public required BatteryDeligreenRecords? BatteryKabinet2 { get; set; }
|
public required StateMachine StateMachine { get; init; }
|
||||||
public required BatteryDeligreenRecords? BatteryKabinet3 { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public required Config Config { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
// See https://aka.ms/new-console-template for more information
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Threading.Tasks;
|
using System.Reactive.Threading.Tasks;
|
||||||
|
|
@ -11,16 +10,21 @@ using InnovEnergy.App.KacoCommunication.DataTypes;
|
||||||
using InnovEnergy.App.KacoCommunication.Devices;
|
using InnovEnergy.App.KacoCommunication.Devices;
|
||||||
using InnovEnergy.App.KacoCommunication.ESS;
|
using InnovEnergy.App.KacoCommunication.ESS;
|
||||||
using InnovEnergy.App.KacoCommunication.MiddlewareClasses;
|
using InnovEnergy.App.KacoCommunication.MiddlewareClasses;
|
||||||
|
using InnovEnergy.App.KacoCommunication.System;
|
||||||
using InnovEnergy.App.KacoCommunication.SystemConfig;
|
using InnovEnergy.App.KacoCommunication.SystemConfig;
|
||||||
using InnovEnergy.Lib.Devices.BatteryDeligreen;
|
using InnovEnergy.Lib.Devices.BatteryDeligreen;
|
||||||
using InnovEnergy.Lib.Devices.Kaco92L3;
|
using InnovEnergy.Lib.Devices.Kaco92L3;
|
||||||
using InnovEnergy.Lib.Devices.Kaco92L3.DataType;
|
using InnovEnergy.Lib.Devices.Kaco92L3.DataType;
|
||||||
using InnovEnergy.Lib.Devices.PLVario2Meter;
|
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;
|
||||||
|
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control;
|
||||||
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
using InnovEnergy.Lib.Units;
|
using InnovEnergy.Lib.Units;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using DeviceState = InnovEnergy.App.KacoCommunication.Devices.DeviceState;
|
||||||
using Formatting = Newtonsoft.Json.Formatting;
|
using Formatting = Newtonsoft.Json.Formatting;
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
|
|
@ -30,12 +34,11 @@ internal static class Program
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5);
|
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5);
|
||||||
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
|
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 _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
|
private static SodistoreAlarmState _sodiAlarmState = SodistoreAlarmState.Green;
|
||||||
private static SodistoreAlarmState _sodiAlarmState = SodistoreAlarmState.Green;
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly IReadOnlyList<Byte> BatteryNodes;
|
private static readonly IReadOnlyList<Byte> BatteryNodes;
|
||||||
|
|
@ -43,13 +46,13 @@ internal static class Program
|
||||||
private static readonly Channel GridMeterChannel;
|
private static readonly Channel GridMeterChannel;
|
||||||
private static readonly Channel DcDcChannel;
|
private static readonly Channel DcDcChannel;
|
||||||
|
|
||||||
private const String Port1Cabinet = "/dev/ttyUSB0"; // 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 Port2Cabinet = "/dev/ttyUSB1"; // move to a config file
|
||||||
private const String Port3Cabinet = "/dev/ttyUSB2"; // 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 static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
|
||||||
private const String VpnServerIp = "10.2.0.11";
|
private const String VpnServerIp = "10.2.0.11";
|
||||||
public static Boolean _subscribedToQueue = false;
|
public static Boolean _subscribedToQueue = false;
|
||||||
public static Boolean _subscribeToQueueForTheFirstTime = false;
|
public static Boolean _subscribeToQueueForTheFirstTime = false;
|
||||||
|
|
||||||
private static Int32 _failsCounter = 0; // move to a config file
|
private static Int32 _failsCounter = 0; // move to a config file
|
||||||
|
|
@ -65,7 +68,7 @@ internal static class Program
|
||||||
? new NullChannel()
|
? new NullChannel()
|
||||||
: new TcpChannel(device);
|
: new TcpChannel(device);
|
||||||
|
|
||||||
BatteryNodes = config
|
BatteryNodes = config
|
||||||
.Devices
|
.Devices
|
||||||
.BatteryNodes
|
.BatteryNodes
|
||||||
.Select(n => n.ConvertTo<Byte>())
|
.Select(n => n.ConvertTo<Byte>())
|
||||||
|
|
@ -74,13 +77,11 @@ internal static class Program
|
||||||
KacoChannel = CreateChannel(d.KacoIp);
|
KacoChannel = CreateChannel(d.KacoIp);
|
||||||
GridMeterChannel = CreateChannel(d.GridMeterIp);
|
GridMeterChannel = CreateChannel(d.GridMeterIp);
|
||||||
DcDcChannel = CreateChannel(d.DcDcIp);
|
DcDcChannel = CreateChannel(d.DcDcIp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async Task Main(String[] args)
|
public static async Task Main(String[] args)
|
||||||
{
|
{
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -89,7 +90,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// e.LogError();
|
e.LogError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ReSharper disable once FunctionNeverReturns
|
// ReSharper disable once FunctionNeverReturns
|
||||||
|
|
@ -105,39 +106,49 @@ internal static class Program
|
||||||
var gridMeterDevice = new PlVarioMeterDevice(GridMeterChannel);
|
var gridMeterDevice = new PlVarioMeterDevice(GridMeterChannel);
|
||||||
var dcDcDevices = new TruConvertDcDcDevices(DcDcChannel);
|
var dcDcDevices = new TruConvertDcDcDevices(DcDcChannel);
|
||||||
|
|
||||||
var firstCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port1Cabinet, n)).ToList();
|
var firstCabinetBatteriesDevice =
|
||||||
var secondCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port2Cabinet, n)).ToList();
|
BatteryNodes.Select(n => new BatteryDeligreenDevice(Port1Cabinet, n)).ToList();
|
||||||
var thirdCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port3Cabinet, 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 batteryDevices1 = new BatteryDeligreenDevices(firstCabinetBatteriesDevice);
|
||||||
var batteryDevices2 = new BatteryDeligreenDevices(secondCabinetBatteriesDevice);
|
var batteryDevices2 = new BatteryDeligreenDevices(secondCabinetBatteriesDevice);
|
||||||
var batteryDevices3 = new BatteryDeligreenDevices(thirdCabinetBatteriesDevice);
|
var batteryDevices3 = new BatteryDeligreenDevices(thirdCabinetBatteriesDevice);
|
||||||
|
|
||||||
StatusRecord? ReadStatus()
|
StatusRecord? ReadStatus()
|
||||||
{
|
{
|
||||||
PlVarioMeterRecord? gridRecord = null;
|
PlVarioMeterRecord? gridRecord = null;
|
||||||
var config = Config.Load();
|
var config = Config.Load();
|
||||||
var kacoRecord = kacoDevice.Read();
|
var kacoRecord = kacoDevice.Read();
|
||||||
var gridrawRecord = gridMeterDevice.Read();
|
var gridrawRecord = gridMeterDevice.Read();
|
||||||
var dcDcRecord = dcDcDevices.Read();
|
var dcDcRecord = dcDcDevices.Read();
|
||||||
|
|
||||||
if (gridrawRecord != null)
|
if (gridrawRecord != null)
|
||||||
{
|
{
|
||||||
gridRecord = new PlVarioMeterRecord(gridrawRecord);
|
gridRecord = new PlVarioMeterRecord(gridrawRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
var batteryKabinet1 = batteryDevices1.Read();
|
var batteryKabinet1 = batteryDevices1.Read();
|
||||||
var batteryKabinet2 = batteryDevices2.Read();
|
var batteryKabinet2 = batteryDevices2.Read();
|
||||||
var batteryKabinet3 = batteryDevices3.Read();
|
var batteryKabinet3 = batteryDevices3.Read();
|
||||||
|
|
||||||
|
var listOfBatteriesRecord = new List<BatteryDeligreenRecords>();
|
||||||
|
if (batteryKabinet1 != null) listOfBatteriesRecord.Add(batteryKabinet1);
|
||||||
|
if (batteryKabinet2 != null) listOfBatteriesRecord.Add(batteryKabinet2);
|
||||||
|
if (batteryKabinet3 != null) listOfBatteriesRecord.Add(batteryKabinet3);
|
||||||
|
|
||||||
|
|
||||||
return new StatusRecord
|
return new StatusRecord
|
||||||
{
|
{
|
||||||
InverterRecord = kacoRecord,
|
InverterRecord = kacoRecord,
|
||||||
GridMeterRecord = gridRecord,
|
GridMeterRecord = gridRecord,
|
||||||
DcDc = dcDcRecord,
|
DcDc = dcDcRecord,
|
||||||
BatteryKabinet1 = batteryKabinet1,
|
ListOfBatteriesRecord = listOfBatteriesRecord,
|
||||||
BatteryKabinet2 = batteryKabinet2,
|
StateMachine = StateMachine.Default,
|
||||||
BatteryKabinet3 = batteryKabinet3,
|
|
||||||
Config = config // load from disk every iteration, so config can be changed while running
|
Config = config // load from disk every iteration, so config can be changed while running
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,35 +180,25 @@ internal static class Program
|
||||||
var statusrecord = ReadStatus();
|
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?.InverterRecord?.BatteryLimitsEnable.WriteLine(" BatteryLimitsEnable");
|
||||||
// statusrecord?.DcDc?.Dc.Battery.Voltage .WriteLine(" Voltage");
|
|
||||||
// statusrecord?.DcDc?.Dc.Battery.Current .WriteLine(" Current");
|
|
||||||
// statusrecord?.DcDc?.Dc.Link.Voltage .WriteLine(" Dc link Voltage");
|
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
statusrecord?.GridMeterRecord?.Frequency .WriteLine(" Frequency");
|
Console.WriteLine(" ********************************* Kaco Inverter *********************************");
|
||||||
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?.ActivePowerW.WriteLine(" Inverter Power");
|
||||||
statusrecord?.InverterRecord?.CurrentState.WriteLine(" CurrentState");
|
statusrecord?.InverterRecord?.CurrentState.WriteLine(" CurrentState");
|
||||||
statusrecord?.InverterRecord?.RequestedState.WriteLine(" RequestedState");
|
statusrecord?.InverterRecord?.RequestedState.WriteLine(" RequestedState");
|
||||||
statusrecord?.InverterRecord?.PcuError.WriteLine(" PcuError");
|
statusrecord?.InverterRecord?.PcuError.WriteLine(" PcuError");
|
||||||
statusrecord?.InverterRecord?.PcuState.WriteLine(" PcuState");
|
statusrecord?.InverterRecord?.PcuState.WriteLine(" PcuState");
|
||||||
|
|
||||||
statusrecord?.InverterRecord?.BattCharId.WriteLine(" _battCharId");
|
|
||||||
statusrecord?.InverterRecord?.BattCharLength.WriteLine(" _battCharLength");
|
|
||||||
|
|
||||||
statusrecord?.InverterRecord?.MinDischargeVoltage.WriteLine(" MinDischargeVoltage");
|
statusrecord?.InverterRecord?.MinDischargeVoltage.WriteLine(" MinDischargeVoltage");
|
||||||
statusrecord?.InverterRecord?.MaxDischargeCurrent.WriteLine(" MaxDischargeCurrent");
|
statusrecord?.InverterRecord?.MaxDischargeCurrent.WriteLine(" MaxDischargeCurrent");
|
||||||
statusrecord?.InverterRecord?.DischargeCutoffCurrent.WriteLine(" DischargeCutoffCurrent");
|
statusrecord?.InverterRecord?.DischargeCutoffCurrent.WriteLine(" DischargeCutoffCurrent");
|
||||||
|
|
@ -205,17 +206,31 @@ internal static class Program
|
||||||
statusrecord?.InverterRecord?.MaxChargeVoltage.WriteLine(" MaxChargeVoltage");
|
statusrecord?.InverterRecord?.MaxChargeVoltage.WriteLine(" MaxChargeVoltage");
|
||||||
statusrecord?.InverterRecord?.MaxChargeCurrent.WriteLine(" MaxChargeCurrent");
|
statusrecord?.InverterRecord?.MaxChargeCurrent.WriteLine(" MaxChargeCurrent");
|
||||||
statusrecord?.InverterRecord?.ChargeCutoffCurrent.WriteLine(" ChargeCutoffCurrent");
|
statusrecord?.InverterRecord?.ChargeCutoffCurrent.WriteLine(" ChargeCutoffCurrent");
|
||||||
|
statusrecord?.InverterRecord?.ActivePowerSetPercent.WriteLine( "ActivePowerSetPercent");
|
||||||
|
|
||||||
statusrecord?.InverterRecord?.ActivePowerSetPercent.WriteLine(" ActivePowerSetPercent");
|
statusrecord?.ControlSystemState();
|
||||||
statusrecord?.InverterRecord?.ReactivePowerSetPercent.WriteLine(" ReactivePowerSetPercent");
|
var i = 0;
|
||||||
statusrecord?.InverterRecord?.WatchdogSeconds.WriteLine(" WatchdogSeconds");
|
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);
|
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
|
statusrecord?.Config.Save(); // save the config file
|
||||||
if (statusrecord?.InverterRecord != null) kacoDevice.Write(statusrecord.InverterRecord);
|
if (statusrecord?.InverterRecord != null) kacoDevice.Write(statusrecord.InverterRecord);
|
||||||
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff"));
|
|
||||||
|
|
||||||
return statusrecord;
|
return statusrecord;
|
||||||
}
|
}
|
||||||
|
|
@ -230,8 +245,7 @@ internal static class Program
|
||||||
|
|
||||||
private static async Task SavingLocalCsvFile(Int64 timestamp, String csv)
|
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
|
// Ensure directory exists
|
||||||
if (!Directory.Exists(directoryPath))
|
if (!Directory.Exists(directoryPath))
|
||||||
{
|
{
|
||||||
|
|
@ -300,10 +314,10 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||||
|
|
||||||
var parts = line.Split(';');
|
var parts = line.Split(';');
|
||||||
var keyPath = parts[0];
|
var keyPath = parts[0];
|
||||||
var value = parts[1];
|
var value = parts[1];
|
||||||
var unit = parts.Length > 2 ? parts[2].Trim() : "";
|
var unit = parts.Length > 2 ? parts[2].Trim() : "";
|
||||||
InsertIntoJson(jsonData, keyPath.Split('/'), value);
|
InsertIntoJson(jsonData, keyPath.Split('/'), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,6 +382,7 @@ internal static class Program
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileCounter++;
|
_fileCounter++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -381,9 +396,9 @@ internal static class Program
|
||||||
var returnedStatus = new StatusMessage
|
var returnedStatus = new StatusMessage
|
||||||
{
|
{
|
||||||
InstallationId = installationId,
|
InstallationId = installationId,
|
||||||
Product = 3,
|
Product = 3,
|
||||||
Status = _sodiAlarmState,
|
Status = _sodiAlarmState,
|
||||||
Type = MessageType.Heartbit,
|
Type = MessageType.Heartbit,
|
||||||
};
|
};
|
||||||
if (s3Bucket != null)
|
if (s3Bucket != null)
|
||||||
RabbitMqManager.InformMiddleware(returnedStatus);
|
RabbitMqManager.InformMiddleware(returnedStatus);
|
||||||
|
|
@ -416,7 +431,8 @@ internal static class Program
|
||||||
//Create a zip directory and put the compressed file inside
|
//Create a zip directory and put the compressed file inside
|
||||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
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 entryStream = entry.Open())
|
||||||
using (var writer = new StreamWriter(entryStream))
|
using (var writer = new StreamWriter(entryStream))
|
||||||
{
|
{
|
||||||
|
|
@ -474,14 +490,16 @@ internal static class Program
|
||||||
var modbusData = new Dictionary<String, UInt16>();
|
var modbusData = new Dictionary<String, UInt16>();
|
||||||
|
|
||||||
// SYSTEM DATA
|
// 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);
|
var result2 = ConvertToModbusRegisters(status.InverterRecord!.PcuError, "UInt32", 30002);
|
||||||
|
|
||||||
// Merge all results into one dictionary
|
// Merge all results into one dictionary
|
||||||
|
|
||||||
var allResults = new[]
|
var allResults = new[]
|
||||||
{
|
{
|
||||||
result1,result2
|
result1, result2
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var result in allResults)
|
foreach (var result in allResults)
|
||||||
|
|
@ -491,14 +509,15 @@ internal static class Program
|
||||||
modbusData[entry.Key] = entry.Value;
|
modbusData[entry.Key] = entry.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to JSON
|
// Write to JSON
|
||||||
var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true });
|
var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true });
|
||||||
await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json);
|
await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json);
|
||||||
|
|
||||||
//Console.WriteLine("JSON file written successfully.");
|
//Console.WriteLine("JSON file written successfully.");
|
||||||
//Console.WriteLine(json);
|
//Console.WriteLine(json);
|
||||||
var stopTime = DateTime.Now;
|
var stopTime = DateTime.Now;
|
||||||
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff" )+ " Finish the loop");
|
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff") + " Finish the loop");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -515,13 +534,13 @@ internal static class Program
|
||||||
|
|
||||||
case "Int16":
|
case "Int16":
|
||||||
var int16Val = Convert.ToInt16(value);
|
var int16Val = Convert.ToInt16(value);
|
||||||
registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort
|
registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "UInt32":
|
case "UInt32":
|
||||||
var uint32Val = Convert.ToUInt32(value);
|
var uint32Val = Convert.ToUInt32(value);
|
||||||
registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word
|
registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word
|
||||||
registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word
|
registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Int32":
|
case "Int32":
|
||||||
|
|
@ -534,9 +553,15 @@ internal static class Program
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Unsupported output type: " + outputType);
|
throw new ArgumentException("Unsupported output type: " + outputType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return registers;
|
return registers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ForAll<T>(this IEnumerable<T> ts, Action<T> action)
|
||||||
|
{
|
||||||
|
foreach (var t in ts)
|
||||||
|
action(t);
|
||||||
|
}
|
||||||
|
|
||||||
private static void InitializeKacoStartup(StatusRecord? statusRecord)
|
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.
|
// 1. Apply DC – This part is physical and cannot be done in software.
|
||||||
// We assume DC power is already present.
|
// 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.
|
// All values temporarily set to "1" as requested.
|
||||||
// You will replace them later with real values.
|
// You will replace them later with real values.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
Console.WriteLine("2. Send real value");
|
||||||
|
|
||||||
if (statusRecord?.InverterRecord != null)
|
if (statusRecord?.InverterRecord != null)
|
||||||
{
|
{
|
||||||
statusRecord.InverterRecord.MinDischargeVoltage = 700f; // 64202.DisMinV
|
statusRecord.InverterRecord.MinDischargeVoltage = 700f; // 64202.DisMinV
|
||||||
|
|
@ -564,7 +600,8 @@ internal static class Program
|
||||||
|
|
||||||
//
|
//
|
||||||
// 3. Enable limits (EnLimit)
|
// 3. Enable limits (EnLimit)
|
||||||
//
|
|
||||||
|
Console.WriteLine("3. Enable limits ");
|
||||||
statusRecord.InverterRecord.BatteryLimitsEnable = EnableDisableEnum.Enabled;
|
statusRecord.InverterRecord.BatteryLimitsEnable = EnableDisableEnum.Enabled;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -579,6 +616,9 @@ internal static class Program
|
||||||
// - Then after grid/DC conditions: CurrentState == 1 (OFF) or 11 (GRID_CONNECTED)
|
// - Then after grid/DC conditions: CurrentState == 1 (OFF) or 11 (GRID_CONNECTED)
|
||||||
//
|
//
|
||||||
|
|
||||||
|
Console.WriteLine("3. Read current state");
|
||||||
|
|
||||||
|
|
||||||
var state = statusRecord.InverterRecord.CurrentState;
|
var state = statusRecord.InverterRecord.CurrentState;
|
||||||
|
|
||||||
Console.WriteLine($"KACO 64201.CurrentState = {state}");
|
Console.WriteLine($"KACO 64201.CurrentState = {state}");
|
||||||
|
|
@ -586,6 +626,7 @@ internal static class Program
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case CurrentState.Standby:
|
case CurrentState.Standby:
|
||||||
|
|
||||||
Console.WriteLine("Device is in STANDBY (8) — battery limits accepted.");
|
Console.WriteLine("Device is in STANDBY (8) — battery limits accepted.");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -601,9 +642,26 @@ internal static class Program
|
||||||
Console.WriteLine("Device in unexpected state: " + state);
|
Console.WriteLine("Device in unexpected state: " + state);
|
||||||
break;
|
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<String, Object> jsonDict, String[] keys, String value)
|
private static void InsertIntoJson(Dictionary<String, Object> jsonDict, String[] keys, String value)
|
||||||
{
|
{
|
||||||
var currentDict = jsonDict;
|
var currentDict = jsonDict;
|
||||||
|
|
@ -617,11 +675,10 @@ internal static class Program
|
||||||
|
|
||||||
if (i == keys.Length - 1) // Last key, store the value
|
if (i == keys.Length - 1) // Last key, store the value
|
||||||
{
|
{
|
||||||
|
if (!value.Contains(",") &&
|
||||||
if (!value.Contains(",") && double.TryParse(value, out Double doubleValue)) // Try to parse value as a number
|
double.TryParse(value, out Double doubleValue)) // Try to parse value as a number
|
||||||
{
|
{
|
||||||
currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
|
currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,376 @@
|
||||||
|
using InnovEnergy.App.KacoCommunication.ESS;
|
||||||
|
using InnovEnergy.Lib.Devices.Kaco92L3.DataType;
|
||||||
|
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.KacoCommunication.System;
|
||||||
|
|
||||||
|
public static class KacoCurrentStateController
|
||||||
|
{
|
||||||
|
// Call every 2 seconds
|
||||||
|
public static Boolean ControlSystemState(this StatusRecord s)
|
||||||
|
{
|
||||||
|
var cs = s.InverterRecord.CurrentState; // 64201.CurrentState (1..12)
|
||||||
|
s.StateMachine.State = (int)cs;
|
||||||
|
|
||||||
|
return cs switch
|
||||||
|
{
|
||||||
|
CurrentState.Off => State_Off(s),
|
||||||
|
CurrentState.Sleeping => State_Sleeping(s),
|
||||||
|
CurrentState.Starting => State_Starting(s),
|
||||||
|
CurrentState.Mppt => State_Mppt(s),
|
||||||
|
CurrentState.Throttled => State_Throttled(s),
|
||||||
|
CurrentState.ShuttingDown => State_ShuttingDown(s),
|
||||||
|
CurrentState.Fault => State_Fault(s),
|
||||||
|
CurrentState.Standby => State_Standby(s),
|
||||||
|
CurrentState.Precharge => State_Precharge(s),
|
||||||
|
CurrentState.GridPreConnected=> State_GridPreConnected(s),
|
||||||
|
CurrentState.GridConnected => State_GridConnected(s),
|
||||||
|
CurrentState.NoErrorPending => State_NoErrorPending(s),
|
||||||
|
_ => UnknownState(s)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Global rule: only allow power writes in 11 or 5
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
private static void EnforcePowerRules(StatusRecord s)
|
||||||
|
{
|
||||||
|
var cs = s.InverterRecord.CurrentState;
|
||||||
|
if (cs is not (CurrentState.GridConnected or CurrentState.Throttled))
|
||||||
|
{
|
||||||
|
// must be 0 outside (11) or (5)
|
||||||
|
s.InverterRecord.ActivePowerSetPercent = 0f;
|
||||||
|
//s.InverterRecord.ReactivePowerSetPercent = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// State handlers (based purely on CurrentState)
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static bool State_Off(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "OFF: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Write limits (ignore details)
|
||||||
|
// WriteLimits();
|
||||||
|
|
||||||
|
// Always aim for running
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Sleeping(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "SLEEPING: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// s.InverterRecord.WriteLimits();
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Standby(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "STANDBY: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// s.InverterRecord.WriteLimits();
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Mppt(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "MPPT: keep requesting connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Starting(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "STARTING: keep requesting connect (11), wait for 10/11/5.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Precharge(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "PRECHARGE: keep requesting connect (11), wait.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_GridPreConnected(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "GRID_PRE_CONNECTED: keep requesting connect (11), wait for 11/5.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_GridConnected(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "GRID_CONNECTED: running. Power writes allowed.";
|
||||||
|
|
||||||
|
// Keep request latched
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
|
||||||
|
// Here you may write power setpoints (your own targets)
|
||||||
|
// Example:
|
||||||
|
// s.InverterRecord.ControlMode = ControlModeEnum.RpcRemote;
|
||||||
|
s.InverterRecord.ActivePowerSetPercent = s.Config.ActivePowerPercent;
|
||||||
|
// s.InverterRecord.ReactivePowerSetPercent = s.Targets.ReactivePowerPercent;
|
||||||
|
|
||||||
|
return true; // end goal reached
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Throttled(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "THROTTLED: still running. Power writes allowed.";
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
|
||||||
|
// Power writes allowed here too
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_ShuttingDown(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "SHUTTING_DOWN: keep requesting connect (11); will reconnect after reaching 8/1.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Fault(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "FAULT: power=0 and acknowledge with RequestedState=1 (OFF).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Per doc: acknowledge uses RequestedState=1
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.Off;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_NoErrorPending(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "NO_ERROR_PENDING: acknowledge with RequestedState=1 then controller will request 11 next cycles.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Per doc Step 8: set RequestedState to 1 to acknowledge
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.Off;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UnknownState(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = $"UNKNOWN CurrentState={s.InverterRecord.CurrentState}. For safety, power=0 and request 11.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
public static class Controller
|
||||||
|
{
|
||||||
|
private static UInt16 GetSystemState(this StatusRecord r)
|
||||||
|
{
|
||||||
|
if (r.InverterRecord != null)
|
||||||
|
{
|
||||||
|
return (UInt16)r.InverterRecord.CurrentState;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (UInt16)StateMachine.Default.State;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean ControlSystemState(this StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.State = s.GetSystemState();
|
||||||
|
|
||||||
|
var cs = s.InverterRecord?.CurrentState; // 64201.CurrentState (1..12)
|
||||||
|
s.StateMachine.State = (UInt16)cs;
|
||||||
|
|
||||||
|
return s.StateMachine.State switch
|
||||||
|
{
|
||||||
|
1 => State_Off(s),
|
||||||
|
2 => State_Sleeping(s),
|
||||||
|
3 => State_Starting(s),
|
||||||
|
4 => State_Mppt(s),
|
||||||
|
5 => State_Throttled(s),
|
||||||
|
6 => State_ShuttingDown(s),
|
||||||
|
7 => State_Fault(s),
|
||||||
|
8 => State_Standby(s),
|
||||||
|
9 => State_Precharge(s),
|
||||||
|
10 => State_GridPreConnected(s),
|
||||||
|
11 => State_GridConnected(s),
|
||||||
|
12 => State_NoErrorPending(s),
|
||||||
|
_ => UnknownState(s)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Global rule: only allow power writes in 11 or 5
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
private static void EnforcePowerRules(StatusRecord s)
|
||||||
|
{
|
||||||
|
var cs = s.InverterRecord?.CurrentState;
|
||||||
|
|
||||||
|
// must be 0 outside (11) or (5)
|
||||||
|
s.InverterRecord.ActivePowerSetPercent = 0f;
|
||||||
|
s.InverterRecord.ReactivePowerSetPercent = 0f; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// State handlers (based purely on CurrentState)
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static Boolean State_Off(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "OFF: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Write limits (ignore details)
|
||||||
|
s.InverterRecord.WriteLimits();
|
||||||
|
|
||||||
|
// Always aim for running
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool State_Sleeping(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "SLEEPING: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.WriteLimits();
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Standby(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "STANDBY: write limits (once) and request connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.WriteLimits();
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Mppt(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "MPPT: keep requesting connect (11).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Starting(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "STARTING: keep requesting connect (11), wait for 10/11/5.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Precharge(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "PRECHARGE: keep requesting connect (11), wait.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_GridPreConnected(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "GRID_PRE_CONNECTED: keep requesting connect (11), wait for 11/5.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_GridConnected(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "GRID_CONNECTED: running. Power writes allowed.";
|
||||||
|
|
||||||
|
// Keep request latched
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
|
||||||
|
// Here you may write power setpoints (your own targets)
|
||||||
|
// Example:
|
||||||
|
// s.InverterRecord.ControlMode = ControlModeEnum.RpcRemote;
|
||||||
|
// s.InverterRecord.ActivePowerSetPercent = s.Targets.ActivePowerPercent;
|
||||||
|
// s.InverterRecord.ReactivePowerSetPercent = s.Targets.ReactivePowerPercent;
|
||||||
|
|
||||||
|
return true; // end goal reached
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Throttled(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "THROTTLED: still running. Power writes allowed.";
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
|
||||||
|
// Power writes allowed here too
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_ShuttingDown(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "SHUTTING_DOWN: keep requesting connect (11); will reconnect after reaching 8/1.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_Fault(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "FAULT: power=0 and acknowledge with RequestedState=1 (OFF).";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Per doc: acknowledge uses RequestedState=1
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.Off;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State_NoErrorPending(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = "NO_ERROR_PENDING: acknowledge with RequestedState=1 then controller will request 11 next cycles.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
// Per doc Step 8: set RequestedState to 1 to acknowledge
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.Off;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UnknownState(StatusRecord s)
|
||||||
|
{
|
||||||
|
s.StateMachine.Message = $"UNKNOWN CurrentState={s.InverterRecord.CurrentState}. For safety, power=0 and request 11.";
|
||||||
|
EnforcePowerRules(s);
|
||||||
|
|
||||||
|
s.InverterRecord.RequestedState = ReuqestedState.GridConnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
@ -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" };
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ public class Config
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||||
|
|
||||||
public required Double MinSoc { get; set; }
|
public required Double MinSoc { get; set; }
|
||||||
|
public required float ActivePowerPercent { get; set; }
|
||||||
|
|
||||||
/* public required Double MaximumDischargingCurrent { get; set; }
|
/* public required Double MaximumDischargingCurrent { get; set; }
|
||||||
public required Double MaximumChargingCurrent { get; set; }
|
public required Double MaximumChargingCurrent { get; set; }
|
||||||
|
|
@ -29,8 +30,8 @@ public class Config
|
||||||
public static Config Default => new()
|
public static Config Default => new()
|
||||||
{
|
{
|
||||||
MinSoc = 20,
|
MinSoc = 20,
|
||||||
/* MaximumChargingCurrent = 180,
|
ActivePowerPercent = 0f,
|
||||||
MaximumDischargingCurrent = 180,
|
/* MaximumDischargingCurrent = 180,
|
||||||
BatteriesCount = 0,
|
BatteriesCount = 0,
|
||||||
ModbusProtcolNumber = 1.2,*/
|
ModbusProtcolNumber = 1.2,*/
|
||||||
Devices = new ()
|
Devices = new ()
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,11 @@ public partial class KacoRecord
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Int16 BatteryCurrentScaleFactor => _battCharASf;
|
public Int16 BatteryCurrentScaleFactor => _battCharASf;
|
||||||
|
|
||||||
|
|
||||||
|
public Int16 ActivePowerW => _activePowerW;
|
||||||
|
public Int16 ReactivePowerVar => _reactivePowerVar;
|
||||||
|
public Int16 LineFrequencyHz => _lineFrequencyHz;
|
||||||
|
|
||||||
// Helper wrappers for scaled values
|
// Helper wrappers for scaled values
|
||||||
private float ScaleBattVoltage(UInt16 raw) => ScaleSunspec(raw, _battCharVSf);
|
private float ScaleBattVoltage(UInt16 raw) => ScaleSunspec(raw, _battCharVSf);
|
||||||
private float ScaleBattCurrent(UInt16 raw) => ScaleSunspec(raw, _battCharASf);
|
private float ScaleBattCurrent(UInt16 raw) => ScaleSunspec(raw, _battCharASf);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public partial class KacoRecord
|
||||||
// State control
|
// State control
|
||||||
[HoldingRegister<UInt16>(41065, writable: true)] private UInt16 _requestedState; // 0xA069 – RequestedState (enum16, RW)
|
[HoldingRegister<UInt16>(41065, writable: true)] private UInt16 _requestedState; // 0xA069 – RequestedState (enum16, RW)
|
||||||
[HoldingRegister<UInt16>(41066)] private UInt16 _currentState; // 0xA06A – CurrentState (enum16, R)
|
[HoldingRegister<UInt16>(41066)] private UInt16 _currentState; // 0xA06A – CurrentState (enum16, R)
|
||||||
[HoldingRegister<UInt16>(41067, writable: true)] private UInt16 _controlMode; // 0xA06B – ControlMode (enum16, RW)
|
[HoldingRegister<UInt16>(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<UInt16>(41068)] private UInt16 _reserved7; // 0xA06C – Reserved
|
[HoldingRegister<UInt16>(41068)] private UInt16 _reserved7; // 0xA06C – Reserved
|
||||||
|
|
||||||
// Watchdog / setpoints
|
// Watchdog / setpoints
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue