/* * Control.cs * * Copyright 2014-2025 Michael Zillgith * * This file is part of libIEC61850. * * libIEC61850 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libIEC61850 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libIEC61850. If not, see . * * See COPYING file for the complete license text. */ using IEC61850.Common; using System; using System.Runtime.InteropServices; namespace IEC61850 { // IEC 61850 common API parts (used by client and server API) namespace Common { /// /// Control model /// public enum ControlModel { /** status only */ STATUS_ONLY = 0, /** direct with normal security */ DIRECT_NORMAL = 1, /** select before operate (SBO) with normal security */ SBO_NORMAL = 2, /** direct with enhanced security */ DIRECT_ENHANCED = 3, /** select before operate (SBO) with enhanced security */ SBO_ENHANCED = 4 } /// /// Originator category /// public enum OrCat { /** Not supported - should not be used */ NOT_SUPPORTED = 0, /** Control operation issued from an operator using a client located at bay level */ BAY_CONTROL = 1, /** Control operation issued from an operator using a client located at station level */ STATION_CONTROL = 2, /** Control operation from a remote operator outside the substation (for example network control center) */ REMOTE_CONTROL = 3, /** Control operation issued from an automatic function at bay level */ AUTOMATIC_BAY = 4, /** Control operation issued from an automatic function at station level */ AUTOMATIC_STATION = 5, /** Control operation issued from a automatic function outside of the substation */ AUTOMATIC_REMOTE = 6, /** Control operation issued from a maintenance/service tool */ MAINTENANCE = 7, /** Status change occurred without control action (for example external trip of a circuit breaker or failure inside the breaker) */ PROCESS = 8 } } namespace Client { [StructLayout(LayoutKind.Sequential)] internal struct LastApplErrorInternal { public int ctlNum; public int error; public int addCause; } public class LastApplError { public int ctlNum; public int error; public ControlAddCause addCause; internal LastApplError(LastApplErrorInternal lastApplError) { addCause = (ControlAddCause)lastApplError.addCause; error = lastApplError.error; ctlNum = lastApplError.ctlNum; } } public enum ControlActionType { SELECT = 0, OPERATE = 1, CANCEL = 2 } /// /// Control object. /// public class ControlObject : IDisposable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern LastApplErrorInternal ControlObjectClient_getLastApplError(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr ControlObjectClient_create(string objectReference, IntPtr connection); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern int ControlObjectClient_getControlModel(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern int ControlObjectClient_getCtlValType(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern int ControlObjectClient_getLastError(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private static extern bool ControlObjectClient_operate(IntPtr self, IntPtr ctlVal, UInt64 operTime); /// /// Handler for asynchronous control actions (select, operate, cancel) /// public delegate void ControlActionHandler(UInt32 invokeId, Object parameter, IedClientError error, ControlActionType type, bool success); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void ControlObjectClient_ControlActionHandler(UInt32 invokeId, IntPtr parameter, int err, int type, [MarshalAs(UnmanagedType.I1)] bool success); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 ControlObjectClient_operateAsync(IntPtr self, out int err, IntPtr ctlVal, UInt64 operTime, ControlObjectClient_ControlActionHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 ControlObjectClient_selectAsync(IntPtr self, out int err, ControlObjectClient_ControlActionHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 ControlObjectClient_selectWithValueAsync(IntPtr self, out int err, IntPtr ctlVal, ControlObjectClient_ControlActionHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 ControlObjectClient_cancelAsync(IntPtr self, out int err, ControlObjectClient_ControlActionHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private static extern bool ControlObjectClient_select(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private static extern bool ControlObjectClient_selectWithValue(IntPtr self, IntPtr ctlVal); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private static extern bool ControlObjectClient_cancel(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_setOrigin(IntPtr self, string orIdent, int orCat); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_setInterlockCheck(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_setSynchroCheck(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_setTestMode(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalCommandTerminationHandler(IntPtr parameter, IntPtr controlClient); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void ControlObjectClient_setCommandTerminationHandler(IntPtr self, InternalCommandTerminationHandler handler, IntPtr handlerParameter); public delegate void CommandTerminationHandler(Object parameter, ControlObject controlObject); private IedConnection iedConnection; private IntPtr self; private CommandTerminationHandler commandTerminationHandler = null; private Object commandTerminationHandlerParameter = null; private void MyCommandTerminationHandler(IntPtr paramter, IntPtr controlClient) { if (commandTerminationHandler != null) commandTerminationHandler(commandTerminationHandlerParameter, this); } private InternalCommandTerminationHandler intCommandTerminationHandler; internal ControlObject(string objectReference, IntPtr connection, IedConnection iedConnection) { this.iedConnection = iedConnection; self = ControlObjectClient_create(objectReference, connection); if (self == System.IntPtr.Zero) throw new IedConnectionException("Control object not found", 0); intCommandTerminationHandler = new InternalCommandTerminationHandler(MyCommandTerminationHandler); ControlObjectClient_setCommandTerminationHandler(self, intCommandTerminationHandler, self); } /// /// Gets the control model. /// /// /// The control model. /// public ControlModel GetControlModel() { ControlModel controlModel = (ControlModel)ControlObjectClient_getControlModel(self); return controlModel; } /// /// Get the type of ctlVal. /// /// MmsType required for the ctlVal value. public MmsType GetCtlValType() { MmsType ctlValType = (MmsType)ControlObjectClient_getCtlValType(self); return ctlValType; } /// /// Sets the origin parameter used by control commands. /// /// /// Originator. An arbitrary string identifying the controlling client. /// /// /// Originator category. /// public void SetOrigin(string originator, OrCat originatorCategory) { ControlObjectClient_setOrigin(self, originator, (int)originatorCategory); } /// /// Gets the error code of the last synchronous control action (operate, select, select-with-value, cancel) /// /// error code. public IedClientError LastError { get { return (IedClientError)ControlObjectClient_getLastError(self); } } /// /// Operate the control with the specified control value. /// /// the new value of the control /// true when the operation has been successful, false otherwise public bool Operate(bool ctlVal) { return Operate(ctlVal, 0); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// true when the operation has been successful, false otherwise public bool Operate(bool ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); return Operate(value, operTime); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// true when the operation has been successful, false otherwise public bool Operate(float ctlVal) { return Operate(ctlVal, 0); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// true when the operation has been successful, false otherwise public bool Operate(float ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); return Operate(value, operTime); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// true when the operation has been successful, false otherwise public bool Operate(int ctlVal) { return Operate(ctlVal, 0); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// true when the operation has been successful, false otherwise public bool Operate(int ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); return Operate(value, operTime); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// true when the operation has been successful, false otherwise public bool Operate(MmsValue ctlVal) { return Operate(ctlVal, 0); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// true when the operation has been successful, false otherwise public bool Operate(MmsValue ctlVal, UInt64 operTime) { return ControlObjectClient_operate(self, ctlVal.valueReference, operTime); } private ControlObjectClient_ControlActionHandler internalOperateHandler = null; private void nativeOperateHandler(UInt32 invokeId, IntPtr parameter, int err, int type, bool success) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; ControlActionHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; handler(invokeId, handlerParameter, clientError, (ControlActionType)type, success); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(bool ctlVal, ControlActionHandler handler, object parameter) { return OperateAsync(ctlVal, 0, handler, parameter); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(bool ctlVal, UInt64 operTime, ControlActionHandler handler, object parameter) { MmsValue value = new MmsValue(ctlVal); return OperateAsync(value, operTime, handler, parameter); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(float ctlVal, ControlActionHandler handler, object parameter) { return OperateAsync(ctlVal, 0, handler, parameter); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(float ctlVal, UInt64 operTime, ControlActionHandler handler, object parameter) { MmsValue value = new MmsValue(ctlVal); return OperateAsync(value, operTime, handler, parameter); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(int ctlVal, ControlActionHandler handler, object parameter) { return OperateAsync(ctlVal, 0, handler, parameter); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(int ctlVal, UInt64 operTime, ControlActionHandler handler, object parameter) { return OperateAsync(ctlVal, operTime, handler, parameter); } /// /// Operate the control with the specified control value. /// /// the new value of the control /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(MmsValue ctlVal, ControlActionHandler handler, object parameter) { return OperateAsync(ctlVal, 0, handler, parameter); } /// /// Operate the control with the specified control value (time activated control). /// /// the new value of the control /// the time when the operation will be executed /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 OperateAsync(MmsValue ctlVal, UInt64 operTime, ControlActionHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalOperateHandler == null) internalOperateHandler = new ControlObjectClient_ControlActionHandler(nativeOperateHandler); UInt32 invokeId = ControlObjectClient_operateAsync(self, out error, ctlVal.valueReference, operTime, internalOperateHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Operate failed", error); } return invokeId; } /// /// Select the control object. /// /// true when the selection has been successful, false otherwise public bool Select() { return ControlObjectClient_select(self); } /// /// Select the control object. /// /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 SelectAsync(ControlActionHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalOperateHandler == null) internalOperateHandler = new ControlObjectClient_ControlActionHandler(nativeOperateHandler); UInt32 invokeId = ControlObjectClient_selectAsync(self, out error, internalOperateHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Select failed", error); } return invokeId; } /// /// Send a select with value command for generic MmsValue instances /// /// the value to be checked. /// true when the selection has been successful, false otherwise public bool SelectWithValue(MmsValue ctlVal) { return ControlObjectClient_selectWithValue(self, ctlVal.valueReference); } /// /// Send a select with value command for boolean controls /// /// /// the value to be checked. /// /// true when the selection has been successful, false otherwise public bool SelectWithValue(bool ctlVal) { return SelectWithValue(new MmsValue(ctlVal)); } /// /// Send a select with value command for integer controls /// /// /// the value to be checked. /// /// true when the selection has been successful, false otherwise public bool SelectWithValue(int ctlVal) { return SelectWithValue(new MmsValue(ctlVal)); } /// /// Send a select with value command for float controls /// /// /// the value to be checked. /// /// true when the selection has been successful, false otherwise public bool SelectWithValue(float ctlVal) { return SelectWithValue(new MmsValue(ctlVal)); } /// /// Send a select with value command for boolean controls - asynchronous version /// /// the value to be checked. /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 SelectWithValueAsync(bool ctlVal, ControlActionHandler handler, object parameter) { return SelectWithValueAsync(new MmsValue(ctlVal), handler, parameter); } /// /// Send a select with value command for integer controls - asynchronous version /// /// the value to be checked. /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 SelectWithValueAsync(int ctlVal, ControlActionHandler handler, object parameter) { return SelectWithValueAsync(new MmsValue(ctlVal), handler, parameter); } /// /// Send a select with value command for float controls - asynchronous version /// /// the value to be checked. /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 SelectWithValueAsync(float ctlVal, ControlActionHandler handler, object parameter) { return SelectWithValueAsync(new MmsValue(ctlVal), handler, parameter); } /// /// Send a select with value command for generic MmsValue instances - asynchronous version /// /// the value to be checked. /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 SelectWithValueAsync(MmsValue ctlVal, ControlActionHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalOperateHandler == null) internalOperateHandler = new ControlObjectClient_ControlActionHandler(nativeOperateHandler); UInt32 invokeId = ControlObjectClient_selectWithValueAsync(self, out error, ctlVal.valueReference, internalOperateHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Select with value failed", error); } return invokeId; } /// /// Cancel a selection or time activated operation /// /// true when the cancelation has been successful, false otherwise /// Callback function to handle the received response or service timeout /// User provided callback parameter. Will be passed to the callback function /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public bool Cancel() { return ControlObjectClient_cancel(self); } /// /// Cancel a selection or time activated operation /// public UInt32 CancelAsync(ControlActionHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalOperateHandler == null) internalOperateHandler = new ControlObjectClient_ControlActionHandler(nativeOperateHandler); UInt32 invokeId = ControlObjectClient_cancelAsync(self, out error, internalOperateHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Cancel failed", error); } return invokeId; } /// /// Enables the synchro check for operate commands /// [Obsolete("use SetSynchroCheck instead")] public void EnableSynchroCheck() { ControlObjectClient_setSynchroCheck(self, true); } /// /// Enables the interlock check for operate and select commands /// [Obsolete("use SetInterlockCheck instead")] public void EnableInterlockCheck() { ControlObjectClient_setInterlockCheck(self, true); } /// /// Sets the value of the interlock check flag for operate and select commands /// public void SetInterlockCheck(bool value) { ControlObjectClient_setInterlockCheck(self, value); } /// /// Sets the value of the synchro check flag for operate command /// public void SetSynchroCheck(bool value) { ControlObjectClient_setSynchroCheck(self, value); } /// /// Sets the value of the test flag for the operate command /// public void SetTestMode(bool value) { ControlObjectClient_setTestMode(self, value); } /// /// Gets the last received LastApplError (Additional Cause Diagnostics) value. /// /// /// The last appl error. /// public LastApplError GetLastApplError() { LastApplErrorInternal lastApplError = ControlObjectClient_getLastApplError(self); return new LastApplError(lastApplError); } /// /// Sets the command termination handler. /// /// /// the handler (delegate) that is invoked when a CommandTerminationMessage is received. /// /// /// Parameter. /// public void SetCommandTerminationHandler(CommandTerminationHandler handler, Object parameter) { commandTerminationHandler = handler; commandTerminationHandlerParameter = parameter; } protected virtual void Dispose(bool disposing) { if (self != System.IntPtr.Zero) { ControlObjectClient_destroy(self); self = System.IntPtr.Zero; } } public void Dispose() { Dispose(true); } ~ControlObject() { Dispose(false); } } } }