- C# API: added client side log service support

This commit is contained in:
Michael Zillgith 2016-09-24 09:56:43 +02:00
parent 2eeac9adcb
commit fca675e2a1
10 changed files with 484 additions and 7 deletions

View file

@ -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;
}
}
/// <summary>
/// Represents an entry of a log
/// </summary>
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<MmsJournalVariable> variables = null;
internal MmsJournalEntry(IntPtr self)
{
this.self = self;
}
public List<MmsJournalVariable> GetJournalVariables()
{
if (variables == null) {
IntPtr linkedList = MmsJournalEntry_getJournalVariables (self);
IntPtr element = LinkedList_getNext (linkedList);
variables = new List<MmsJournalVariable> ();
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 ();
}
}
/// <summary>
/// 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<MmsJournalEntry> WrapNativeLogQueryResult(IntPtr linkedList)
{
List<MmsJournalEntry> journalEntries = new List<MmsJournalEntry> ();
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;
}
/// <summary>
/// Queries all log entries after the entry with the given entryID and timestamp
/// </summary>
/// <returns>The list of log entries contained in the response</returns>
/// <param name="logRef">The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog")</param>
/// <param name="entryID">EntryID of the last received MmsJournalEntry</param>
/// <param name="timestamp">Timestamp of the last received MmsJournalEntry</param>
/// <param name="moreFollows">Indicates that more log entries are available</param>
public List<MmsJournalEntry> 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);
}
/// <summary>
/// Queries all log entries of the given time range
/// </summary>
/// <returns>The list of log entries contained in the response</returns>
/// <param name="logRef">The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog")</param>
/// <param name="startTime">Start time of the time range</param>
/// <param name="stopTime">End time of the time range</param>
/// <param name="moreFollows">Indicates that more log entries are available</param>
public List<MmsJournalEntry> 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);
}
/// <summary>
/// Queries all log entries of the given time range
/// </summary>
/// <returns>The list of log entries contained in the response</returns>
/// <param name="logRef">The object reference of the log (e.g. "simpleIOGenericIO/LLN0$EventLog")</param>
/// <param name="startTime">Start time of the time range</param>
/// <param name="stopTime">End time of the time range</param>
/// <param name="moreFollows">Indicates that more log entries are available</param>
public List<MmsJournalEntry> 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);
}
/// <summary>Read the variable specification (type description of a DA or FDCO</summary>
/// <param name="objectReference">The object reference of a DA or FCDO.</param>
@ -1349,6 +1581,8 @@ namespace IEC61850
RP = 15,
/** Buffered report */
BR = 16,
/** Log control blocks */
LG = 17,
/** All FCs - wildcard value */
ALL = 99,

View file

@ -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);
}
/// <summary>
/// Create a new MmsValue instance of type MMS_BINARY_TIME
/// </summary>
@ -271,8 +288,24 @@ namespace IEC61850
MmsValue_setBinaryTime (this.valueReference, timestamp);
}
internal IntPtr valueReference;
private bool responsableForDeletion;
/// <summary>
/// Gets the binary time value as UTC time in ms.
/// </summary>
/// <description>
/// Return the value as milliseconds since epoch (1.1.1970 UTC).
/// The value has to be of type MMS_UTC_TIME.
/// </description>
/// <returns>
/// The UTC time in ms.
/// </returns>
/// <exception cref="MmsValueException">This exception is thrown if the value has the wrong type.</exception>
public ulong GetBinaryTimeAsUtcMs ()
{
if (GetType () == MmsType.MMS_BINARY_TIME) {
return MmsValue_getBinaryTimeAsUtcMs (valueReference);
} else
throw new MmsValueException ("Value is not a time type");
}
/// <summary>
/// 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";
}

View file

@ -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

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}</ProjectGuid>
<OutputType>Exe</OutputType>

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{77127456-19B9-4D1A-AEF9-40F8D1C5695E}</ProjectGuid>
<OutputType>Exe</OutputType>

View file

@ -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<MmsJournalEntry> 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<string> serverDirectory = con.GetServerDirectory(false);
foreach (string deviceName in serverDirectory)
{
Console.WriteLine("LD: " + deviceName);
List<string> deviceDirectory = con.GetLogicalDeviceDirectory(deviceName);
foreach (string lnName in deviceDirectory) {
Console.WriteLine(" LN: " + lnName);
List<string> 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<string> 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<MmsJournalEntry> 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 ();
}
}
}

View file

@ -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("")]

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{14C71267-2F38-460D-AA55-6803EE80AFB4}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>log_client</RootNamespace>
<AssemblyName>log_client</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\IEC61850forCSharp\IEC61850forCSharp.csproj">
<Project>{C35D624E-5506-4560-8074-1728F1FA1A4D}</Project>
<Name>IEC61850forCSharp</Name>
</ProjectReference>
</ItemGroup>
</Project>

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{71485F99-2976-45E6-B73D-4946E594C15C}</ProjectGuid>
<OutputType>Exe</OutputType>

View file

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