/* * IEC61850ClientAPI.cs * * Copyright 2014-2023 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 System; using System.Text; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Collections; using IEC61850.Common; using IEC61850.TLS; // IEC 61850 API for the libiec61850 .NET wrapper library namespace IEC61850 { // IEC 61850 client API. namespace Client { [StructLayout(LayoutKind.Sequential)] public class MmsServerIdentity { public string vendorName; public string modelName; public string revision; } /// /// Represents an MmsConnection object (a single connection to an MMS server) /// public class MmsConnection { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr MmsConnection_create(); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void MmsConnection_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr MmsConnection_identify(IntPtr self, out int mmsError); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void MmsServerIdentity_destroy(IntPtr self); [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] private static extern void MmsConnection_setLocalDetail (IntPtr self, Int32 localDetail); [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] private static extern Int32 MmsConnection_getLocalDetail (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern Int32 MmsConnection_setRequestTimeout(IntPtr self, UInt32 timeoutInMs); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr MmsConnection_readMultipleVariables(IntPtr self, out int mmsError, string domainId, IntPtr items); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void MmsValue_delete(IntPtr self); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void LinkedListValueDeleteFunction(IntPtr pointer); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void LinkedList_destroyDeep(IntPtr list, LinkedListValueDeleteFunction valueDeleteFunction); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr LinkedList_create(); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void LinkedList_add(IntPtr self, IntPtr data); private IntPtr self = IntPtr.Zero; private bool selfDestroy = false; public MmsConnection() { selfDestroy = true; self = MmsConnection_create(); } internal MmsConnection(IntPtr mmsConnection) { self = mmsConnection; } ~MmsConnection () { if (selfDestroy) if (self != IntPtr.Zero) MmsConnection_destroy(self); } private void FreeHGlobaleDeleteFunction(IntPtr pointer) { Marshal.FreeHGlobal(pointer); } /// /// Requests the server identity information /// /// The server identity. public MmsServerIdentity GetServerIdentity() { int mmsError; if (self == IntPtr.Zero) { throw new IedConnectionException("Pointer is Zero!"); } IntPtr identity = MmsConnection_identify(self, out mmsError); if (mmsError != 0) throw new IedConnectionException("Failed to read server identity", mmsError); if (identity == IntPtr.Zero) throw new IedConnectionException("Failed to read server identity"); MmsServerIdentity serverIdentity = (MmsServerIdentity) Marshal.PtrToStructure(identity, typeof(MmsServerIdentity)); MmsServerIdentity_destroy(identity); return serverIdentity; } /// /// Sets the local detail (maximum MMS PDU size) /// /// maximum accepted MMS PDU size in bytes public void SetLocalDetail(int localDetail) { MmsConnection_setLocalDetail(self, localDetail); } /// /// Gets the local detail (maximum MMS PDU size) /// /// maximum accepted MMS PDU size in bytes public int GetLocalDetail() { return MmsConnection_getLocalDetail(self); } /// /// Sets the request timeout /// /// request timeout in milliseconds public void SetRequestTimeout(uint timeoutMs) { MmsConnection_setRequestTimeout(self, timeoutMs); } /// /// Reads multipe MMS variables from the same domain /// /// MmsValue of type MMS_ARRAY containing the access results for the requested variables. /// the domain name (logical device) /// list of variable names (in MMS notation e.g. "GGIO$ST$Ind1") public MmsValue ReadMultipleVariables(string domainName, List variables) { IntPtr linkedList = LinkedList_create(); foreach (string variableName in variables) { IntPtr handle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(variableName); LinkedList_add(linkedList, handle); } int error; IntPtr mmsValue = MmsConnection_readMultipleVariables(self, out error, domainName, linkedList); LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction)); if (error != 0) { if (mmsValue != IntPtr.Zero) MmsValue_delete(mmsValue); throw new IedConnectionException("ReadMultipleVariables failed", error); } return new MmsValue(mmsValue, true); } } /// /// Represents a variable in a log entry /// public class MmsJournalVariable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsJournalVariable_getTag(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsJournalVariable_getValue(IntPtr self); private IntPtr self; internal MmsJournalVariable(IntPtr self) { this.self = self; } public string GetTag() { return Marshal.PtrToStringAnsi(MmsJournalVariable_getTag(self)); } public MmsValue GetValue() { MmsValue mmsValue = new MmsValue(MmsJournalVariable_getValue(self)); return mmsValue; } } /// /// Represents an entry of a log /// public class MmsJournalEntry { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void MmsJournalEntry_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsJournalEntry_getEntryID(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsJournalEntry_getOccurenceTime(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsJournalEntry_getJournalVariables(IntPtr self); /**************** * LinkedList ***************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_getNext(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_getData(IntPtr self); private IntPtr self; private List variables = null; internal MmsJournalEntry(IntPtr self) { this.self = self; } /// /// Gets the journal variables. /// /// The journal variables. public List GetJournalVariables() { if (variables == null) { IntPtr linkedList = MmsJournalEntry_getJournalVariables(self); IntPtr element = LinkedList_getNext(linkedList); variables = new List(); while (element != IntPtr.Zero) { MmsJournalVariable journalVariable = new MmsJournalVariable(LinkedList_getData(element)); variables.Add(journalVariable); element = LinkedList_getNext(element); } } return variables; } /// /// Gets the entry identifier of the log entry /// /// The entry identifier. public byte[] GetEntryID() { IntPtr mmsValuePtr = MmsJournalEntry_getEntryID(self); MmsValue mmsValue = new MmsValue(mmsValuePtr); byte[] octetString = mmsValue.getOctetString(); return octetString; } /// /// Gets the occurence time of the log entry /// /// The occurence time. public ulong GetOccurenceTime() { IntPtr mmsValuePtr = MmsJournalEntry_getOccurenceTime(self); MmsValue mmsValue = new MmsValue(mmsValuePtr); return mmsValue.GetBinaryTimeAsUtcMs(); } /// /// Releases all resource used by the object. /// public void Dispose() { if (self != IntPtr.Zero) { MmsJournalEntry_destroy(self); self = IntPtr.Zero; } } ~MmsJournalEntry () { Dispose(); } } /// /// Asynchonous service handler for the get RCB values service /// /// The invoke ID of the request triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout /// the report control block instance public delegate void GetRCBValuesHandler(UInt32 invokeId,object parameter,IedClientError err,ReportControlBlock rcb); /// /// Asynchonous service handler for the set RCB values service /// /// The invoke ID of the request triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout /// the report control block instance public delegate void SetRCBValuesHandler(UInt32 invokeId,object parameter,IedClientError err,ReportControlBlock rcb); /// /// Generic asynchonous service handler - used by simple services that have only success or error result /// /// The invoke ID of the request triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout public delegate void GenericServiceHandler(UInt32 invokeId,object parameter,IedClientError err); /// /// This class acts as the entry point for the IEC 61850 client API. It represents a single /// (MMS) connection to a server. /// public partial class IedConnection : IDisposable { /************* * MmsValue *************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_toString(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern float MmsValue_toFloat(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool MmsValue_getBoolean(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 MmsValue_getBitStringAsInteger(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int MmsValue_getType(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void MmsValue_delete(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int MmsValue_getDataAccessError(IntPtr self); /**************** * IedConnection ***************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_create(); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_createWithTlsSupport(IntPtr tlsConfig); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int Connection_getState(IedConnection self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setConnectTimeout(IntPtr self, UInt32 timeoutInMs); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setRequestTimeout(IntPtr self, UInt32 timeoutInMs); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getRequestTimeout(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setMaxOutstandingCalls(IntPtr self, int calling, int called); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setTimeQuality(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool leapSecondKnown, [MarshalAs(UnmanagedType.I1)] bool clockFailure, [MarshalAs(UnmanagedType.I1)] bool clockNotSynchronized, int subsecondPrecision); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_connect(IntPtr self, out int error, string hostname, int tcpPort); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setLocalAddress(IntPtr self, string localIpAddress, int localPort); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_abort(IntPtr self, out int error); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IedConnection_release(IntPtr self, out int error); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IedConnection_close(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_readObject(IntPtr self, out int error, string objectReference, int fc); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_writeObject(IntPtr self, out int error, string dataAttributeReference, int fc, IntPtr value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataDirectory(IntPtr self, out int error, string dataReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataDirectoryByFC(IntPtr self, out int error, string dataReference, FunctionalConstraint fc); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataDirectoryFC(IntPtr self, out int error, string dataReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getLogicalNodeDirectory(IntPtr self, out int error, string logicalNodeReference, int acsiClass); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getServerDirectory(IntPtr self, out int error, [MarshalAs(UnmanagedType.I1)] bool getFileNames); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_getDeviceModelFromServer(IntPtr self, out int error); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getLogicalDeviceDirectory(IntPtr self, out int error, string logicalDeviceName); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getVariableSpecification(IntPtr self, out int error, string objectReference, int fc); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalConnectionClosedHandler(IntPtr parameter,IntPtr Iedconnection); /// /// Called when the connection is closed /// public delegate void ConnectionClosedHandler(IedConnection connection); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installConnectionClosedHandler(IntPtr self, InternalConnectionClosedHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_readDataSetValues(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_writeDataSetValues(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr values, out IntPtr accessResults); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_createDataSet(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IedConnection_deleteDataSet(IntPtr self, out int error, string dataSetReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataSetDirectory(IntPtr self, out int error, string dataSetReference, [MarshalAs(UnmanagedType.I1)] out bool isDeletable); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getMmsConnection(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getFileDirectoryEx(IntPtr self, out int error, string directoryName, string continueAfter , [MarshalAs(UnmanagedType.I1)] out bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_queryLogAfter(IntPtr self, out int error, string logReference, IntPtr entryID, ulong timeStamp, [MarshalAs(UnmanagedType.I1)] out bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_queryLogByTime(IntPtr self, out int error, string logReference, ulong startTime, ulong endTime, [MarshalAs(UnmanagedType.I1)] out bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getRCBValues(IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setRCBValues(IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, [MarshalAs(UnmanagedType.I1)] bool singleRequest); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installReportHandler(IntPtr connection, string rcbReference, string rptId, InternalReportHandler handler, IntPtr handlerParameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_uninstallReportHandler(IntPtr connection, string rcbReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int IedConnection_getState(IntPtr connection); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalStateChangedHandler(IntPtr parameter,IntPtr iedConnection,int newState); /// /// Called when there is a change in the connection state /// public delegate void StateChangedHandler(IedConnection connection, IedConnectionState newState); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); /********************* * Async functions *********************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_connectAsync(IntPtr self, out int error, string hostname, int tcpPort); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_abortAsync(IntPtr self, out int error); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IedConnection_releaseAsync(IntPtr self, out int error); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_ReadObjectHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_readObjectAsync(IntPtr self, out int error, string objRef, int fc, IedConnection_ReadObjectHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_WriteObjectHandler(UInt32 invokeId,IntPtr parameter,int err); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_writeObjectAsync(IntPtr self, out int error, string objRef, int fc, IntPtr value, IedConnection_WriteObjectHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GetNameListHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr nameList,[MarshalAs(UnmanagedType.I1)] bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getServerDirectoryAsync(IntPtr self, out int error, string continueAfter, IntPtr result, IedConnection_GetNameListHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getLogicalDeviceVariablesAsync(IntPtr self, out int error, string ldName, string continueAfter, IntPtr result, IedConnection_GetNameListHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getLogicalDeviceDataSetsAsync(IntPtr self, out int error, string ldName, string continueAfter, IntPtr result, IedConnection_GetNameListHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_QueryLogHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr journalEntries,[MarshalAs(UnmanagedType.I1)] bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_queryLogByTimeAsync(IntPtr self, out int error, string logReference, UInt64 startTime, UInt64 endTime, IedConnection_QueryLogHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_queryLogAfterAsync(IntPtr self, out int error, string logReference, IntPtr entryID, UInt64 timeStamp, IedConnection_QueryLogHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GetVariableSpecificationHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr spec); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getVariableSpecificationAsync(IntPtr self, out int error, string dataAttributeReference, int fc, IedConnection_GetVariableSpecificationHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_ReadDataSetHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSet); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_readDataSetValuesAsync(IntPtr self, out int error, string dataSetReference, IntPtr dataSet, IedConnection_ReadDataSetHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_createDataSetAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet, IedConnection_GenericServiceHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_deleteDataSetAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IedConnection_GenericServiceHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GetDataSetDirectoryHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSetDirectory, [MarshalAs(UnmanagedType.I1)] bool isDeletable); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getDataSetDirectoryAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IedConnection_GetDataSetDirectoryHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GetRCBValuesHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr rcb); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getRCBValuesAsync(IntPtr self, out int error, string rcbReference, IntPtr updateRcb, IedConnection_GetRCBValuesHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GenericServiceHandler(UInt32 invokeId,IntPtr parameter,int err); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_setRCBValuesAsync(IntPtr self, out int error, IntPtr rcb, UInt32 parametersMask, [MarshalAs(UnmanagedType.I1)] bool singleRequest, IedConnection_GenericServiceHandler handler, IntPtr parameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_deleteFileAsync(IntPtr self, out int error, string fileName, IedConnection_GenericServiceHandler handler, IntPtr parameter); /******************** * FileDirectoryEntry *********************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void FileDirectoryEntry_destroy(IntPtr self); /**************** * LinkedList ***************/ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_getNext(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_getData(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void LinkedList_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void LinkedList_destroyStatic(IntPtr self); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void LinkedListValueDeleteFunction(IntPtr pointer); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void LinkedList_destroyDeep(IntPtr list, LinkedListValueDeleteFunction valueDeleteFunction); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_create(); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void LinkedList_add(IntPtr self, IntPtr data); private IntPtr connection = IntPtr.Zero; private InternalConnectionClosedHandler connectionClosedHandler = null; private ConnectionClosedHandler userProvidedConnectionClosedHandler = null; private InternalStateChangedHandler internalStateChangedHandler = null; private StateChangedHandler stateChangedHandler = null; /// /// Initializes a new instance of the class. /// public IedConnection() { connection = IedConnection_create(); } /// /// Initializes a new instance of the class. /// /// TLS configuration to use public IedConnection(TLSConfiguration tlsConfig) { connection = IedConnection_createWithTlsSupport(tlsConfig.GetNativeInstance()); } /// /// Releases all resource used by the object. /// /// Call when you are finished using the . The /// method leaves the in an unusable state. After /// calling , you must release all references to the /// so the garbage collector can reclaim the memory that the was occupying. public void Dispose() { if (connection != IntPtr.Zero) { IedConnection_destroy(connection); connection = IntPtr.Zero; } } ~IedConnection () { Dispose(); } private IsoConnectionParameters isoConnectionParameters = null; /// /// Gets the connection parameters /// /// The connection parameters public IsoConnectionParameters GetConnectionParameters() { if (isoConnectionParameters == null) { IntPtr mmsConnection = IedConnection_getMmsConnection(connection); IntPtr parameters = MmsConnection_getIsoConnectionParameters(mmsConnection); isoConnectionParameters = new IsoConnectionParameters(parameters); } return isoConnectionParameters; } private void FreeHGlobaleDeleteFunction(IntPtr pointer) { Marshal.FreeHGlobal(pointer); } private UInt32 connectTimeout = 10000; /// /// Gets or sets the timeout used for connection attempts. /// /// The connect timeout in milliseconds public UInt32 ConnectTimeout { get { return connectTimeout; } set { connectTimeout = value; } } /// /// Gets or sets the request timeout for this connection /// /// The request timeout in milliseconds public UInt32 RequestTimeout { get { return IedConnection_getRequestTimeout(connection); } set { IedConnection_setRequestTimeout(connection, value); } } /// /// Set the maximum number outstanding calls allowed for this connection /// /// the maximum outstanding calls allowed by the caller (client) /// the maximum outstanding calls allowed by the called endpoint (server) public void SetMaxOutstandingCalls(int calling, int called) { IedConnection_setMaxOutstandingCalls(connection, calling, called); } /// /// Gets or sets the maximum size if a PDU (has to be set before calling connect!). /// /// The maximum allowed size of an MMS PDU. public int MaxPduSize { get { return GetMmsConnection().GetLocalDetail(); } set { GetMmsConnection().SetLocalDetail(value); } } /// /// Set the time quality for all timestamps generated by this IedConnection instance /// /// set or unset leap seconds known flag /// set or unset clock failure flag /// set or unset clock not synchronized flag /// set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp) public void SetTimeQuality(bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision) { IedConnection_setTimeQuality(connection, leapSecondKnown, clockFailure, clockNotSynchronized, subsecondPrecision); } /// /// Gets the underlying MmsConnection instance. /// /// The mms connection. public MmsConnection GetMmsConnection() { IntPtr mmsConnectionPtr = IedConnection_getMmsConnection(connection); return new MmsConnection(mmsConnectionPtr); } /// Establish an MMS connection to a server /// This exception is thrown if there is a connection or service error public void Connect(string hostname, int tcpPort) { int error; IedConnection_setConnectTimeout(connection, connectTimeout); IedConnection_connect(connection, out error, hostname, tcpPort); if (error != 0) throw new IedConnectionException("Connect to " + hostname + ":" + tcpPort + " failed", error); } public void ConnectAsync(string hostname, int tcpPort) { int error; IedConnection_setConnectTimeout(connection, connectTimeout); IedConnection_connectAsync(connection, out error, hostname, tcpPort); if (error != 0) throw new IedConnectionException("Connect to " + hostname + ":" + tcpPort + " failed", error); } /// /// Gets the current state of the connection /// /// The current connection state public IedConnectionState GetState() { return (IedConnectionState)IedConnection_getState(connection); } /// Establish an MMS connection to a server. /// This exception is thrown if there is a connection or service error. public void Connect(string hostname) { Connect(hostname, -1); } /// /// Set the local IP address and port to be used by the client /// /// the local IP address or hostname /// the local TCP port to use. When 0 the OS will chose the TCP port to use. public void SetLocalAddress(string localIpAddress, int localPort) { IedConnection_setLocalAddress(connection, localIpAddress, localPort); } /// /// Set the local IP address to be used by the client /// /// the local IP address or hostname public void SetLocalAddress(string localIpAddress) { IedConnection_setLocalAddress(connection, localIpAddress, 0); } /// This exception is thrown if there is a connection or service error public ControlObject CreateControlObject(string objectReference) { ControlObject controlObject = new ControlObject(objectReference, connection, this); return controlObject; } /// /// Creates a new SampledValuesControlBlock instance. /// /// > /// This function will also read the SVCB values from the server. /// /// The new SVCB instance /// The object reference of the SVCB public SampledValuesControlBlock GetSvControlBlock(string svcbObjectReference) { return new SampledValuesControlBlock(connection, svcbObjectReference); } /// /// Creates a new SampledValuesControlBlock instance. /// /// The new GoCB instance /// The object reference of the GoCB (e.g. "simpleIOGenericIO/LLN0.gcbAnalogValues") public GooseControlBlock GetGooseControlBlock(string gocbObjectReference) { return new GooseControlBlock(gocbObjectReference, connection); } /// /// Updates the device model by quering the server. /// public void UpdateDeviceModel() { int error; IedConnection_getDeviceModelFromServer(connection, out error); if (error != 0) throw new IedConnectionException("UpdateDeviceModel failed", error); } /// /// Gets the server directory (Logical devices or file objects) /// /// List of logical devices or files /// If set to true the file directory is returned, otherwise the LD names /// This exception is thrown if there is a connection or service error public List GetServerDirectory(bool fileDirectory = false) { int error; IntPtr linkedList = IedConnection_getServerDirectory(connection, out error, fileDirectory); if (error != 0) throw new IedConnectionException("GetDeviceDirectory failed", error); List newList = new List(); if (fileDirectory == false) { IntPtr element = LinkedList_getNext(linkedList); while (element != IntPtr.Zero) { string ld = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(ld); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); } else { IntPtr element = LinkedList_getNext(linkedList); while (element != IntPtr.Zero) { IntPtr elementData = LinkedList_getData(element); FileDirectoryEntry entry = new FileDirectoryEntry(elementData); newList.Add(entry.GetFileName()); FileDirectoryEntry_destroy(elementData); element = LinkedList_getNext(element); } LinkedList_destroyStatic(linkedList); } return newList; } /// This exception is thrown if there is a connection or service error public List GetLogicalDeviceDirectory(string logicalDeviceName) { int error; IntPtr linkedList = IedConnection_getLogicalDeviceDirectory(connection, out error, logicalDeviceName); if (error != 0) throw new IedConnectionException("GetLogicalDeviceDirectory failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string ln = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(ln); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } /// Get the directory of a logical node (LN) /// This function returns the directory contents of a LN. Depending on the provided ACSI class /// The function returns either data object references, or references of other objects like data sets, /// report control blocks, ... /// The object reference of a DO, SDO, or DA. /// the ACSI class of the requested directory elements. /// This exception is thrown if there is a connection or service error public List GetLogicalNodeDirectory(string logicalNodeName, ACSIClass acsiClass) { int error; IntPtr linkedList = IedConnection_getLogicalNodeDirectory(connection, out error, logicalNodeName, (int)acsiClass); if (error != 0) throw new IedConnectionException("GetLogicalNodeDirectory failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } /// Get a list of attributes (with functional constraints) of a DO, SDO, or DA /// The object reference of a DO, SDO, or DA. /// This exception is thrown if there is a connection or service error public List GetDataDirectory(string dataReference) { int error; IntPtr linkedList = IedConnection_getDataDirectory(connection, out error, dataReference); if (error != 0) throw new IedConnectionException("GetDataDirectory failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } /// Get the list of attributes with the specified FC of a DO, SDO, or DA /// The object reference of a DO, SDO, or DA. /// Functional constraint /// This exception is thrown if there is a connection or service error public List GetDataDirectory(string dataReference, FunctionalConstraint fc) { int error; IntPtr linkedList = IedConnection_getDataDirectoryByFC(connection, out error, dataReference, fc); if (error != 0) throw new IedConnectionException("GetDataDirectory failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } /// Get a list of attributes (with functional constraints) of a DO, SDO, or DA /// This function is similar to the GetDataDirectory except that the returned element names /// have the functional contraint (FC) appended. /// The object reference of a DO, SDO, or DA. /// This exception is thrown if there is a connection or service error public List GetDataDirectoryFC(string dataReference) { int error; IntPtr linkedList = IedConnection_getDataDirectoryFC(connection, out error, dataReference); if (error != 0) throw new IedConnectionException("GetDataDirectoryFC failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } private static List WrapNativeLogQueryResult(IntPtr linkedList) { if (linkedList == IntPtr.Zero) return null; List journalEntries = new List(); IntPtr element = LinkedList_getNext(linkedList); while (element != IntPtr.Zero) { MmsJournalEntry journalEntry = new MmsJournalEntry(LinkedList_getData(element)); journalEntries.Add(journalEntry); element = LinkedList_getNext(element); } LinkedList_destroyStatic(linkedList); return journalEntries; } /// /// Queries all log entries after the entry with the given entryID and timestamp /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// EntryID of the last received MmsJournalEntry /// Timestamp of the last received MmsJournalEntry /// Indicates that more log entries are available /// This exception is thrown if there is a connection or service error public List QueryLogAfter(string logRef, byte[] entryID, ulong timestamp, out bool moreFollows) { int error; bool moreFollowsVal; MmsValue entryIdValue = new MmsValue(entryID); IntPtr linkedList = IedConnection_queryLogAfter(connection, out error, logRef, entryIdValue.valueReference, timestamp, out moreFollowsVal); if (error != 0) throw new IedConnectionException("QueryLogAfter failed", error); moreFollows = moreFollowsVal; return WrapNativeLogQueryResult(linkedList); } /// /// Queries all log entries of the given time range /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// Start time of the time range /// End time of the time range /// Indicates that more log entries are available /// This exception is thrown if there is a connection or service error public List QueryLogByTime(string logRef, ulong startTime, ulong stopTime, out bool moreFollows) { int error; bool moreFollowsVal; IntPtr linkedList = IedConnection_queryLogByTime(connection, out error, logRef, startTime, stopTime, out moreFollowsVal); if (error != 0) throw new IedConnectionException("QueryLogByTime failed", error); moreFollows = moreFollowsVal; return WrapNativeLogQueryResult(linkedList); } /// /// Queries all log entries of the given time range /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// Start time of the time range /// End time of the time range /// Indicates that more log entries are available /// This exception is thrown if there is a connection or service error public List QueryLogByTime(string logRef, DateTime startTime, DateTime stopTime, out bool moreFollows) { ulong startTimeMs = LibIEC61850.DateTimeToMsTimestamp(startTime); ulong stopTimeMs = LibIEC61850.DateTimeToMsTimestamp(stopTime); return QueryLogByTime(logRef, startTimeMs, stopTimeMs, out moreFollows); } /// Read the variable specification (type description of a DA or FCDO /// The object reference of a DA or FCDO. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public MmsVariableSpecification GetVariableSpecification(string objectReference, FunctionalConstraint fc) { int error; IntPtr varSpecPtr = IedConnection_getVariableSpecification(connection, out error, objectReference, (int)fc); if (error != 0) throw new IedConnectionException("GetVariableSpecification failed", error); return new MmsVariableSpecification(varSpecPtr, true); } private IntPtr readObjectInternal(string objectReference, FunctionalConstraint fc) { int error; IntPtr mmsValue = IedConnection_readObject(connection, out error, objectReference, (int)fc); if (error != 0) throw new IedConnectionException("Reading value failed", error); if (mmsValue == IntPtr.Zero) throw new IedConnectionException("Variable not found on server", error); return mmsValue; } /// Read the value of a data attribute (DA) or functional constraint data object (FCDO). /// The object reference of a DA or FCDO. /// The functional constraint (FC) of the object /// the received value as an MmsValue instance /// This exception is thrown if there is a connection or service error public MmsValue ReadValue(String objectReference, FunctionalConstraint fc) { var value = readObjectInternal(objectReference, fc); return new MmsValue(value, true); } private IntPtr readObjectInternalAndCheckDataAccessError(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternal(objectReference, fc); if (MmsValue_getType(mmsValue) == (int)MmsType.MMS_DATA_ACCESS_ERROR) { int dataAccessError = MmsValue_getDataAccessError(mmsValue); MmsValue_delete(mmsValue); throw new IedConnectionException("Data access error", dataAccessError); } return mmsValue; } /// Read the value of a basic data attribute (BDA) of type boolean. /// The object reference of a BDA. /// The functional constraint (FC) of the object /// the received boolean value /// This exception is thrown if there is a connection or service error public bool ReadBooleanValue(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternalAndCheckDataAccessError(objectReference, fc); if (MmsValue_getType(mmsValue) != (int)MmsType.MMS_BOOLEAN) { MmsValue_delete(mmsValue); throw new IedConnectionException("Result is not of type boolean (MMS_BOOLEAN)", 0); } bool value = MmsValue_getBoolean(mmsValue); MmsValue_delete(mmsValue); return value; } /// Read the value of a basic data attribute (BDA) of type float. /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public float ReadFloatValue(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternalAndCheckDataAccessError(objectReference, fc); if (MmsValue_getType(mmsValue) != (int)MmsType.MMS_FLOAT) { MmsValue_delete(mmsValue); throw new IedConnectionException("Result is not of type float (MMS_FLOAT)", 0); } float value = MmsValue_toFloat(mmsValue); MmsValue_delete(mmsValue); return value; } /// Read the value of a basic data attribute (BDA) of type string (VisibleString or MmsString). /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public string ReadStringValue(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternalAndCheckDataAccessError(objectReference, fc); if (!((MmsValue_getType(mmsValue) == (int)MmsType.MMS_VISIBLE_STRING) || (MmsValue_getType(mmsValue) == (int)MmsType.MMS_STRING))) { MmsValue_delete(mmsValue); throw new IedConnectionException("Result is not of type string", 0); } IntPtr ptr = MmsValue_toString(mmsValue); string returnString = Marshal.PtrToStringAnsi(ptr); MmsValue_delete(mmsValue); return returnString; } /// Read the value of a basic data attribute (BDA) of type quality. /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public Quality ReadQualityValue(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternalAndCheckDataAccessError(objectReference, fc); if (MmsValue_getType(mmsValue) == (int)MmsType.MMS_BIT_STRING) { int bitStringValue = (int)MmsValue_getBitStringAsInteger(mmsValue); MmsValue_delete(mmsValue); return new Quality(bitStringValue); } else { MmsValue_delete(mmsValue); throw new IedConnectionException("Result is not of type bit string(Quality)", 0); } } /// Read the value of a basic data attribute (BDA) of type bit string. /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public int ReadBitStringValue(string objectReference, FunctionalConstraint fc) { IntPtr mmsValue = readObjectInternalAndCheckDataAccessError(objectReference, fc); if (MmsValue_getType(mmsValue) == (int)MmsType.MMS_BIT_STRING) { int bitStringValue = (int)MmsValue_getBitStringAsInteger(mmsValue); MmsValue_delete(mmsValue); return bitStringValue; } else { MmsValue_delete(mmsValue); throw new IedConnectionException("Result is not of type bit string", 0); } } /// Read the value of a basic data attribute (BDA) of type timestamp (MMS_UTC_TIME). /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public Timestamp ReadTimestampValue(string objectReference, FunctionalConstraint fc) { var mmsValuePtr = readObjectInternalAndCheckDataAccessError(objectReference, fc); var mmsValue = new MmsValue(mmsValuePtr, true); if (mmsValue.GetType() == MmsType.MMS_UTC_TIME) return new Timestamp(mmsValue); else throw new IedConnectionException("Result is not of type timestamp (MMS_UTC_TIME)", 0); } /// Read the value of a basic data attribute (BDA) of type integer (MMS_INTEGER). /// This function should also be used if enumerations are beeing read. Because /// enumerations are mapped to integer types for the MMS mapping /// The object reference of a BDA. /// The functional constraint (FC) of the object /// This exception is thrown if there is a connection or service error public Int64 ReadIntegerValue(string objectReference, FunctionalConstraint fc) { var mmsValuePtr = readObjectInternalAndCheckDataAccessError(objectReference, fc); var mmsValue = new MmsValue(mmsValuePtr, true); if (mmsValue.GetType() == MmsType.MMS_INTEGER) return mmsValue.ToInt64(); else throw new IedConnectionException("Result is not of type integer (MMS_INTEGER)", 0); } /// Write the value of a data attribute (DA) or functional constraint data object (FCDO). /// This function can be used to write simple or complex variables (setpoints, parameters, descriptive values...) /// of the server. /// The object reference of a BDA. /// The functional constraint (FC) of the object /// MmsValue object representing asimple or complex variable data /// This exception is thrown if there is a connection or service error public void WriteValue(string objectReference, FunctionalConstraint fc, MmsValue value) { int error; IedConnection_writeObject(connection, out error, objectReference, (int)fc, value.valueReference); if (error != 0) throw new IedConnectionException("Write value failed", error); } /// Delete file /// The name of the file. /// This exception is thrown if there is a connection or service error public void DeleteFile(string fileName) { int error; IedConnection_deleteFile(connection, out error, fileName); if (error != 0) throw new IedConnectionException("Deleting file " + fileName + " failed", error); } private IedConnection_GenericServiceHandler internalGenericServiceHandler = null; private void nativeGenericServiceHandler(UInt32 invokeId, IntPtr parameter, int err) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; GenericServiceHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; handler(invokeId, handlerParameter, clientError); } /// Delete file- asynchronous version /// The name of the file. /// This exception is thrown if there is a connection or service error public UInt32 DeleteFileAsync(string filename, GenericServiceHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGenericServiceHandler == null) internalGenericServiceHandler = new IedConnection_GenericServiceHandler(nativeGenericServiceHandler); UInt32 invokeId = IedConnection_deleteFileAsync(connection, out error, filename, internalGenericServiceHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get file failed", error); } return invokeId; } /// Read the content of a file directory. /// The name of the directory. /// This exception is thrown if there is a connection or service error public List GetFileDirectory(string directoryName) { int error; IntPtr fileEntryList = IedConnection_getFileDirectory(connection, out error, directoryName); if (error != 0) throw new IedConnectionException("Reading file directory failed", error); List fileDirectory = new List(); IntPtr element = LinkedList_getNext(fileEntryList); while (element != IntPtr.Zero) { IntPtr elementData = LinkedList_getData(element); FileDirectoryEntry entry = new FileDirectoryEntry(elementData); fileDirectory.Add(entry); FileDirectoryEntry_destroy(elementData); element = LinkedList_getNext(element); } LinkedList_destroyStatic(fileEntryList); return fileDirectory; } /// Read the content of a file directory. - single request version /// The name of the directory. /// the filename that defines the continuation point, or null for the first request /// true, when more files are available, false otherwise /// list of file directory entries /// This exception is thrown if there is a connection or service error public List GetFileDirectoryEx(string directoryName, string continueAfter, out bool moreFollows) { int error; IntPtr fileEntryList = IedConnection_getFileDirectoryEx(connection, out error, directoryName, continueAfter, out moreFollows); if (error != 0) throw new IedConnectionException("Reading file directory failed", error); List fileDirectory = new List(); IntPtr element = LinkedList_getNext(fileEntryList); while (element != IntPtr.Zero) { IntPtr elementData = LinkedList_getData(element); FileDirectoryEntry entry = new FileDirectoryEntry(elementData); fileDirectory.Add(entry); FileDirectoryEntry_destroy(elementData); element = LinkedList_getNext(element); } LinkedList_destroyStatic(fileEntryList); return fileDirectory; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool InternalIedClientGetFileHandler(IntPtr parameter,IntPtr buffer,UInt32 bytesRead); private bool iedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead) { GCHandle handle = GCHandle.FromIntPtr(parameter); GetFileCallback getFileCallback = (GetFileCallback)handle.Target; byte[] bytes = new byte[bytesRead]; Marshal.Copy(buffer, bytes, 0, (int)bytesRead); return getFileCallback.handler(getFileCallback.parameter, bytes); } [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getFile(IntPtr self, out int error, string fileName, InternalIedClientGetFileHandler handler, IntPtr handlerParameter); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setFile(IntPtr self, out int error, string sourceFilename, string destinationFilename); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setFilestoreBasepath(IntPtr self, string fileName); public delegate bool GetFileHandler(object parameter,byte[] data); private class GetFileCallback { public GetFileCallback(GetFileHandler handler, object parameter) { this.handler = handler; this.parameter = parameter; } public GetFileHandler handler; public object parameter; } /// /// Download a file from the server. /// /// /// File name of the file (full path) /// /// /// Callback handler that is invoked for each chunk of the file received /// /// /// User provided parameter that is passed to the callback handler /// /// This exception is thrown if there is a connection or service error public void GetFile(string fileName, GetFileHandler handler, object parameter) { int error; GetFileCallback getFileCallback = new GetFileCallback(handler, parameter); GCHandle handle = GCHandle.Alloc(getFileCallback); IedConnection_getFile(connection, out error, fileName, new InternalIedClientGetFileHandler(iedClientGetFileHandler), GCHandle.ToIntPtr(handle)); if (error != 0) throw new IedConnectionException("Error reading file", error); handle.Free(); } /// /// Set the virtual filestore basepath for the setFile service /// /// The new virtual filestore basepath public void SetFilestoreBasepath(string basepath) { IedConnection_setFilestoreBasepath(connection, basepath); } /// /// Upload a file to the server. /// /// The filename of the local (client side) file /// The filename of the remote (service side) file public void SetFile(string sourceFilename, string destinationFilename) { int error; IedConnection_setFile(connection, out error, sourceFilename, destinationFilename); if (error != 0) throw new IedConnectionException("Error uploading file", error); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool IedConnection_GetFileAsyncHandler(UInt32 invokeId, IntPtr parameter, int err, UInt32 originalInvokeId, IntPtr buffer, UInt32 bytesRead, [MarshalAs(UnmanagedType.I1)] bool moreFollows); /// /// Callback handler for the asynchronous get file service. Will be invoked for each chunk of received data /// /// The invoke ID of the reqeust triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout /// the invokeId of the first (file open) request /// the file data received with the last response, or null if no file data available /// indicates that more file data follows /// true, continue the file download when moreFollows is true, false, stop file download public delegate bool GetFileAsyncHandler(UInt32 invokeId, object parameter, IedClientError err, UInt32 originalInvokeId, byte[] buffer, bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getFileAsync(IntPtr self, out int error, string fileName, IedConnection_GetFileAsyncHandler handler, IntPtr parameter); private IedConnection_GetFileAsyncHandler internalGetFileAsyncHandler = null; private bool nativeGetFileAsyncHandler(UInt32 invokeId, IntPtr parameter, int err, UInt32 originalInvokeId, IntPtr buffer, UInt32 bytesRead, bool moreFollows) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; GetFileAsyncHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; IedClientError clientError = (IedClientError)err; byte[] bytes = null; if (clientError == IedClientError.IED_ERROR_OK) { bytes = new byte[bytesRead]; Marshal.Copy(buffer, bytes, 0, (int)bytesRead); } bool retVal = handler(invokeId, handlerParameter, clientError, originalInvokeId, bytes, moreFollows); if (clientError != IedClientError.IED_ERROR_OK) { handle.Free(); } else { if (moreFollows == false) { handle.Free(); } else { if (retVal == false) { handle.Free(); } } } return retVal; } /// /// Download a file from the server. /// /// /// File name of the file (full path) /// /// /// Callback handler that is invoked for each chunk of the file received /// /// /// User provided parameter that is passed to the callback handler /// /// invoke ID of the request /// This exception is thrown if there is a connection or service error public UInt32 GetFileAsync(string fileName, GetFileAsyncHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetFileAsyncHandler == null) internalGetFileAsyncHandler = new IedConnection_GetFileAsyncHandler(nativeGetFileAsyncHandler); UInt32 invokeId = IedConnection_getFileAsync(connection, out error, fileName, internalGetFileAsyncHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get file failed", error); } return invokeId; } /// /// Abort (close) the connection. /// /// This function will send an abort request to the server. This will immediately interrupt the /// connection. /// This exception is thrown if there is a connection or service error public void Abort() { int error; IedConnection_abort(connection, out error); if (error != 0) throw new IedConnectionException("Abort failed", error); } /// /// Abort (close) the connection - asynchronous version /// /// This function will send an abort request to the server. This will immediately interrupt the /// connection. /// This exception is thrown if there is a connection or service error public void AbortAsync() { int error; IedConnection_abortAsync(connection, out error); if (error != 0) throw new IedConnectionException("Abort failed", error); } /// /// Release (close) the connection. /// /// This function will send an release request to the server. The function will block until the /// connection is released or an error occured. /// This exception is thrown if there is a connection or service error public void Release() { int error; IedConnection_release(connection, out error); if (error != 0) throw new IedConnectionException("Release failed", error); } /// /// Release (close) the connection - asynchronous version /// /// This function will send an release request to the server. The function will block until the /// connection is released or an error occured. /// This exception is thrown if there is a connection or service error public void ReleaseAsync() { int error; IedConnection_releaseAsync(connection, out error); if (error != 0) throw new IedConnectionException("Release failed", error); } /// /// Immediately close the connection. /// /// This function will close the connnection to the server by closing the TCP connection. /// The client will not send an abort or release request as required by the specification! /// This exception is thrown if there is a connection or service error public void Close() { IedConnection_close(connection); } private void MyConnectionClosedHandler(IntPtr parameter, IntPtr self) { if (userProvidedConnectionClosedHandler != null) userProvidedConnectionClosedHandler(this); } /// /// Install a callback handler that will be invoked if the connection is closed. /// /// The handler is called when the connection is closed no matter if the connection was closed /// by the client or by the server. Any new call to this function will replace the callback handler installed /// by a prior function call. /// The user provided callback handler /// This exception is thrown if there is a connection or service error [Obsolete("ConnectionClosedHandler is deprecated, please use StateChangedHandler instead")] public void InstallConnectionClosedHandler(ConnectionClosedHandler handler) { if (connectionClosedHandler == null) { connectionClosedHandler = new InternalConnectionClosedHandler(MyConnectionClosedHandler); IedConnection_installConnectionClosedHandler(connection, connectionClosedHandler, connection); } userProvidedConnectionClosedHandler = handler; } private void MyStateChangedHandler(IntPtr parameter, IntPtr IedConnection, int newState) { if (stateChangedHandler != null) stateChangedHandler(this, (IedConnectionState)newState); } /// /// Sets the handler for StateChanged events /// /// The state changed event handler public void InstallStateChangedHandler(StateChangedHandler handler) { stateChangedHandler = handler; if (internalStateChangedHandler == null) { internalStateChangedHandler = new InternalStateChangedHandler(MyStateChangedHandler); IedConnection_installStateChangedHandler(connection, internalStateChangedHandler, IntPtr.Zero); } } /// /// Sets the handler for StateChanged events /// /// The state changed event handler public StateChangedHandler StateChanged { set { InstallStateChangedHandler(value); } } /// /// Read the values of a data set (GetDataSetValues service). /// /// This function will invoke a readDataSetValues service and return a new DataSet value containing the /// received values. /// The object reference of the data set /// This exception is thrown if there is a connection or service error public DataSet GetDataSetValues(string dataSetReference) { return ReadDataSetValues(dataSetReference, null); } /// /// Read the values of a data set (GetDataSetValues service). /// /// This function will invoke a readDataSetValues service and return a new DataSet value containing the /// received values. If an existing instance of DataSet is provided to the function the existing instance will be /// updated by the new values. /// The object reference of the data set /// The object reference of an existing data set instance or null /// a DataSet instance containing the received values /// This exception is thrown if there is a connection or service error public DataSet ReadDataSetValues(string dataSetReference, DataSet dataSet) { IntPtr nativeClientDataSet = IntPtr.Zero; if (dataSet != null) nativeClientDataSet = dataSet.getNativeInstance(); int error; nativeClientDataSet = IedConnection_readDataSetValues(connection, out error, dataSetReference, nativeClientDataSet); if (error != 0) throw new IedConnectionException("Reading data set failed", error); if (dataSet == null) dataSet = new DataSet(nativeClientDataSet); return dataSet; } /// /// Writes the values of a data set (SetDataSetValues service). /// /// The list of access results /// The object reference of the data set /// The new values for the data set members. The values have to be of the same number and type as the data set members public List WriteDataSetValues(string dataSetReference, List values) { int error; IntPtr accessResults = IntPtr.Zero; IntPtr valueList = LinkedList_create(); foreach (MmsValue mmsValue in values) { LinkedList_add(valueList, mmsValue.valueReference); } IedConnection_writeDataSetValues(connection, out error, dataSetReference, valueList, out accessResults); LinkedList_destroyStatic(valueList); /* handle access results */ List accessResultList = null; if (accessResults != IntPtr.Zero) { IntPtr element = LinkedList_getNext(accessResults); while (element != IntPtr.Zero) { IntPtr elementData = LinkedList_getData(element); MmsValue accessResultValue = new MmsValue(elementData, true); MmsDataAccessError dataAccessError = accessResultValue.GetDataAccessError(); if (accessResultList == null) accessResultList = new List(); accessResultList.Add(dataAccessError); element = LinkedList_getNext(element); } LinkedList_destroyStatic(accessResults); } if (error != 0) throw new IedConnectionException("Writing data set failed", error); return accessResultList; } /// /// Create a new data set. /// /// This function creates a new data set at the server. The data set consists of the members defined /// by the list of object references provided. /// The object reference of the data set /// A list of object references of the data set elements /// This exception is thrown if there is a connection or service error public void CreateDataSet(string dataSetReference, List dataSetElements) { IntPtr linkedList = LinkedList_create(); foreach (string dataSetElement in dataSetElements) { IntPtr handle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(dataSetElement); LinkedList_add(linkedList, handle); } int error; IedConnection_createDataSet(connection, out error, dataSetReference, linkedList); LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction)); if (error != 0) throw new IedConnectionException("Failed to create data set", error); } /// /// Create a new data set - asynchronous version. /// /// This function creates a new data set at the server. The data set consists of the members defined /// by the list of object references provided. /// The object reference of the data set /// A list of object references of the data set elements /// 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 CreateDataSetAsync(string dataSetReference, List dataSetElements, GenericServiceHandler handler, object parameter) { int error = 0; IntPtr linkedList = LinkedList_create(); foreach (string dataSetElement in dataSetElements) { IntPtr dataSetElementHandle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(dataSetElement); LinkedList_add(linkedList, dataSetElementHandle); } Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGenericServiceHandler == null) internalGenericServiceHandler = new IedConnection_GenericServiceHandler(nativeGenericServiceHandler); UInt32 invokeId = IedConnection_createDataSetAsync(connection, out error, dataSetReference, linkedList, internalGenericServiceHandler, GCHandle.ToIntPtr(handle)); LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction)); if (error != 0) { handle.Free(); throw new IedConnectionException("Create data set failed", error); } return invokeId; } /// /// Delete a data set. /// /// This function will delete a data set at the server. This function may fail if the data set is not /// deletable. /// The object reference of the data set /// true if data set has been deleted, false otherwise /// This exception is thrown if there is a connection or service error public bool DeleteDataSet(string dataSetReference) { int error; bool isDeleted = IedConnection_deleteDataSet(connection, out error, dataSetReference); if (error != 0) throw new IedConnectionException("Failed to delete data set", error); return isDeleted; } /// /// Delete a data set - asynchronous version. /// /// This function will delete a data set at the server. This function may fail if the data set is not /// deletable. /// The object reference of the data set /// 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 DeleteDataSetAsync(string dataSetReference, GenericServiceHandler handler, object parameter) { int error = 0; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGenericServiceHandler == null) internalGenericServiceHandler = new IedConnection_GenericServiceHandler(nativeGenericServiceHandler); UInt32 invokeId = IedConnection_deleteDataSetAsync(connection, out error, dataSetReference, internalGenericServiceHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Delete data set failed", error); } return invokeId; } /// /// Get the directory of the data set. /// /// This function returns a list of object references with appended functional constraints (FC) of the data set elemenents. /// The object reference of the data set /// the list of object references /// This exception is thrown if there is a connection or service error public List GetDataSetDirectory(string dataSetReference) { bool isDeletable; return GetDataSetDirectory(dataSetReference, out isDeletable); } /// /// Get the directory of the data set. /// /// This function returns a list of object references with appended functional constraints (FC) of the data set elemenents. /// The object reference of the data set /// Indication if this data set is permanent or deletable. /// the list of object references /// This exception is thrown if there is a connection or service error public List GetDataSetDirectory(string dataSetReference, out bool isDeletable) { int error; IntPtr linkedList = IedConnection_getDataSetDirectory(connection, out error, dataSetReference, out isDeletable); if (error != 0) throw new IedConnectionException("getDataSetDirectory failed", error); IntPtr element = LinkedList_getNext(linkedList); List newList = new List(); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(linkedList); return newList; } /// /// Get data set directory handler. /// /// The invoke ID of the reqeust triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout /// the list of data set entry references /// data set can be deleted by a client (dynamic data set) public delegate void GetDataSetDirectoryHandler(UInt32 invokeId, object parameter, IedClientError err, List dataSetDirectory, bool isDeletable); private IedConnection_GetDataSetDirectoryHandler internalGetDataSetDirectoryHandler = null; private void nativeGetDataSetDirectoryHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSetDirectory, bool isDeletable) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; GetDataSetDirectoryHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); List newList = null; if (dataSetDirectory != IntPtr.Zero) { newList = new List(); IntPtr element = LinkedList_getNext(dataSetDirectory); while (element != IntPtr.Zero) { string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); newList.Add(dataObject); element = LinkedList_getNext(element); } LinkedList_destroy(dataSetDirectory); } handler.Invoke(invokeId, handlerParameter, (IedClientError)err, newList, isDeletable); } /// /// Read the data set directory - asynchronous version /// /// The data set directory async. /// Data set reference. /// 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 GetDataSetDirectoryAsync(string dataSetReference, GetDataSetDirectoryHandler handler, object parameter) { int error = 0; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetDataSetDirectoryHandler == null) internalGetDataSetDirectoryHandler = new IedConnection_GetDataSetDirectoryHandler(nativeGetDataSetDirectoryHandler); UInt32 invokeId = IedConnection_getDataSetDirectoryAsync(connection, out error, dataSetReference, internalGetDataSetDirectoryHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get data set directory failed", error); } return invokeId; } /// /// Read object handler. /// /// The invoke ID of the reqeust triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout /// The read result value or null in case of an error public delegate void ReadValueHandler(UInt32 invokeId,object parameter,IedClientError err,MmsValue value); private IedConnection_ReadObjectHandler internalReadObjectHandler = null; private void nativeReadObjectHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr value) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; ReadValueHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; MmsValue mmsValue = null; if (value != IntPtr.Zero) { mmsValue = new MmsValue(value, true); } handler(invokeId, handlerParameter, clientError, mmsValue); } /// Asynchronously read the value of a data attribute (DA) or functional constraint data object (FCDO) - GetData service /// The object reference of a DA or FCDO. /// The functional constraint (FC) of the 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 ReadValueAsync(string objectReference, FunctionalConstraint fc, ReadValueHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalReadObjectHandler == null) internalReadObjectHandler = new IedConnection_ReadObjectHandler(nativeReadObjectHandler); UInt32 invokeId = IedConnection_readObjectAsync(connection, out error, objectReference, (int)fc, internalReadObjectHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Reading value failed", error); } return invokeId; } private IedConnection_GetVariableSpecificationHandler internalGetVariableSpecificationHandler = null; private void nativeGetVariableSpecifcationHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr spec) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; GetVariableSpecifcationHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; MmsVariableSpecification varSpec = null; if (spec != IntPtr.Zero) varSpec = new MmsVariableSpecification(spec, true); handler(invokeId, handlerParameter, clientError, varSpec); } public delegate void GetVariableSpecifcationHandler(UInt32 invokeId,object parameter,IedClientError err,MmsVariableSpecification spec); /// Read the variable specification (type description of a DA or FCDO /// The object reference of a DA or FCDO. /// The functional constraint (FC) of the 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 GetVariableSpecificationAsync(string objectReference, FunctionalConstraint fc, GetVariableSpecifcationHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetVariableSpecificationHandler == null) internalGetVariableSpecificationHandler = new IedConnection_GetVariableSpecificationHandler(nativeGetVariableSpecifcationHandler); UInt32 invokeId = IedConnection_getVariableSpecificationAsync(connection, out error, objectReference, (int)fc, internalGetVariableSpecificationHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Reading variable specification failed", error); } return invokeId; } private IedConnection_ReadDataSetHandler internalReadDataSetHandler = null; private void nativeReadDataSetHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr nativeDataSet) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; ReadDataSetHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; DataSet dataSet = callbackInfo.Item3; handle.Free(); IedClientError clientError = (IedClientError)err; if (nativeDataSet != IntPtr.Zero) { if (dataSet == null) dataSet = new DataSet(nativeDataSet); } handler(invokeId, handlerParameter, clientError, dataSet); } public delegate void ReadDataSetHandler(UInt32 invokeId,object parameter,IedClientError err,DataSet dataSet); /// /// Read the values of a data set (GetDataSetValues service) - asynchronous version /// /// This function will invoke a readDataSetValues service and in case of success returns a new DataSet value /// containing the received values by the callback function. If an existing instance of DataSet is provided to the /// function the existing instance will be updated by the new values. /// /// The object reference of the data set /// The object reference of an existing data set instance or null /// 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 ReadDataSetValuesAsync(string dataSetReference, DataSet dataSet, ReadDataSetHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter, dataSet); GCHandle handle = GCHandle.Alloc(callbackInfo); IntPtr dataSetPtr = IntPtr.Zero; if (dataSet != null) dataSetPtr = dataSet.getNativeInstance(); if (internalReadDataSetHandler == null) internalReadDataSetHandler = new IedConnection_ReadDataSetHandler(nativeReadDataSetHandler); UInt32 invokeId = IedConnection_readDataSetValuesAsync(connection, out error, dataSetReference, dataSetPtr, internalReadDataSetHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Reading data set failed", error); } return invokeId; } /// /// Write value handler. /// /// The invoke ID of the reqeust triggering this callback /// user provided callback parameter /// Error code of response or timeout error in case of a response timeout public delegate void WriteValueHandler(UInt32 invokeId,object parameter,IedClientError err); private IedConnection_WriteObjectHandler internalWriteObjectHandler = null; private void nativeWriteObjectHandler(UInt32 invokeId, IntPtr parameter, int err) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; WriteValueHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; handler(invokeId, handlerParameter, clientError); } public UInt32 WriteValueAsync(string objectReference, FunctionalConstraint fc, MmsValue value, WriteValueHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalWriteObjectHandler == null) internalWriteObjectHandler = new IedConnection_WriteObjectHandler(nativeWriteObjectHandler); UInt32 invokeId = IedConnection_writeObjectAsync(connection, out error, objectReference, (int)fc, value.valueReference, internalWriteObjectHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Writing value failed", error); } return invokeId; } public delegate void GetNameListHandler(UInt32 invokeId,object parameter,IedClientError err,List nameList,bool moreFollows); private IedConnection_GetNameListHandler internalGetNameListHandler = null; private void nativeGetNameListHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr nameList, [MarshalAs(UnmanagedType.I1)] bool moreFollows) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple> callbackInfo = handle.Target as Tuple>; GetNameListHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; List resultList = callbackInfo.Item3; handle.Free(); IedClientError clientError = (IedClientError)err; if (nameList != IntPtr.Zero) { IntPtr element = LinkedList_getNext(nameList); if (resultList == null) resultList = new List(); while (element != IntPtr.Zero) { string ld = Marshal.PtrToStringAnsi(LinkedList_getData(element)); resultList.Add(ld); element = LinkedList_getNext(element); } LinkedList_destroy(nameList); handler(invokeId, handlerParameter, clientError, resultList, moreFollows); } else { handler(invokeId, handlerParameter, clientError, null, moreFollows); } } /// /// Gets the server directory (Logical devices or file objects) /// /// list where to store the result or null to create a new list for the result /// continuation value (last received name) /// user provided callback function /// user provided callback parameter /// the invoke ID of the sent request /// This exception is thrown if there is a connection or service error public UInt32 GetServerDirectoryAsync(List result, string continueAfter, GetNameListHandler handler, object parameter) { int error; Tuple> callbackInfo = Tuple.Create(handler, parameter, result); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetNameListHandler == null) internalGetNameListHandler = new IedConnection_GetNameListHandler(nativeGetNameListHandler); UInt32 invokeId = IedConnection_getServerDirectoryAsync(connection, out error, continueAfter, IntPtr.Zero, internalGetNameListHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get server directory failed", error); } return invokeId; } public UInt32 GetLogicalDeviceVariablesAsync(string ldName, string continueAfter, GetNameListHandler handler, object parameter) { return GetLogicalDeviceVariablesAsync(null, ldName, continueAfter, handler, parameter); } public UInt32 GetLogicalDeviceVariablesAsync(List result, string ldName, string continueAfter, GetNameListHandler handler, object parameter) { int error; Tuple> callbackInfo = Tuple.Create(handler, parameter, result); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetNameListHandler == null) internalGetNameListHandler = new IedConnection_GetNameListHandler(nativeGetNameListHandler); UInt32 invokeId = IedConnection_getLogicalDeviceVariablesAsync(connection, out error, ldName, continueAfter, IntPtr.Zero, internalGetNameListHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get logical device variables failed", error); } return invokeId; } public UInt32 GetLogicalDeviceDataSetsAsync(string ldName, string continueAfter, GetNameListHandler handler, object parameter) { return GetLogicalDeviceDataSetsAsync(null, ldName, continueAfter, handler, parameter); } public UInt32 GetLogicalDeviceDataSetsAsync(List result, string ldName, string continueAfter, GetNameListHandler handler, object parameter) { int error; Tuple> callbackInfo = Tuple.Create(handler, parameter, result); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetNameListHandler == null) internalGetNameListHandler = new IedConnection_GetNameListHandler(nativeGetNameListHandler); UInt32 invokeId = IedConnection_getLogicalDeviceDataSetsAsync(connection, out error, ldName, continueAfter, IntPtr.Zero, internalGetNameListHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("Get logical device data sets failed", error); } return invokeId; } public delegate void QueryLogHandler(UInt32 invokeId,object parameter,IedClientError err,List journalEntries,bool moreFollows); private IedConnection_QueryLogHandler internalQueryLogHandler = null; private void nativeQueryLogHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr journalEntries, [MarshalAs(UnmanagedType.I1)] bool moreFollows) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; QueryLogHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; handle.Free(); IedClientError clientError = (IedClientError)err; var logResult = WrapNativeLogQueryResult(journalEntries); handler(invokeId, handlerParameter, clientError, logResult, moreFollows); } /// /// Queries all log entries of the given time range (asynchronous version) /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// Start time of the time range /// End time of the time range /// user provided callback function /// user provided callback parameter /// This exception is thrown if there is a connection or service error public UInt32 QueryLogByTimeAsync(string logRef, ulong startTime, ulong stopTime, QueryLogHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalQueryLogHandler == null) internalQueryLogHandler = new IedConnection_QueryLogHandler(nativeQueryLogHandler); UInt32 invokeId = IedConnection_queryLogByTimeAsync(connection, out error, logRef, startTime, stopTime, internalQueryLogHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("QueryLogByTime failed", error); } return invokeId; } /// /// Queries all log entries of the given time range (asynchronous version) /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// Start time of the time range /// End time of the time range /// user provided callback function /// user provided callback parameter /// This exception is thrown if there is a connection or service error public UInt32 QueryLogByTimeAsync(string logRef, DateTime startTime, DateTime stopTime, QueryLogHandler handler, object parameter) { ulong startTimeMs = LibIEC61850.DateTimeToMsTimestamp(startTime); ulong stopTimeMs = LibIEC61850.DateTimeToMsTimestamp(stopTime); return QueryLogByTimeAsync(logRef, startTimeMs, stopTimeMs, handler, parameter); } /// /// Queries all log entries after the entry with the given entryID and timestamp (asynchronous version) /// /// The list of log entries contained in the response /// The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog") /// EntryID of the last received MmsJournalEntry /// user provided callback function /// user provided callback parameter /// This exception is thrown if there is a connection or service error public UInt32 QueryLogAfterAsync(string logRef, byte[] entryID, ulong timestamp, QueryLogHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter); GCHandle handle = GCHandle.Alloc(callbackInfo); MmsValue entryIdValue = new MmsValue(entryID); if (internalQueryLogHandler == null) internalQueryLogHandler = new IedConnection_QueryLogHandler(nativeQueryLogHandler); UInt32 invokeId = IedConnection_queryLogAfterAsync(connection, out error, logRef, entryIdValue.valueReference, timestamp, internalQueryLogHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("QueryLogAfter failed", error); } return invokeId; } internal void UninstallReportHandler(string objectReference) { if (connection != IntPtr.Zero) { IedConnection_uninstallReportHandler(connection, objectReference); } } internal void InstallReportHandler(string objectReference, string reportId, InternalReportHandler internalHandler) { if (connection != IntPtr.Zero) { IedConnection_installReportHandler(connection, objectReference, reportId, internalHandler, IntPtr.Zero); } } internal void GetRCBValues(out int error, string objectReference, IntPtr updateRcb) { if (connection != IntPtr.Zero) { IedConnection_getRCBValues(connection, out error, objectReference, updateRcb); } else { error = 1; /* not connected */ } } internal void SetRCBValues(out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest) { if (connection != IntPtr.Zero) { IedConnection_setRCBValues(connection, out error, rcb, parametersMask, singleRequest); } else { error = 1; /* not connected */ } } private IedConnection_GetRCBValuesHandler internalGetRCBValuesHandler = null; private void nativeGetRCBValuesHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr rcbPtr) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; GetRCBValuesHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; ReportControlBlock rcb = callbackInfo.Item3; handle.Free(); IedClientError clientError = (IedClientError)err; handler(invokeId, handlerParameter, clientError, rcb); } internal UInt32 GetRCBValuesAsync(string objectReference, ReportControlBlock updateRcb, GetRCBValuesHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter, updateRcb); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalGetRCBValuesHandler == null) internalGetRCBValuesHandler = new IedConnection_GetRCBValuesHandler(nativeGetRCBValuesHandler); UInt32 invokeId = IedConnection_getRCBValuesAsync(connection, out error, objectReference, updateRcb.self, internalGetRCBValuesHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("GetRCBValues failed", error); } return invokeId; } private IedConnection_GenericServiceHandler internalSetRcbValueHandler = null; private void nativeSetRcbValuesHandler(UInt32 invokeId, IntPtr parameter, int err) { GCHandle handle = GCHandle.FromIntPtr(parameter); Tuple callbackInfo = handle.Target as Tuple; SetRCBValuesHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; ReportControlBlock rcb = callbackInfo.Item3; handle.Free(); IedClientError clientError = (IedClientError)err; handler(invokeId, handlerParameter, clientError, rcb); } internal UInt32 SetRCBValuesAsync(ReportControlBlock rcb, UInt32 parametersMask, bool singleRequest, SetRCBValuesHandler handler, object parameter) { int error; Tuple callbackInfo = Tuple.Create(handler, parameter, rcb); GCHandle handle = GCHandle.Alloc(callbackInfo); if (internalSetRcbValueHandler == null) internalSetRcbValueHandler = new IedConnection_GenericServiceHandler(nativeSetRcbValuesHandler); UInt32 invokeId = IedConnection_setRCBValuesAsync(connection, out error, rcb.self, parametersMask, singleRequest, internalSetRcbValueHandler, GCHandle.ToIntPtr(handle)); if (error != 0) { handle.Free(); throw new IedConnectionException("SetRCBValues failed", error); } return invokeId; } } public class IedConnectionException : Exception { private int errorCode; public IedConnectionException(string message, int errorCode) : base(message) { this.errorCode = errorCode; } public IedConnectionException(string message) : base(message) { this.errorCode = 0; } public int GetErrorCode() { return this.errorCode; } public IedClientError GetIedClientError() { return (IedClientError)this.errorCode; } } public class FileDirectoryEntry { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr FileDirectoryEntry_getFileName(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 FileDirectoryEntry_getFileSize(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern UInt64 FileDirectoryEntry_getLastModified(IntPtr self); private string fileName; private UInt32 fileSize; private UInt64 lastModified; internal FileDirectoryEntry(IntPtr nativeFileDirectoryEntry) { fileName = Marshal.PtrToStringAnsi(FileDirectoryEntry_getFileName(nativeFileDirectoryEntry)); fileSize = FileDirectoryEntry_getFileSize(nativeFileDirectoryEntry); lastModified = FileDirectoryEntry_getLastModified(nativeFileDirectoryEntry); } public string GetFileName() { return fileName; } public UInt32 GetFileSize() { return fileSize; } public UInt64 GetLastModified() { return lastModified; } } /// /// Connection state of an IedConnection instance /// public enum IedConnectionState { /// /// The connection is closed. Requests cannot be sent. /// IED_STATE_CLOSED = 0, /// /// The connection is connecting. Requests cannot be sent yet. /// IED_STATE_CONNECTING = 1, /// /// The connection is up. Requests can be sent. /// IED_STATE_CONNECTED = 2, /// /// The connection is closing. Requests connect be sent. /// IED_STATE_CLOSING = 3 } /// /// Error codes for client side functions /// public enum IedClientError { /* general errors */ /** No error occurred - service request has been successful */ IED_ERROR_OK = 0, /** The service request can not be executed because the client is not yet connected */ IED_ERROR_NOT_CONNECTED = 1, /** Connect service not execute because the client is already connected */ IED_ERROR_ALREADY_CONNECTED = 2, /** The service request can not be executed caused by a loss of connection */ IED_ERROR_CONNECTION_LOST = 3, /** The service or some given parameters are not supported by the client stack or by the server */ IED_ERROR_SERVICE_NOT_SUPPORTED = 4, /** Connection rejected by server */ IED_ERROR_CONNECTION_REJECTED = 5, /* client side errors */ /** API function has been called with an invalid argument */ IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT = 10, IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH = 11, /** The object provided object reference is invalid (there is a syntactical error). */ IED_ERROR_OBJECT_REFERENCE_INVALID = 12, /** Received object is of unexpected type */ IED_ERROR_UNEXPECTED_VALUE_RECEIVED = 13, /* service error - error reported by server */ /** The communication to the server failed with a timeout */ IED_ERROR_TIMEOUT = 20, /** The server rejected the access to the requested object/service due to access control */ IED_ERROR_ACCESS_DENIED = 21, /** The server reported that the requested object does not exist */ IED_ERROR_OBJECT_DOES_NOT_EXIST = 22, /** The server reported that the requested object already exists */ IED_ERROR_OBJECT_EXISTS = 23, /** The server does not support the requested access method */ IED_ERROR_OBJECT_ACCESS_UNSUPPORTED = 24, /** The server expected an object of another type */ IED_ERROR_TYPE_INCONSISTENT = 25, /** The object or service is temporarily unavailable */ IED_ERROR_TEMPORARILY_UNAVAILABLE = 26, /** The specified object is not defined in the server (returned by server) */ IED_ERROR_OBJECT_UNDEFINED = 27, /** The specified address is invalid (returned by server) */ IED_ERROR_INVALID_ADDRESS = 28, /** Service failed due to a hardware fault (returned by server) */ IED_ERROR_HARDWARE_FAULT = 29, /** The requested data type is not supported by the server (returned by server) */ IED_ERROR_TYPE_UNSUPPORTED = 30, /** The provided attributes are inconsistent (returned by server) */ IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 31, /** The provided object value is invalid (returned by server) */ IED_ERROR_OBJECT_VALUE_INVALID = 32, /** The object is invalidated (returned by server) */ IED_ERROR_OBJECT_INVALIDATED = 33, /** Received an invalid response message from the server */ IED_ERROR_MALFORMED_MESSAGE = 34, /** Service not implemented */ IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98, /* unknown error */ IED_ERROR_UNKNOWN = 99 } } }