diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 10a6c8e..ef82f76 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -117,6 +117,127 @@ namespace IEC61850 } + + 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; + } + + 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; + } + + + public byte[] GetEntryID() + { + IntPtr mmsValuePtr = MmsJournalEntry_getEntryID (self); + + MmsValue mmsValue = new MmsValue (mmsValuePtr); + + byte[] octetString = mmsValue.getOctetString (); + + return octetString; + } + + public ulong GetOccurenceTime() + { + IntPtr mmsValuePtr = MmsJournalEntry_getOccurenceTime (self); + + MmsValue mmsValue = new MmsValue (mmsValuePtr); + + return mmsValue.GetBinaryTimeAsUtcMs (); + } + + public void Dispose() + { + if (self != IntPtr.Zero) { + MmsJournalEntry_destroy (self); + self = IntPtr.Zero; + } + } + + ~MmsJournalEntry () + { + Dispose (); + } + } + /// /// This class acts as the entry point for the IEC 61850 client API. It represents a single /// (MMS) connection to a server. @@ -230,7 +351,16 @@ namespace IEC61850 static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName); + 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, out bool moreFollows); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedConnection_queryLogByTime (IntPtr self, out int error, string logReference, + ulong startTime, ulong endTime, out bool moreFollows); + /******************** * FileDirectoryEntry @@ -573,6 +703,108 @@ namespace IEC61850 return newList; } + private static List WrapNativeLogQueryResult(IntPtr linkedList) + { + 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 + 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 + 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); + + } + + private static DateTime DateTimeFromMsTimestamp(ulong msTime) + { + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + ulong seconds = msTime / 1000; + ulong millies = msTime % 1000; + + dateTime.AddSeconds ((double) seconds); + dateTime.AddMilliseconds((double) millies); + + return dateTime; + } + + private static ulong DateTimeToMsTimestamp(DateTime dateTime) + { + return (ulong) (dateTime.ToUniversalTime ().Subtract (new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds); + } + + /// + /// 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 + public List QueryLogByTime(string logRef, DateTime startTime, DateTime stopTime, out bool moreFollows) + { + ulong startTimeMs = DateTimeToMsTimestamp (startTime); + ulong stopTimeMs = DateTimeToMsTimestamp (stopTime); + + return QueryLogByTime (logRef, startTimeMs, stopTimeMs, out moreFollows); + } /// Read the variable specification (type description of a DA or FDCO /// The object reference of a DA or FCDO. @@ -1349,6 +1581,8 @@ namespace IEC61850 RP = 15, /** Buffered report */ BR = 16, + /** Log control blocks */ + LG = 17, /** All FCs - wildcard value */ ALL = 99, diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index 8af017f..12b6b6d 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -161,6 +161,13 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void MmsValue_setBinaryTime (IntPtr self, UInt64 timestamp); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern ulong MmsValue_getBinaryTimeAsUtcMs (IntPtr self); + + internal IntPtr valueReference; /* reference to native MmsValue instance */ + + private bool responsableForDeletion; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */ + // TODO make internal public MmsValue (IntPtr value) { @@ -251,6 +258,16 @@ namespace IEC61850 return new MmsValue(newValue, true); } + public MmsValue(byte[] octetString) + { + if (octetString.Length > 255) + throw new MmsValueException ("octet string too long"); + + valueReference = MmsValue_newOctetString(octetString.Length, octetString.Length); + + this.setOctetString (octetString); + } + /// /// Create a new MmsValue instance of type MMS_BINARY_TIME /// @@ -271,8 +288,24 @@ namespace IEC61850 MmsValue_setBinaryTime (this.valueReference, timestamp); } - internal IntPtr valueReference; - private bool responsableForDeletion; + /// + /// Gets the binary time value as UTC time in ms. + /// + /// + /// Return the value as milliseconds since epoch (1.1.1970 UTC). + /// The value has to be of type MMS_UTC_TIME. + /// + /// + /// The UTC time in ms. + /// + /// This exception is thrown if the value has the wrong type. + public ulong GetBinaryTimeAsUtcMs () + { + if (GetType () == MmsType.MMS_BINARY_TIME) { + return MmsValue_getBinaryTimeAsUtcMs (valueReference); + } else + throw new MmsValueException ("Value is not a time type"); + } /// /// Gets the type of the value @@ -755,8 +788,33 @@ namespace IEC61850 return ToDouble ().ToString (); case MmsType.MMS_UTC_TIME: return GetUtcTimeAsDateTimeOffset ().ToString (); + case MmsType.MMS_BINARY_TIME: + return (MsTimeToDateTimeOffset (GetBinaryTimeAsUtcMs ()).ToString ()); + case MmsType.MMS_OCTET_STRING: + return BitConverter.ToString (getOctetString ()); case MmsType.MMS_BIT_STRING: return GetBitStringAsString(); + case MmsType.MMS_STRUCTURE: + { + string retString = "{"; + + bool first = true; + + foreach (MmsValue element in this) { + if (first) { + retString += element.ToString (); + + first = false; + } else { + retString += ", " + element.ToString (); + } + } + + retString += "}"; + + return retString; + } + default: return "unknown"; } diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index 8ea2e27..9583a43 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example3", "example3\exampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "report_new_dataset", "report_new_dataset\report_new_dataset.csproj", "{71485F99-2976-45E6-B73D-4946E594C15C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "log_client", "log_client\log_client.csproj", "{14C71267-2F38-460D-AA55-6803EE80AFB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.Build.0 = Release|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.Build.0 = Release|Any CPU {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -89,7 +95,6 @@ Global {EDC263E3-0419-4B23-91A4-250EF0A6DD62}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = IEC61850forCSharp\IEC61850forCSharp.csproj Policies = $0 $0.DotNetNamingPolicy = $1 $1.DirectoryNamespaceAssociation = None @@ -116,6 +121,7 @@ Global $0.StandardHeader = $7 $7.Text = $7.IncludeInNewFiles = True + StartupItem = IEC61850forCSharp\IEC61850forCSharp.csproj EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dotnet/example3/example3.csproj b/dotnet/example3/example3.csproj index 7a1f9d0..1274bc4 100644 --- a/dotnet/example3/example3.csproj +++ b/dotnet/example3/example3.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 10.0.0 + 8.0.30703 2.0 {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2} Exe diff --git a/dotnet/files/files.csproj b/dotnet/files/files.csproj index 348f804..e78b862 100644 --- a/dotnet/files/files.csproj +++ b/dotnet/files/files.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 10.0.0 + 8.0.30703 2.0 {77127456-19B9-4D1A-AEF9-40F8D1C5695E} Exe diff --git a/dotnet/log_client/Program.cs b/dotnet/log_client/Program.cs new file mode 100644 index 0000000..b857a0f --- /dev/null +++ b/dotnet/log_client/Program.cs @@ -0,0 +1,100 @@ +using System; +using IEC61850.Client; +using System.Collections.Generic; +using IEC61850.Common; + +namespace log_client +{ + class MainClass + { + private static void PrintJournalEntries(List journalEntries) { + foreach (MmsJournalEntry entry in journalEntries) { + Console.WriteLine("EntryID: " + BitConverter.ToString(entry.GetEntryID())); + Console.WriteLine(" occurence time: " + MmsValue.MsTimeToDateTimeOffset(entry.GetOccurenceTime()).ToString()); + foreach (MmsJournalVariable variable in entry.GetJournalVariables()) { + Console.WriteLine(" variable: " + variable.GetTag()); + Console.WriteLine(" value: " + variable.GetValue().ToString()); + } + } + + } + + public static void Main (string[] args) + { + IedConnection con = new IedConnection (); + + string hostname; + + if (args.Length > 0) + hostname = args[0]; + else + hostname = "localhost"; + + Console.WriteLine("Connect to " + hostname); + + + try + { + + con.Connect(hostname, 102); + + Console.WriteLine("Negotiated PDU size: " + con.GetMmsConnection().GetLocalDetail()); + + List serverDirectory = con.GetServerDirectory(false); + + foreach (string deviceName in serverDirectory) + { + Console.WriteLine("LD: " + deviceName); + List deviceDirectory = con.GetLogicalDeviceDirectory(deviceName); + + foreach (string lnName in deviceDirectory) { + Console.WriteLine(" LN: " + lnName); + + List lcbs = con.GetLogicalNodeDirectory(deviceName + "/" + lnName, IEC61850.Common.ACSIClass.ACSI_CLASS_LCB); + + foreach(string lcbName in lcbs) { + Console.WriteLine(" LCB: " + lcbName); + + MmsValue lcbValues = con.ReadValue(deviceName + "/" + lnName + "." + lcbName, FunctionalConstraint.LG); + + Console.WriteLine(" values: " + lcbValues.ToString()); + } + + List logs = con.GetLogicalNodeDirectory(deviceName + "/" + lnName, IEC61850.Common.ACSIClass.ACSI_CLASS_LOG); + + foreach(string logName in logs) { + Console.WriteLine(" LOG: " + logName); + } + } + } + + bool moreFollows; + + Console.WriteLine("\nQueryLogAfter:"); + + List journalEntries = con.QueryLogAfter("simpleIOGenericIO/LLN0$EventLog", + new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0, out moreFollows); + + PrintJournalEntries(journalEntries); + + Console.WriteLine("\nQueryLogByTime:"); + + journalEntries = con.QueryLogByTime("simpleIOGenericIO/LLN0$EventLog", + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + DateTime.UtcNow, + out moreFollows); + + PrintJournalEntries(journalEntries); + + con.Release(); + } + catch (IedConnectionException e) + { + Console.WriteLine(e.Message); + } + + // release all resources - do NOT use the object after this call!! + con.Dispose (); + } + } +} diff --git a/dotnet/log_client/Properties/AssemblyInfo.cs b/dotnet/log_client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8ed67c8 --- /dev/null +++ b/dotnet/log_client/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("log_client")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("mzillgit")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/dotnet/log_client/log_client.csproj b/dotnet/log_client/log_client.csproj new file mode 100644 index 0000000..2a8d37e --- /dev/null +++ b/dotnet/log_client/log_client.csproj @@ -0,0 +1,46 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {14C71267-2F38-460D-AA55-6803EE80AFB4} + Exe + log_client + log_client + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850forCSharp + + + \ No newline at end of file diff --git a/dotnet/report_new_dataset/report_new_dataset.csproj b/dotnet/report_new_dataset/report_new_dataset.csproj index e33af7a..b1eff69 100644 --- a/dotnet/report_new_dataset/report_new_dataset.csproj +++ b/dotnet/report_new_dataset/report_new_dataset.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 10.0.0 + 8.0.30703 2.0 {71485F99-2976-45E6-B73D-4946E594C15C} Exe diff --git a/examples/iec61850_client_example_log/client_example_log.c b/examples/iec61850_client_example_log/client_example_log.c index 83f8e95..b777484 100644 --- a/examples/iec61850_client_example_log/client_example_log.c +++ b/examples/iec61850_client_example_log/client_example_log.c @@ -92,6 +92,12 @@ int main(int argc, char** argv) { if (error == IED_ERROR_OK) { + char printBuf[1024]; + + MmsValue_printToBuffer(lcbValue, printBuf, 1024); + + printf("LCB values: %s\n", printBuf); + MmsValue* oldEntryTm = MmsValue_getElement(lcbValue, 3); MmsValue* oldEntry = MmsValue_getElement(lcbValue, 5);