376 lines
13 KiB
C#
376 lines
13 KiB
C#
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;
|
|
}
|
|
|
|
}*/ |