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);