From cbd4801568d6d73ebbf036aaa3061e60b790ca1f Mon Sep 17 00:00:00 2001 From: atef Date: Fri, 27 Feb 2026 11:15:04 +0100 Subject: [PATCH] Add Machine state Controller --- .../KacoCommunication/System/Controller.cs | 376 ++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 csharp/App/KacoCommunication/System/Controller.cs diff --git a/csharp/App/KacoCommunication/System/Controller.cs b/csharp/App/KacoCommunication/System/Controller.cs new file mode 100644 index 000000000..ef14e4eb7 --- /dev/null +++ b/csharp/App/KacoCommunication/System/Controller.cs @@ -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; + } + +}*/ \ No newline at end of file