Add Machine state Controller

This commit is contained in:
atef 2026-02-27 11:15:04 +01:00
parent da9d2b1f40
commit cbd4801568
1 changed files with 376 additions and 0 deletions

View File

@ -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;
}
}*/