Samples Reappend
This commit is contained in:
100
Libraries/h-opc/ClientUtils.cs
Normal file
100
Libraries/h-opc/ClientUtils.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Linq;
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using Opc.Ua.Client;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// List of static utility methods
|
||||
/// </summary>
|
||||
internal static class ClientUtils
|
||||
{
|
||||
// TODO I didn't write these methods. I should rewrite it once I understand whtat it does, beacuse it looks crazy
|
||||
|
||||
public static EndpointDescription SelectEndpoint(Uri discoveryUrl, bool useSecurity)
|
||||
{
|
||||
var configuration = EndpointConfiguration.Create();
|
||||
configuration.OperationTimeout = 5000;
|
||||
EndpointDescription endpointDescription1 = null;
|
||||
using (var discoveryClient = DiscoveryClient.Create(discoveryUrl, configuration))
|
||||
{
|
||||
var endpoints = discoveryClient.GetEndpoints(null);
|
||||
foreach (var endpointDescription2 in endpoints.Where(endpointDescription2 => endpointDescription2.EndpointUrl.StartsWith(discoveryUrl.Scheme)))
|
||||
{
|
||||
if (useSecurity)
|
||||
{
|
||||
if (endpointDescription2.SecurityMode == MessageSecurityMode.None)
|
||||
continue;
|
||||
}
|
||||
else if (endpointDescription2.SecurityMode != MessageSecurityMode.None)
|
||||
continue;
|
||||
if (endpointDescription1 == null)
|
||||
endpointDescription1 = endpointDescription2;
|
||||
if (endpointDescription2.SecurityLevel > endpointDescription1.SecurityLevel)
|
||||
endpointDescription1 = endpointDescription2;
|
||||
}
|
||||
if (endpointDescription1 == null)
|
||||
{
|
||||
if (endpoints.Count > 0)
|
||||
endpointDescription1 = endpoints[0];
|
||||
}
|
||||
}
|
||||
var uri = Utils.ParseUri(endpointDescription1.EndpointUrl);
|
||||
if (uri != null && uri.Scheme == discoveryUrl.Scheme)
|
||||
endpointDescription1.EndpointUrl = new UriBuilder(uri)
|
||||
{
|
||||
Host = discoveryUrl.DnsSafeHost,
|
||||
Port = discoveryUrl.Port
|
||||
}.ToString();
|
||||
return endpointDescription1;
|
||||
}
|
||||
|
||||
public static ReferenceDescriptionCollection Browse(Session session, NodeId nodeId)
|
||||
{
|
||||
var desc = new BrowseDescription
|
||||
{
|
||||
NodeId = nodeId,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = 0U,
|
||||
ResultMask = 63U,
|
||||
};
|
||||
return Browse(session, desc, true);
|
||||
}
|
||||
|
||||
public static ReferenceDescriptionCollection Browse(Session session, BrowseDescription nodeToBrowse, bool throwOnError)
|
||||
{
|
||||
try
|
||||
{
|
||||
var descriptionCollection = new ReferenceDescriptionCollection();
|
||||
var nodesToBrowse = new BrowseDescriptionCollection { nodeToBrowse };
|
||||
BrowseResultCollection results;
|
||||
DiagnosticInfoCollection diagnosticInfos;
|
||||
session.Browse(null, null, 0U, nodesToBrowse, out results, out diagnosticInfos);
|
||||
ClientBase.ValidateResponse(results, nodesToBrowse);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
|
||||
while (!StatusCode.IsBad(results[0].StatusCode))
|
||||
{
|
||||
for (var index = 0; index < results[0].References.Count; ++index)
|
||||
descriptionCollection.Add(results[0].References[index]);
|
||||
if (results[0].References.Count == 0 || results[0].ContinuationPoint == null)
|
||||
return descriptionCollection;
|
||||
var continuationPoints = new ByteStringCollection();
|
||||
continuationPoints.Add(results[0].ContinuationPoint);
|
||||
session.BrowseNext(null, false, continuationPoints, out results, out diagnosticInfos);
|
||||
ClientBase.ValidateResponse(results, continuationPoints);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
|
||||
}
|
||||
throw new ServiceResultException(results[0].StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ServiceResultException(ex, 2147549184U);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
Libraries/h-opc/Common/ClientExtensions.cs
Normal file
34
Libraries/h-opc/Common/ClientExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Useful extension methods for OPC Clients
|
||||
/// </summary>
|
||||
public static class ClientExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a tag from the OPC. If for whatever reason the read fails (Tag doesn't exist, server not available) returns a default value
|
||||
/// </summary>
|
||||
/// <param name="client">the opc client to use for the read</param>
|
||||
/// <param name="tag">The fully qualified identifier of the tag</param>
|
||||
/// <param name="defaultValue">the default value to read if the read fails</param>
|
||||
/// <returns></returns>
|
||||
public static ReadEvent<T> ReadOrdefault<T>(this IClient<Node> client, string tag, T defaultValue = default(T))
|
||||
{
|
||||
try
|
||||
{
|
||||
return client.Read<T>(tag);
|
||||
}
|
||||
catch (OpcException)
|
||||
{
|
||||
var readEvent = new ReadEvent<T>();
|
||||
readEvent.Quality = Quality.Good;
|
||||
readEvent.Value = defaultValue;
|
||||
readEvent.SourceTimestamp = DateTime.Now;
|
||||
readEvent.ServerTimestamp = DateTime.Now;
|
||||
return readEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Libraries/h-opc/Common/IClient.cs
Normal file
103
Libraries/h-opc/Common/IClient.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Client interface to perform basic Opc tasks, like discovery, monitoring, reading/writing tags,
|
||||
/// </summary>
|
||||
public interface IClient<out TNode> : IDisposable
|
||||
where TNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
Task Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the OPC Client
|
||||
/// </summary>
|
||||
OpcStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the datatype of an OPC tag
|
||||
/// </summary>
|
||||
/// <param name="tag">Tag to get datatype of</param>
|
||||
/// <returns>System Type</returns>
|
||||
System.Type GetDataType(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
ReadEvent<T> Read<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item"></param>
|
||||
void Write<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Monitor the specified tag for changes
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of tag to monitor</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="callback">the callback to execute when the value is changed.
|
||||
/// The first parameter is the new value of the node, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an async method.")]
|
||||
void Monitor<T>(string tag, Action<ReadEvent<T>, Action> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
|
||||
TNode FindNode(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of the server
|
||||
/// </summary>
|
||||
TNode RootNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
|
||||
/// <returns>The list of sub-nodes</returns>
|
||||
IEnumerable<TNode> ExploreFolder(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronusly
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an async method.")]
|
||||
Task<ReadEvent<T>> ReadAsync<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
Task WriteAsync<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
Task<Node> FindNodeAsync(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
|
||||
Justification = "Task")]
|
||||
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
|
||||
}
|
||||
}
|
||||
48
Libraries/h-opc/Common/Node.cs
Normal file
48
Libraries/h-opc/Common/Node.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class representing a node on the OPC server
|
||||
/// </summary>
|
||||
public abstract class Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the displayed name of the node
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dot-separated fully qualified tag of the node
|
||||
/// </summary>
|
||||
public string Tag { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent node. If the node is root, returns null
|
||||
/// </summary>
|
||||
public Node Parent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new node
|
||||
/// </summary>
|
||||
/// <param name="name">the name of the node</param>
|
||||
/// <param name="parent">The parent node</param>
|
||||
protected Node(string name, Node parent = null)
|
||||
{
|
||||
Name = name;
|
||||
Parent = parent;
|
||||
if (parent != null && !string.IsNullOrEmpty(parent.Tag))
|
||||
Tag = parent.Tag + '.' + name;
|
||||
else
|
||||
Tag = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides ToString()
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Libraries/h-opc/Common/OpcException.cs
Normal file
70
Libraries/h-opc/Common/OpcException.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies an exception occurred during OPC Communication
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OpcException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an (optional) associated OPC UA StatusCode for the exception.
|
||||
/// </summary>
|
||||
public StatusCode? Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message, StatusCode status)
|
||||
: base(message)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
protected OpcException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the System.Runtime.Serialization.SerializationInfo with information about the exception.
|
||||
/// </summary>
|
||||
/// <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual information about the source or destination.</param>
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
Libraries/h-opc/Common/OpcStatus.cs
Normal file
18
Libraries/h-opc/Common/OpcStatus.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the status of an OPC connector
|
||||
/// </summary>
|
||||
public enum OpcStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The client is not connected
|
||||
/// </summary>
|
||||
NotConnected,
|
||||
|
||||
/// <summary>
|
||||
/// The client is connected
|
||||
/// </summary>
|
||||
Connected
|
||||
}
|
||||
}
|
||||
28
Libraries/h-opc/Common/Quality.cs
Normal file
28
Libraries/h-opc/Common/Quality.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the quality of the value captured
|
||||
/// </summary>
|
||||
public enum Quality
|
||||
{
|
||||
/// <summary>
|
||||
/// Quality: Unknown, the value of the quality could not be inferred by the library
|
||||
/// </summary>
|
||||
[Description("Unknown")]
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Quality: Good
|
||||
/// </summary>
|
||||
[Description("Good")]
|
||||
Good,
|
||||
|
||||
/// <summary>
|
||||
/// Quality: Bad
|
||||
/// </summary>
|
||||
[Description("Bad")]
|
||||
Bad
|
||||
}
|
||||
}
|
||||
37
Libraries/h-opc/Common/ReadEvent.cs
Normal file
37
Libraries/h-opc/Common/ReadEvent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class representing a monitor event on the OPC server
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ReadEvent<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value that was read from the server
|
||||
/// </summary>
|
||||
public T Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality of the signal from the server
|
||||
/// </summary>
|
||||
[DefaultValue(Common.Quality.Unknown)]
|
||||
public Quality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source timestamp on when the event ocurred
|
||||
/// </summary>
|
||||
public DateTime SourceTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server timestamp on when the event ocurred
|
||||
/// </summary>
|
||||
public DateTime ServerTimestamp { get; set; }
|
||||
}
|
||||
}
|
||||
24
Libraries/h-opc/NodeExtensions.cs
Normal file
24
Libraries/h-opc/NodeExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
using OpcF = Opc.Ua;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Class with extension methods for OPC UA
|
||||
/// </summary>
|
||||
public static class NodeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an OPC Foundation node to an Hylasoft OPC UA Node
|
||||
/// </summary>
|
||||
/// <param name="node">The node to convert</param>
|
||||
/// <param name="parent">the parent node (optional)</param>
|
||||
/// <returns></returns>
|
||||
internal static UaNode ToHylaNode(this OpcF.ReferenceDescription node, Node parent = null)
|
||||
{
|
||||
var name = node.DisplayName.ToString();
|
||||
var nodeId = node.NodeId.ToString();
|
||||
return new UaNode(name, nodeId, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
642
Libraries/h-opc/UaClient.cs
Normal file
642
Libraries/h-opc/UaClient.cs
Normal file
@@ -0,0 +1,642 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using Opc.Ua.Configuration;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Implementation for UA
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling",
|
||||
Justification = "Doesn't make sense to split this class")]
|
||||
public class UaClient : IClient<UaNode>
|
||||
{
|
||||
private readonly UaClientOptions _options = new UaClientOptions();
|
||||
private readonly Uri _serverUrl;
|
||||
private Session _session;
|
||||
|
||||
private readonly IDictionary<string, UaNode> _nodesCache = new Dictionary<string, UaNode>();
|
||||
private readonly IDictionary<string, IList<UaNode>> _folderCache = new Dictionary<string, IList<UaNode>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a server object
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">the url of the server to connect to</param>
|
||||
public UaClient(Uri serverUrl)
|
||||
{
|
||||
_serverUrl = serverUrl;
|
||||
Status = OpcStatus.NotConnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a server object
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">the url of the server to connect to</param>
|
||||
/// <param name="options">custom options to use with ua client</param>
|
||||
public UaClient(Uri serverUrl, UaClientOptions options)
|
||||
{
|
||||
_serverUrl = serverUrl;
|
||||
_options = options;
|
||||
Status = OpcStatus.NotConnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options to configure the UA client session
|
||||
/// </summary>
|
||||
public UaClientOptions Options
|
||||
{
|
||||
get { return _options; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OPC Foundation underlying session object
|
||||
/// </summary>
|
||||
protected Session Session
|
||||
{
|
||||
get
|
||||
{
|
||||
return _session;
|
||||
}
|
||||
}
|
||||
|
||||
private void PostInitializeSession()
|
||||
{
|
||||
var node = _session.NodeCache.Find(ObjectIds.ObjectsFolder);
|
||||
RootNode = new UaNode(string.Empty, node.NodeId.ToString());
|
||||
AddNodeToCache(RootNode);
|
||||
Status = OpcStatus.Connected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
public async Task Connect()
|
||||
{
|
||||
if (Status == OpcStatus.Connected)
|
||||
return;
|
||||
_session = await InitializeSession(_serverUrl);
|
||||
_session.KeepAlive += SessionKeepAlive;
|
||||
_session.SessionClosing += SessionClosing;
|
||||
PostInitializeSession();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the datatype of an OPC tag
|
||||
/// </summary>
|
||||
/// <param name="tag">Tag to get datatype of</param>
|
||||
/// <returns>System Type</returns>
|
||||
public System.Type GetDataType(string tag)
|
||||
{
|
||||
var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
|
||||
DataValueCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
_session.Read(
|
||||
requestHeader: null,
|
||||
maxAge: 0,
|
||||
timestampsToReturn: TimestampsToReturn.Neither,
|
||||
nodesToRead: nodesToRead,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
var type = results[0].WrappedValue.TypeInfo.BuiltInType;
|
||||
return System.Type.GetType("System." + type.ToString());
|
||||
}
|
||||
|
||||
private void SessionKeepAlive(ISession session, KeepAliveEventArgs e)
|
||||
{
|
||||
if (e.CurrentState != ServerState.Running)
|
||||
{
|
||||
if (Status == OpcStatus.Connected)
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
NotifyServerConnectionLost();
|
||||
}
|
||||
}
|
||||
else if (e.CurrentState == ServerState.Running)
|
||||
{
|
||||
if (Status == OpcStatus.NotConnected)
|
||||
{
|
||||
Status = OpcStatus.Connected;
|
||||
NotifyServerConnectionRestored();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionClosing(object? sender, EventArgs e)
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
NotifyServerConnectionLost();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reconnect the OPC session
|
||||
/// </summary>
|
||||
public void ReConnect()
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
_session.Reconnect();
|
||||
Status = OpcStatus.Connected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OPC session, based on the current session parameters.
|
||||
/// </summary>
|
||||
public void RecreateSession()
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
_session = Session.Recreate(_session);
|
||||
PostInitializeSession();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the OPC Client
|
||||
/// </summary>
|
||||
public OpcStatus Status { get; private set; }
|
||||
|
||||
|
||||
private ReadValueIdCollection BuildReadValueIdCollection(string tag, uint attributeId)
|
||||
{
|
||||
var n = FindNode(tag, RootNode);
|
||||
var readValue = new ReadValueId
|
||||
{
|
||||
NodeId = n.NodeId,
|
||||
AttributeId = attributeId
|
||||
};
|
||||
return new ReadValueIdCollection { readValue };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
public ReadEvent<T> Read<T>(string tag)
|
||||
{
|
||||
var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
|
||||
DataValueCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
_session.Read(
|
||||
requestHeader: null,
|
||||
maxAge: 0,
|
||||
timestampsToReturn: TimestampsToReturn.Neither,
|
||||
nodesToRead: nodesToRead,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
var val = results[0];
|
||||
|
||||
var readEvent = new ReadEvent<T>();
|
||||
readEvent.Value = (T)val.Value;
|
||||
readEvent.SourceTimestamp = val.SourceTimestamp;
|
||||
readEvent.ServerTimestamp = val.ServerTimestamp;
|
||||
if (StatusCode.IsGood(val.StatusCode)) readEvent.Quality = Quality.Good;
|
||||
if (StatusCode.IsBad(val.StatusCode)) readEvent.Quality = Quality.Bad;
|
||||
return readEvent;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronously
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
public Task<ReadEvent<T>> ReadAsync<T>(string tag)
|
||||
{
|
||||
var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
|
||||
|
||||
// Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
|
||||
var taskCompletionSource = new TaskCompletionSource<ReadEvent<T>>();
|
||||
_session.BeginRead(
|
||||
requestHeader: null,
|
||||
maxAge: 0,
|
||||
timestampsToReturn: TimestampsToReturn.Neither,
|
||||
nodesToRead: nodesToRead,
|
||||
callback: ar =>
|
||||
{
|
||||
DataValueCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
var response = _session.EndRead(
|
||||
result: ar,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
|
||||
try
|
||||
{
|
||||
CheckReturnValue(response.ServiceResult);
|
||||
var val = results[0];
|
||||
var readEvent = new ReadEvent<T>();
|
||||
readEvent.Value = (T)val.Value;
|
||||
readEvent.SourceTimestamp = val.SourceTimestamp;
|
||||
readEvent.ServerTimestamp = val.ServerTimestamp;
|
||||
if (StatusCode.IsGood(val.StatusCode)) readEvent.Quality = Quality.Good;
|
||||
if (StatusCode.IsBad(val.StatusCode)) readEvent.Quality = Quality.Bad;
|
||||
taskCompletionSource.TrySetResult(readEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.TrySetException(ex);
|
||||
}
|
||||
},
|
||||
asyncState: null);
|
||||
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
|
||||
private WriteValueCollection BuildWriteValueCollection(string tag, uint attributeId, object dataValue)
|
||||
{
|
||||
var n = FindNode(tag, RootNode);
|
||||
var writeValue = new WriteValue
|
||||
{
|
||||
NodeId = n.NodeId,
|
||||
AttributeId = attributeId,
|
||||
Value = { Value = dataValue }
|
||||
};
|
||||
return new WriteValueCollection { writeValue };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item">The value for the item to write</param>
|
||||
public void Write<T>(string tag, T item)
|
||||
{
|
||||
var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);
|
||||
|
||||
StatusCodeCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
_session.Write(
|
||||
requestHeader: null,
|
||||
nodesToWrite: nodesToWrite,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
|
||||
CheckReturnValue(results[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item">The value for the item to write</param>
|
||||
public Task WriteAsync<T>(string tag, T item)
|
||||
{
|
||||
var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);
|
||||
|
||||
// Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
|
||||
var taskCompletionSource = new TaskCompletionSource<StatusCode>();
|
||||
_session.BeginWrite(
|
||||
requestHeader: null,
|
||||
nodesToWrite: nodesToWrite,
|
||||
callback: ar =>
|
||||
{
|
||||
StatusCodeCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
var response = _session.EndWrite(
|
||||
result: ar,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
try
|
||||
{
|
||||
CheckReturnValue(response.ServiceResult);
|
||||
CheckReturnValue(results[0]);
|
||||
taskCompletionSource.SetResult(response.ServiceResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.TrySetException(ex);
|
||||
}
|
||||
},
|
||||
asyncState: null);
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Monitor the specified tag for changes
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of tag to monitor</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="callback">the callback to execute when the value is changed.
|
||||
/// The first parameter is a MonitorEvent object which represents the data point, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||
public void Monitor<T>(string tag, Action<ReadEvent<T>, Action> callback)
|
||||
{
|
||||
var node = FindNode(tag);
|
||||
|
||||
var sub = new Subscription
|
||||
{
|
||||
PublishingInterval = _options.DefaultMonitorInterval,
|
||||
PublishingEnabled = true,
|
||||
LifetimeCount = _options.SubscriptionLifetimeCount,
|
||||
KeepAliveCount = _options.SubscriptionKeepAliveCount,
|
||||
DisplayName = tag,
|
||||
Priority = byte.MaxValue
|
||||
};
|
||||
|
||||
var item = new MonitoredItem
|
||||
{
|
||||
StartNodeId = node.NodeId,
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = tag,
|
||||
SamplingInterval = _options.DefaultMonitorInterval
|
||||
};
|
||||
sub.AddItem(item);
|
||||
_session.AddSubscription(sub);
|
||||
sub.Create();
|
||||
sub.ApplyChanges();
|
||||
|
||||
item.Notification += (monitoredItem, args) =>
|
||||
{
|
||||
var p = (MonitoredItemNotification)args.NotificationValue;
|
||||
var t = p.Value.WrappedValue.Value;
|
||||
Action unsubscribe = () =>
|
||||
{
|
||||
sub.RemoveItems(sub.MonitoredItems);
|
||||
sub.Delete(true);
|
||||
_session.RemoveSubscription(sub);
|
||||
sub.Dispose();
|
||||
};
|
||||
|
||||
var monitorEvent = new ReadEvent<T>();
|
||||
monitorEvent.Value = (T)t;
|
||||
monitorEvent.SourceTimestamp = p.Value.SourceTimestamp;
|
||||
monitorEvent.ServerTimestamp = p.Value.ServerTimestamp;
|
||||
if (StatusCode.IsGood(p.Value.StatusCode)) monitorEvent.Quality = Quality.Good;
|
||||
if (StatusCode.IsBad(p.Value.StatusCode)) monitorEvent.Quality = Quality.Bad;
|
||||
callback(monitorEvent, unsubscribe);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
|
||||
/// <returns>The list of sub-nodes</returns>
|
||||
public IEnumerable<UaNode> ExploreFolder(string tag)
|
||||
{
|
||||
IList<UaNode> nodes;
|
||||
_folderCache.TryGetValue(tag, out nodes);
|
||||
if (nodes != null)
|
||||
return nodes;
|
||||
|
||||
var folder = FindNode(tag);
|
||||
nodes = ClientUtils.Browse(_session, folder.NodeId)
|
||||
.GroupBy(n => n.NodeId) //this is to select distinct
|
||||
.Select(n => n.First())
|
||||
.Where(n => n.NodeClass == NodeClass.Variable || n.NodeClass == NodeClass.Object)
|
||||
.Select(n => n.ToHylaNode(folder))
|
||||
.ToList();
|
||||
|
||||
//add nodes to cache
|
||||
if (!_folderCache.ContainsKey(tag))
|
||||
_folderCache.Add(tag, nodes);
|
||||
foreach (var node in nodes)
|
||||
AddNodeToCache(node);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explores a folder asynchronously
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<Common.Node>> ExploreFolderAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => ExploreFolder(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
|
||||
public UaNode FindNode(string tag)
|
||||
{
|
||||
// if the tag already exists in cache, return it
|
||||
if (_nodesCache.ContainsKey(tag))
|
||||
return _nodesCache[tag];
|
||||
|
||||
// try to find the tag otherwise
|
||||
var found = FindNode(tag, RootNode);
|
||||
if (found != null)
|
||||
{
|
||||
AddNodeToCache(found);
|
||||
return found;
|
||||
}
|
||||
|
||||
// throws an exception if not found
|
||||
throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on the Server", tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find node asynchronously
|
||||
/// </summary>
|
||||
public async Task<Common.Node> FindNodeAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => FindNode(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of the server
|
||||
/// </summary>
|
||||
public UaNode RootNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_session != null)
|
||||
{
|
||||
_session.RemoveSubscriptions(_session.Subscriptions.ToList());
|
||||
_session.Close();
|
||||
_session.Dispose();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void CheckReturnValue(StatusCode status)
|
||||
{
|
||||
if (!StatusCode.IsGood(status))
|
||||
throw new OpcException(string.Format("Invalid response from the server. (Response Status: {0})", status), status);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a node to the cache using the tag as its key
|
||||
/// </summary>
|
||||
/// <param name="node">the node to add</param>
|
||||
private void AddNodeToCache(UaNode node)
|
||||
{
|
||||
if (!_nodesCache.ContainsKey(node.Tag))
|
||||
_nodesCache.Add(node.Tag, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return identity login object for a given URI.
|
||||
/// </summary>
|
||||
/// <param name="url">Login URI</param>
|
||||
/// <returns>AnonUser or User with name and password</returns>
|
||||
private UserIdentity GetIdentity(Uri url)
|
||||
{
|
||||
if (_options.UserIdentity != null)
|
||||
{
|
||||
return _options.UserIdentity;
|
||||
}
|
||||
var uriLogin = new UserIdentity();
|
||||
if (!string.IsNullOrEmpty(url.UserInfo))
|
||||
{
|
||||
var uis = url.UserInfo.Split(':');
|
||||
uriLogin = new UserIdentity(uis[0], uis[1]);
|
||||
}
|
||||
return uriLogin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crappy method to initialize the session. I don't know what many of these things do, sincerely.
|
||||
/// </summary>
|
||||
private async Task<Session> InitializeSession(Uri url)
|
||||
{
|
||||
var certificateValidator = new CertificateValidator();
|
||||
certificateValidator.CertificateValidation += (sender, eventArgs) =>
|
||||
{
|
||||
if (ServiceResult.IsGood(eventArgs.Error))
|
||||
eventArgs.Accept = true;
|
||||
else if ((eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted) && _options.AutoAcceptUntrustedCertificates)
|
||||
eventArgs.Accept = true;
|
||||
else
|
||||
throw new OpcException(string.Format("Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo), eventArgs.Error.StatusCode);
|
||||
};
|
||||
// Build the application configuration
|
||||
var appInstance = new ApplicationInstance
|
||||
{
|
||||
ApplicationType = ApplicationType.Client,
|
||||
ConfigSectionName = _options.ConfigSectionName,
|
||||
ApplicationConfiguration = new ApplicationConfiguration
|
||||
{
|
||||
ApplicationUri = url.ToString(),
|
||||
ApplicationName = _options.ApplicationName,
|
||||
ApplicationType = ApplicationType.Client,
|
||||
CertificateValidator = certificateValidator,
|
||||
ServerConfiguration = new ServerConfiguration
|
||||
{
|
||||
MaxSubscriptionCount = _options.MaxSubscriptionCount,
|
||||
MaxMessageQueueSize = _options.MaxMessageQueueSize,
|
||||
MaxNotificationQueueSize = _options.MaxNotificationQueueSize,
|
||||
MaxPublishRequestCount = _options.MaxPublishRequestCount
|
||||
},
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
AutoAcceptUntrustedCertificates = _options.AutoAcceptUntrustedCertificates
|
||||
},
|
||||
TransportQuotas = new TransportQuotas
|
||||
{
|
||||
OperationTimeout = 600000,
|
||||
MaxStringLength = 1048576,
|
||||
MaxByteStringLength = 1048576,
|
||||
MaxArrayLength = 65535,
|
||||
MaxMessageSize = 4194304,
|
||||
MaxBufferSize = 65535,
|
||||
ChannelLifetime = 600000,
|
||||
SecurityTokenLifetime = 3600000
|
||||
},
|
||||
ClientConfiguration = new ClientConfiguration
|
||||
{
|
||||
DefaultSessionTimeout = 60000,
|
||||
MinSubscriptionLifetime = 10000
|
||||
},
|
||||
DisableHiResClock = true
|
||||
}
|
||||
};
|
||||
|
||||
// Assign a application certificate (when specified)
|
||||
if (_options.ApplicationCertificate != null)
|
||||
appInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(_options.ApplicationCertificate);
|
||||
|
||||
// Find the endpoint to be used
|
||||
var endpoints = ClientUtils.SelectEndpoint(url, _options.UseMessageSecurity);
|
||||
|
||||
// Create the OPC session:
|
||||
var session = await Session.Create(
|
||||
configuration: appInstance.ApplicationConfiguration,
|
||||
endpoint: new ConfiguredEndpoint(
|
||||
collection: null,
|
||||
description: endpoints,
|
||||
configuration: EndpointConfiguration.Create(applicationConfiguration: appInstance.ApplicationConfiguration)),
|
||||
updateBeforeConnect: false,
|
||||
checkDomain: false,
|
||||
sessionName: _options.SessionName,
|
||||
sessionTimeout: _options.SessionTimeout,
|
||||
identity: GetIdentity(url),
|
||||
preferredLocales: new string[] { });
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node starting from the specified node as the root folder
|
||||
/// </summary>
|
||||
/// <param name="tag">the tag to find</param>
|
||||
/// <param name="node">the root node</param>
|
||||
/// <returns></returns>
|
||||
private UaNode FindNode(string tag, UaNode node)
|
||||
{
|
||||
var folders = tag.Split('.');
|
||||
var head = folders.FirstOrDefault();
|
||||
UaNode found;
|
||||
try
|
||||
{
|
||||
var subNodes = ExploreFolder(node.Tag);
|
||||
found = subNodes.Single(n => n.Name == head);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on folder \"{1}\"", head, node.Tag), ex);
|
||||
}
|
||||
|
||||
// remove an array element by converting it to a list
|
||||
var folderList = folders.ToList();
|
||||
folderList.RemoveAt(0); // remove the first node
|
||||
folders = folderList.ToArray();
|
||||
return folders.Length == 0
|
||||
? found // last node, return it
|
||||
: FindNode(string.Join(".", folders), found); // find sub nodes
|
||||
}
|
||||
|
||||
|
||||
private void NotifyServerConnectionLost()
|
||||
{
|
||||
if (ServerConnectionLost != null)
|
||||
ServerConnectionLost(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NotifyServerConnectionRestored()
|
||||
{
|
||||
if (ServerConnectionRestored != null)
|
||||
ServerConnectionRestored(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when the connection to the OPC server is lost.
|
||||
/// </summary>
|
||||
public event EventHandler ServerConnectionLost;
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when the connection to the OPC server is restored.
|
||||
/// </summary>
|
||||
public event EventHandler ServerConnectionRestored;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
121
Libraries/h-opc/UaClientOptions.cs
Normal file
121
Libraries/h-opc/UaClientOptions.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using OpcUa = Opc.Ua;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// This class defines the configuration options for the setup of the UA client session
|
||||
/// </summary>
|
||||
public class UaClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the (optional) certificate for the application to connect to the server
|
||||
/// </summary>
|
||||
public X509Certificate2 ApplicationCertificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the ApplicationName for the client application.
|
||||
/// </summary>
|
||||
public string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should untrusted certificates be silently accepted by the client?
|
||||
/// </summary>
|
||||
public bool AutoAcceptUntrustedCertificates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the ConfigSectionName for the client configuration.
|
||||
/// </summary>
|
||||
public string ConfigSectionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// default monitor interval in Milliseconds.
|
||||
/// </summary>
|
||||
public int DefaultMonitorInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a name to be associated with the created sessions.
|
||||
/// </summary>
|
||||
public string SessionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the timeout for the sessions.
|
||||
/// </summary>
|
||||
public uint SessionTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specify whether message exchange should be secured.
|
||||
/// </summary>
|
||||
public bool UseMessageSecurity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of notifications per publish request.
|
||||
/// The client’s responsibility is to send PublishRequests to the server,
|
||||
/// in order to enable the server to send PublishResponses back.
|
||||
/// The PublishResponses are used to deliver the notifications: but if there
|
||||
/// are no PublishRequests, the server cannot send a notification to the client.
|
||||
/// The server will also verify that the client is alive by checking that
|
||||
/// new PublishRequests are received – LifeTimeCount defines the number of
|
||||
/// PublishingIntervals to wait for a new PublishRequest, before realizing
|
||||
/// that the client is no longer active.The Subscription is then removed from
|
||||
/// the server.
|
||||
/// </summary>
|
||||
public uint SubscriptionLifetimeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If there is no data to send after the next PublishingInterval,
|
||||
/// the server will skip it. But KeepAlive defines how many intervals may be skipped,
|
||||
/// before an empty notification is sent anyway: to give the client a hint that
|
||||
/// the subscription is still alive in the server and that there just has not been
|
||||
/// any data arriving to the client.
|
||||
/// </summary>
|
||||
public uint SubscriptionKeepAliveCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max subscription count.
|
||||
/// </summary>
|
||||
public int MaxSubscriptionCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of messages saved in the queue for each subscription.
|
||||
/// </summary>
|
||||
public int MaxMessageQueueSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of notificates saved in the queue for each monitored item.
|
||||
/// </summary>
|
||||
public int MaxNotificationQueueSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max publish request count.
|
||||
/// </summary>
|
||||
public int MaxPublishRequestCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The identity to connect to the OPC server as
|
||||
/// </summary>
|
||||
public OpcUa.UserIdentity UserIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a client options object
|
||||
/// </summary>
|
||||
public UaClientOptions()
|
||||
{
|
||||
// Initialize default values:
|
||||
ApplicationName = "h-opc-client";
|
||||
AutoAcceptUntrustedCertificates = true;
|
||||
ConfigSectionName = "h-opc-client";
|
||||
DefaultMonitorInterval = 100;
|
||||
SessionName = "h-opc-client";
|
||||
SessionTimeout = 60000U;
|
||||
UseMessageSecurity = false;
|
||||
SubscriptionLifetimeCount = 0;
|
||||
SubscriptionKeepAliveCount = 0;
|
||||
MaxSubscriptionCount = 100;
|
||||
MaxMessageQueueSize = 10;
|
||||
MaxNotificationQueueSize = 100;
|
||||
MaxPublishRequestCount = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Libraries/h-opc/UaNode.cs
Normal file
29
Libraries/h-opc/UaNode.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a node to be used specifically for OPC UA
|
||||
/// </summary>
|
||||
public class UaNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// The UA Id of the node
|
||||
/// </summary>
|
||||
public string NodeId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a UaNode class
|
||||
/// </summary>
|
||||
/// <param name="name">the name of the node</param>
|
||||
/// <param name="nodeId">The UA Id of the node</param>
|
||||
/// <param name="parent">The parent node</param>
|
||||
internal UaNode(string name, string nodeId, Node parent = null)
|
||||
: base(name, parent)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
14
Libraries/h-opc/h-opc.csproj
Normal file
14
Libraries/h-opc/h-opc.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>h_opc</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.4.371.60" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
73
Modbus.Net/Modbus.Net.OPC/AddressFormaterOpc.cs
Normal file
73
Modbus.Net/Modbus.Net.OPC/AddressFormaterOpc.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc地址编码器
|
||||
/// </summary>
|
||||
public class AddressFormaterOpc<TMachineKey, TUnitKey> : AddressFormater where TMachineKey : IEquatable<TMachineKey>
|
||||
where TUnitKey : IEquatable<TUnitKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// 协议构造器
|
||||
/// </summary>
|
||||
/// <param name="tagGeter">如何通过BaseMachine和AddressUnit构造Opc的标签</param>
|
||||
/// <param name="machine">调用这个编码器的设备</param>
|
||||
/// <param name="seperator">每两个标签之间用什么符号隔开,默认为/</param>
|
||||
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey>, AddressUnit<TUnitKey>, string[]> tagGeter,
|
||||
BaseMachine<TMachineKey, TUnitKey> machine,
|
||||
char seperator = '/')
|
||||
{
|
||||
Machine = machine;
|
||||
TagGeter = tagGeter;
|
||||
Seperator = seperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备
|
||||
/// </summary>
|
||||
public BaseMachine<TMachineKey, TUnitKey> Machine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标签构造器
|
||||
/// (设备,地址)->不具备分隔符的标签数组
|
||||
/// </summary>
|
||||
protected Func<BaseMachine<TMachineKey, TUnitKey>, AddressUnit<TUnitKey>, string[]> TagGeter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分割符
|
||||
/// </summary>
|
||||
public char Seperator { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编码地址
|
||||
/// </summary>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
public override string FormatAddress(string area, int address)
|
||||
{
|
||||
var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address == address);
|
||||
if (findAddress == null) return null;
|
||||
var strings = TagGeter(Machine, findAddress);
|
||||
var ans = "";
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
ans += strings[i].Trim().Replace(" ", "") + '\r';
|
||||
ans = ans.Substring(0, ans.Length - 1);
|
||||
return ans;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编码地址
|
||||
/// </summary>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <param name="subAddress">子地址(忽略)</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
public override string FormatAddress(string area, int address, int subAddress)
|
||||
{
|
||||
return FormatAddress(area, address);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Modbus.Net/Modbus.Net.OPC/AddressTranslatorOpc.cs
Normal file
31
Modbus.Net/Modbus.Net.OPC/AddressTranslatorOpc.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc地址解析器
|
||||
/// </summary>
|
||||
public class AddressTranslatorOpc : AddressTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// 地址转换
|
||||
/// </summary>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
public override AddressDef AddressTranslate(string address, bool isRead)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取区域中的单个地址占用的字节长度
|
||||
/// </summary>
|
||||
/// <param name="area">区域名称</param>
|
||||
/// <returns>字节长度</returns>
|
||||
public override double GetAreaByteLength(string area)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Modbus.Net/Modbus.Net.OPC/ClientExtend.cs
Normal file
126
Modbus.Net/Modbus.Net.OPC/ClientExtend.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Hylasoft.Opc.Common;
|
||||
using Hylasoft.Opc.Ua;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc Client Extend interface, Unified for DA and UA
|
||||
/// </summary>
|
||||
public interface IClientExtend : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Unified Root Node
|
||||
/// </summary>
|
||||
Node RootNodeBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
Task Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">
|
||||
/// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`
|
||||
/// </param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
ReadEvent<T> Read<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">
|
||||
/// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`
|
||||
/// </param>
|
||||
/// <param name="item"></param>
|
||||
void Write<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronusly
|
||||
/// </summary>
|
||||
Task<ReadEvent<T>> ReadAsync<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
Task WriteAsync<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
Task<Node> FindNodeAsync(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DaClient Extend
|
||||
/// </summary>
|
||||
public class MyUaClient : UaClient, IClientExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// DaClient Extend
|
||||
/// </summary>
|
||||
public MyUaClient(Uri serverUrl) : base(serverUrl)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unified root node
|
||||
/// </summary>
|
||||
public Node RootNodeBase => RootNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Param input of OpcConnector
|
||||
/// </summary>
|
||||
public class OpcParamIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the action read (not is write)
|
||||
/// </summary>
|
||||
public bool IsRead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tag of a node
|
||||
/// </summary>
|
||||
public string[] Tag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tag splitter of a node
|
||||
/// </summary>
|
||||
public char Split { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value set to node(only available when IsRead is false
|
||||
/// </summary>
|
||||
public object SetValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Param output of OpcConnector
|
||||
/// </summary>
|
||||
public class OpcParamOut
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the action success
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Action return values
|
||||
/// </summary>
|
||||
public byte[] Value { get; set; }
|
||||
}
|
||||
}
|
||||
37
Modbus.Net/Modbus.Net.OPC/Modbus.Net.OPC.csproj
Normal file
37
Modbus.Net/Modbus.Net.OPC/Modbus.Net.OPC.csproj
Normal file
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<AssemblyName>Modbus.Net.OPC</AssemblyName>
|
||||
<RootNamespace>Modbus.Net.OPC</RootNamespace>
|
||||
<PackageId>Modbus.Net.OPC</PackageId>
|
||||
<Version>1.4.1</Version>
|
||||
<Authors>Chris L.(Luo Sheng)</Authors>
|
||||
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
|
||||
<Product>Modbus.Net.OPC</Product>
|
||||
<Description>Modbus.Net OPC Implementation</Description>
|
||||
<Copyright>Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.</Copyright>
|
||||
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.OPC</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/parallelbgls/Modbus.Net</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>hardware communicate protocol modbus Delian</PackageTags>
|
||||
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<IncludeSymbols>True</IncludeSymbols>
|
||||
<IncludeSource>True</IncludeSource>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>bin\Debug\Modbus.Net.OPC.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Opc.UaFx.Client" Version="2.30.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Libraries\h-opc\h-opc.csproj" />
|
||||
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
211
Modbus.Net/Modbus.Net.OPC/OpcConnector.cs
Normal file
211
Modbus.Net/Modbus.Net.OPC/OpcConnector.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Hylasoft.Opc.Common;
|
||||
using Serilog;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc连接器
|
||||
/// </summary>
|
||||
public abstract class OpcConnector : BaseConnector<OpcParamIn, OpcParamOut>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否正在连接
|
||||
/// </summary>
|
||||
protected bool _connect;
|
||||
|
||||
/// <summary>
|
||||
/// Opc客户端
|
||||
/// </summary>
|
||||
protected IClientExtend Client;
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启正则匹配
|
||||
/// </summary>
|
||||
protected bool RegexOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="host">服务端url</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
protected OpcConnector(string host, bool isRegexOn)
|
||||
{
|
||||
ConnectionToken = host;
|
||||
RegexOn = isRegexOn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接标识
|
||||
/// </summary>
|
||||
public override string ConnectionToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在连接
|
||||
/// </summary>
|
||||
public override bool IsConnected => _connect;
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
Client?.Dispose();
|
||||
Client = null;
|
||||
_connect = false;
|
||||
Log.Information("opc client {ConnectionToken} disconnected success", ConnectionToken);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "opc client {ConnectionToken} disconnected error", ConnectionToken);
|
||||
_connect = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReceiveMsgThreadStart()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void ReceiveMsgThreadStop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Task SendMsgWithoutConfirm(OpcParamIn message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带返回发送数据
|
||||
/// </summary>
|
||||
/// <param name="message">需要发送的数据</param>
|
||||
/// <returns>是否发送成功</returns>
|
||||
public override async Task<OpcParamOut> SendMsgAsync(OpcParamIn message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.IsRead)
|
||||
{
|
||||
var split = message.Split;
|
||||
var tag = message.Tag;
|
||||
var rootDirectory = await Client.ExploreFolderAsync("");
|
||||
var answerTag = await SearchTag(tag, split, 0, rootDirectory);
|
||||
if (answerTag != null)
|
||||
{
|
||||
var result = await Client.ReadAsync<object>(answerTag);
|
||||
Log.Verbose($"Opc Machine {ConnectionToken} Read opc tag {answerTag} for value {result}");
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = true,
|
||||
Value = BigEndianValueHelper.Instance.GetBytes(result, result.GetType())
|
||||
};
|
||||
}
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = false,
|
||||
Value = Encoding.ASCII.GetBytes("NoData")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var tag = message.Tag;
|
||||
var split = message.Split;
|
||||
var value = message.SetValue;
|
||||
|
||||
var rootDirectory = await Client.ExploreFolderAsync("");
|
||||
var answerTag = await SearchTag(tag, split, 0, rootDirectory);
|
||||
if (answerTag != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.WriteAsync(answerTag, value);
|
||||
Log.Verbose($"Opc Machine {ConnectionToken} Write opc tag {answerTag} for value {value}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "opc client {ConnectionToken} write exception", ConnectionToken);
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
}
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "opc client {ConnectionToken} read exception", ConnectionToken);
|
||||
return new OpcParamOut
|
||||
{
|
||||
Success = false,
|
||||
Value = Encoding.ASCII.GetBytes("NoData")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索标签
|
||||
/// </summary>
|
||||
/// <param name="tags">标签</param>
|
||||
/// <param name="split">分隔符</param>
|
||||
/// <param name="deep">递归深度(第几级标签)</param>
|
||||
/// <param name="nodes">当前搜索的节点</param>
|
||||
/// <returns>搜索到的标签</returns>
|
||||
private async Task<string> SearchTag(string[] tags, char split, int deep, IEnumerable<Node> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var currentTag = node.Tag.Substring(node.Tag.LastIndexOf(split) + 1);
|
||||
if (RegexOn && Regex.IsMatch(currentTag, tags[deep]) || !RegexOn && currentTag == tags[deep])
|
||||
{
|
||||
if (deep == tags.Length - 1) return node.Tag;
|
||||
var subDirectories = await Client.ExploreFolderAsync(node.Tag);
|
||||
var answerTag = await SearchTag(tags, split, deep + 1, subDirectories);
|
||||
if (answerTag != null) return answerTag;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接PLC
|
||||
/// </summary>
|
||||
/// <returns>是否连接成功</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.Connect();
|
||||
_connect = true;
|
||||
Log.Information("opc client {ConnectionToken} connect success", ConnectionToken);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "opc client {ConnectionToken} connected failed", ConnectionToken);
|
||||
_connect = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Modbus.Net/Modbus.Net.OPC/OpcMachine.cs
Normal file
44
Modbus.Net/Modbus.Net.OPC/OpcMachine.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc Da设备
|
||||
/// </summary>
|
||||
public abstract class OpcMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey> where TKey : IEquatable<TKey>
|
||||
where TUnitKey : IEquatable<TUnitKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="getAddresses">需要读写的地址</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
protected OpcMachine(TKey id, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect)
|
||||
: base(id, getAddresses, keepConnect)
|
||||
{
|
||||
AddressCombiner = new AddressCombinerSingle<TUnitKey>();
|
||||
AddressCombinerSet = new AddressCombinerSingle<TUnitKey>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opc Da设备
|
||||
/// </summary>
|
||||
public abstract class OpcMachine : BaseMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="getAddresses">需要读写的地址</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
protected OpcMachine(string id, IEnumerable<AddressUnit> getAddresses, bool keepConnect)
|
||||
: base(id, getAddresses, keepConnect)
|
||||
{
|
||||
AddressCombiner = new AddressCombinerSingle();
|
||||
AddressCombinerSet = new AddressCombinerSingle();
|
||||
}
|
||||
}
|
||||
}
|
||||
197
Modbus.Net/Modbus.Net.OPC/OpcProtocol.cs
Normal file
197
Modbus.Net/Modbus.Net.OPC/OpcProtocol.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc协议
|
||||
/// </summary>
|
||||
public abstract class OpcProtocol : BaseProtocol<OpcParamIn, OpcParamOut, ProtocolUnit<OpcParamIn, OpcParamOut>,
|
||||
PipeUnit<OpcParamIn, OpcParamOut, IProtocolLinker<OpcParamIn, OpcParamOut>,
|
||||
ProtocolUnit<OpcParamIn, OpcParamOut>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected OpcProtocol() : base(0, 0, Endian.BigEndianLsb)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region 读数据
|
||||
|
||||
/// <summary>
|
||||
/// 读数据输入
|
||||
/// </summary>
|
||||
public class ReadRequestOpcInputStruct : IInputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="tag">标签</param>
|
||||
/// <param name="split">分隔符</param>
|
||||
public ReadRequestOpcInputStruct(string[] tag, char split)
|
||||
{
|
||||
Tag = tag;
|
||||
Split = split;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签
|
||||
/// </summary>
|
||||
public string[] Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 分隔符
|
||||
/// </summary>
|
||||
public char Split { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读地址输出
|
||||
/// </summary>
|
||||
public class ReadRequestOpcOutputStruct : IOutputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="value">读取的数据</param>
|
||||
public ReadRequestOpcOutputStruct(byte[] value)
|
||||
{
|
||||
GetValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取的地址
|
||||
/// </summary>
|
||||
public byte[] GetValue { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读数据协议
|
||||
/// </summary>
|
||||
[SpecialProtocolUnit]
|
||||
public class ReadRequestOpcProtocol : ProtocolUnit<OpcParamIn, OpcParamOut>
|
||||
{
|
||||
/// <summary>
|
||||
/// 从对象的参数数组格式化
|
||||
/// </summary>
|
||||
/// <param name="message">非结构化的输入数据</param>
|
||||
/// <returns>格式化后的字节流</returns>
|
||||
public override OpcParamIn Format(IInputStruct message)
|
||||
{
|
||||
var r_message = (ReadRequestOpcInputStruct) message;
|
||||
return new OpcParamIn
|
||||
{
|
||||
IsRead = true,
|
||||
Tag = r_message.Tag,
|
||||
Split = r_message.Split
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把仪器返回的内容填充到输出结构中
|
||||
/// </summary>
|
||||
/// <param name="messageBytes">返回数据的字节流</param>
|
||||
/// <param name="pos">转换标记位</param>
|
||||
/// <returns>结构化的输出数据</returns>
|
||||
public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos)
|
||||
{
|
||||
return new ReadRequestOpcOutputStruct(messageBytes.Value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 写数据
|
||||
|
||||
/// <summary>
|
||||
/// 写数据输入
|
||||
/// </summary>
|
||||
public class WriteRequestOpcInputStruct : IInputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="tag">标签</param>
|
||||
/// <param name="split">分隔符</param>
|
||||
/// <param name="setValue">写入的数据</param>
|
||||
public WriteRequestOpcInputStruct(string[] tag, char split, object setValue)
|
||||
{
|
||||
Tag = tag;
|
||||
Split = split;
|
||||
SetValue = setValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签
|
||||
/// </summary>
|
||||
public string[] Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 分隔符
|
||||
/// </summary>
|
||||
public char Split { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入的数据
|
||||
/// </summary>
|
||||
public object SetValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写数据输出
|
||||
/// </summary>
|
||||
public class WriteRequestOpcOutputStruct : IOutputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="writeResult">写入是否成功</param>
|
||||
public WriteRequestOpcOutputStruct(bool writeResult)
|
||||
{
|
||||
WriteResult = writeResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入是否成功
|
||||
/// </summary>
|
||||
public bool WriteResult { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写数据协议
|
||||
/// </summary>
|
||||
[SpecialProtocolUnit]
|
||||
public class WriteRequestOpcProtocol : ProtocolUnit<OpcParamIn, OpcParamOut>
|
||||
{
|
||||
/// <summary>
|
||||
/// 从对象的参数数组格式化
|
||||
/// </summary>
|
||||
/// <param name="message">非结构化的输入数据</param>
|
||||
/// <returns>格式化后的字节流</returns>
|
||||
public override OpcParamIn Format(IInputStruct message)
|
||||
{
|
||||
var r_message = (WriteRequestOpcInputStruct) message;
|
||||
return new OpcParamIn
|
||||
{
|
||||
IsRead = false,
|
||||
Tag = r_message.Tag,
|
||||
Split = r_message.Split,
|
||||
SetValue = r_message.SetValue
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把仪器返回的内容填充到输出结构中
|
||||
/// </summary>
|
||||
/// <param name="messageBytes">返回数据的字节流</param>
|
||||
/// <param name="pos">转换标记位</param>
|
||||
/// <returns>结构化的输出数据</returns>
|
||||
public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos)
|
||||
{
|
||||
var ansByte = BigEndianValueHelper.Instance.GetByte(messageBytes.Value, ref pos);
|
||||
var ans = ansByte != 0;
|
||||
return new WriteRequestOpcOutputStruct(ans);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
55
Modbus.Net/Modbus.Net.OPC/OpcProtocolLinker.cs
Normal file
55
Modbus.Net/Modbus.Net.OPC/OpcProtocolLinker.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc协议连接器
|
||||
/// </summary>
|
||||
public abstract class OpcProtocolLinker : ProtocolLinker<OpcParamIn, OpcParamOut>
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送并接收数据
|
||||
/// </summary>
|
||||
/// <param name="content">发送协议的内容</param>
|
||||
/// <returns>接收协议的内容</returns>
|
||||
public override async Task<OpcParamOut> SendReceiveAsync(OpcParamIn content)
|
||||
{
|
||||
var extBytes = BytesExtend(content);
|
||||
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
|
||||
return receiveBytes == null
|
||||
? null
|
||||
: receiveBytes.Value.Length == 0 ? receiveBytes : BytesDecact(receiveBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
||||
/// </summary>
|
||||
/// <param name="content">发送协议的内容</param>
|
||||
/// <returns>接收协议的内容</returns>
|
||||
public override async Task<OpcParamOut> SendReceiveWithoutExtAndDecAsync(OpcParamIn content)
|
||||
{
|
||||
//发送数据
|
||||
var receiveBytes = await BaseConnector.SendMsgAsync(content);
|
||||
//容错处理
|
||||
var checkRight = CheckRight(receiveBytes);
|
||||
return checkRight == null
|
||||
? new OpcParamOut {Success = false, Value = new byte[0]}
|
||||
: (!checkRight.Value ? null : receiveBytes);
|
||||
//返回字符
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查接收的数据是否正确
|
||||
/// </summary>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
public override bool? CheckRight(OpcParamOut content)
|
||||
{
|
||||
if (content == null || !content.Success) return false;
|
||||
if (content.Value.Length == 6 && Encoding.ASCII.GetString(content.Value) == "NoData")
|
||||
return null;
|
||||
return base.CheckRight(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Modbus.Net/Modbus.Net.OPC/OpcUaConnector.cs
Normal file
43
Modbus.Net/Modbus.Net.OPC/OpcUaConnector.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc UA连接实现
|
||||
/// </summary>
|
||||
public class OpcUaConnector : OpcConnector
|
||||
{
|
||||
/// <summary>
|
||||
/// UA单例管理
|
||||
/// </summary>
|
||||
protected static Dictionary<string, OpcUaConnector> _instances = new Dictionary<string, OpcUaConnector>();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="host">Opc UA 服务地址</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
protected OpcUaConnector(string host, bool isRegexOn) : base(host, isRegexOn)
|
||||
{
|
||||
Client = new MyUaClient(new Uri(ConnectionToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据地址获取UA连接器单例
|
||||
/// </summary>
|
||||
/// <param name="host">Opc UA服务地址</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
/// <returns>OPC UA实例</returns>
|
||||
public static OpcUaConnector Instance(string host, bool isRegexOn)
|
||||
{
|
||||
if (!_instances.ContainsKey(host))
|
||||
{
|
||||
var connector = new OpcUaConnector(host, isRegexOn);
|
||||
_instances.Add(host, connector);
|
||||
}
|
||||
return _instances[host];
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Modbus.Net/Modbus.Net.OPC/OpcUaMachine.cs
Normal file
74
Modbus.Net/Modbus.Net.OPC/OpcUaMachine.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc UA设备
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">设备Id的类型</typeparam>
|
||||
/// <typeparam name="TUnitKey">设备中地址的Id的类型</typeparam>
|
||||
public class OpcUaMachine<TKey, TUnitKey> : OpcMachine<TKey, TUnitKey> where TKey : IEquatable<TKey>
|
||||
where TUnitKey : IEquatable<TUnitKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">需要读写的数据</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaMachine(TKey id, string connectionString, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, bool isRegexOn = false)
|
||||
: base(id, getAddresses, keepConnect)
|
||||
{
|
||||
BaseUtility = new OpcUaUtility(connectionString, isRegexOn);
|
||||
((OpcUtility) BaseUtility).GetSeperator +=
|
||||
() => ((AddressFormaterOpc<string, string>) AddressFormater).Seperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">需要读写的数据</param>
|
||||
public OpcUaMachine(TKey id, string connectionString, IEnumerable<AddressUnit<TUnitKey>> getAddresses)
|
||||
: this(id, connectionString, getAddresses, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opc UA设备
|
||||
/// </summary>
|
||||
public class OpcUaMachine : OpcMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">需要读写的数据</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaMachine(string id, string connectionString, IEnumerable<AddressUnit> getAddresses, bool keepConnect, bool isRegexOn = false)
|
||||
: base(id, getAddresses, keepConnect)
|
||||
{
|
||||
BaseUtility = new OpcUaUtility(connectionString, isRegexOn);
|
||||
((OpcUtility) BaseUtility).GetSeperator +=
|
||||
() => ((AddressFormaterOpc<string, string>) AddressFormater).Seperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">需要读写的数据</param>
|
||||
public OpcUaMachine(string id, string connectionString, IEnumerable<AddressUnit> getAddresses)
|
||||
: this(id, connectionString, getAddresses, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Modbus.Net/Modbus.Net.OPC/OpcUaProtocol.cs
Normal file
36
Modbus.Net/Modbus.Net.OPC/OpcUaProtocol.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc UA协议
|
||||
/// </summary>
|
||||
public class OpcUaProtocol : OpcProtocol
|
||||
{
|
||||
private readonly string _host;
|
||||
|
||||
private readonly bool _isRegexOn;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="host">Opc UA服务地址</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaProtocol(string host, bool isRegexOn)
|
||||
{
|
||||
_host = host;
|
||||
_isRegexOn = isRegexOn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接设备
|
||||
/// </summary>
|
||||
/// <returns>是否连接成功</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
ProtocolLinker = new OpcUaProtocolLinker(_host, _isRegexOn);
|
||||
if (!await ProtocolLinker.ConnectAsync()) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Modbus.Net/Modbus.Net.OPC/OpcUaProtocolLinker.cs
Normal file
28
Modbus.Net/Modbus.Net.OPC/OpcUaProtocolLinker.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Configuration;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc UA协议连接器
|
||||
/// </summary>
|
||||
public class OpcUaProtocolLinker : OpcProtocolLinker
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaProtocolLinker(bool isRegexOn) : this(ConfigurationManager.AppSettings["OpcUaHost"], isRegexOn)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="host">Opc UA服务地址</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaProtocolLinker(string host, bool isRegexOn)
|
||||
{
|
||||
BaseConnector = OpcUaConnector.Instance(host, isRegexOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Modbus.Net/Modbus.Net.OPC/OpcUaUtility.cs
Normal file
18
Modbus.Net/Modbus.Net.OPC/OpcUaUtility.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc Ua协议Api入口
|
||||
/// </summary>
|
||||
public class OpcUaUtility : OpcUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||
public OpcUaUtility(string connectionString, bool isRegexOn = false) : base(connectionString)
|
||||
{
|
||||
Wrapper = new OpcUaProtocol(ConnectionString, isRegexOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Modbus.Net/Modbus.Net.OPC/OpcUtility.cs
Normal file
100
Modbus.Net/Modbus.Net.OPC/OpcUtility.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Modbus.Net.OPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Opc通用Api入口
|
||||
/// </summary>
|
||||
public abstract class OpcUtility : BaseUtility<OpcParamIn, OpcParamOut, ProtocolUnit<OpcParamIn, OpcParamOut>,
|
||||
PipeUnit<OpcParamIn, OpcParamOut, IProtocolLinker<OpcParamIn, OpcParamOut>,
|
||||
ProtocolUnit<OpcParamIn, OpcParamOut>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取分隔符
|
||||
/// </summary>
|
||||
/// <returns>分隔符</returns>
|
||||
public delegate char GetSeperatorDelegate();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
protected OpcUtility(string connectionString) : base(0, 0)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
AddressTranslator = new AddressTranslatorOpc();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 端格式(大端)
|
||||
/// </summary>
|
||||
public override Endian Endian => Endian.BigEndianLsb;
|
||||
|
||||
/// <summary>
|
||||
/// 获取分隔符
|
||||
/// </summary>
|
||||
public event GetSeperatorDelegate GetSeperator;
|
||||
|
||||
/// <summary>
|
||||
/// 设置连接方式(Opc忽略该函数)
|
||||
/// </summary>
|
||||
/// <param name="connectionType">连接方式</param>
|
||||
public override void SetConnectionType(int connectionType)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据
|
||||
/// </summary>
|
||||
/// <param name="startAddress">开始地址</param>
|
||||
/// <param name="getByteCount">获取字节数个数</param>
|
||||
/// <returns>接收到的byte数据</returns>
|
||||
public override async Task<byte[]> GetDatasAsync(string startAddress, int getByteCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
var split = GetSeperator?.Invoke() ?? '/';
|
||||
var readRequestOpcInputStruct = new ReadRequestOpcInputStruct(startAddress.Split('\r'), split);
|
||||
var readRequestOpcOutputStruct =
|
||||
await
|
||||
Wrapper.SendReceiveAsync<ReadRequestOpcOutputStruct>(Wrapper[typeof(ReadRequestOpcProtocol)],
|
||||
readRequestOpcInputStruct);
|
||||
return readRequestOpcOutputStruct?.GetValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"OpcUtility -> GetDatas: {ConnectionString} error");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据
|
||||
/// </summary>
|
||||
/// <param name="startAddress">开始地址</param>
|
||||
/// <param name="setContents">设置数据</param>
|
||||
/// <returns>是否设置成功</returns>
|
||||
public override async Task<bool> SetDatasAsync(string startAddress, object[] setContents)
|
||||
{
|
||||
try
|
||||
{
|
||||
var split = GetSeperator?.Invoke() ?? '/';
|
||||
var writeRequestOpcInputStruct =
|
||||
new WriteRequestOpcInputStruct(startAddress.Split('\r'), split, setContents[0]);
|
||||
var writeRequestOpcOutputStruct =
|
||||
await
|
||||
Wrapper.SendReceiveAsync<WriteRequestOpcOutputStruct>(Wrapper[typeof(WriteRequestOpcProtocol)],
|
||||
writeRequestOpcInputStruct);
|
||||
return writeRequestOpcOutputStruct?.WriteResult == true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"OpcUtility -> SetDatas: {ConnectionString} error");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Modbus.Net/Modbus.Net.OPC/README.md
Normal file
7
Modbus.Net/Modbus.Net.OPC/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Modbus.Net.OPC
|
||||
===================
|
||||
[](https://www.nuget.org/packages/Modbus.Net.OPC/)
|
||||
|
||||
OPC Implementation of Modbus.Net
|
||||
|
||||
Doc has been moved to wiki.
|
||||
@@ -20,13 +20,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Tests", "..\Test
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.Test", "..\Samples\Modbus.Net.Modbus.Test\Modbus.Net.Modbus.Test.csproj", "{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MachineJob", "..\Samples\MachineJob\MachineJob.csproj", "{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.NA200H", "Modbus.Net.Modbus.NA200H\Modbus.Net.Modbus.NA200H.csproj", "{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.PersistedTests", "..\Tests\Modbus.Net.PersistedTests\Modbus.Net.PersistedTests.csproj", "{4B946C56-D09F-4EEB-BE88-4A2C97815A77}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyType", "..\Samples\AnyType\AnyType.csproj", "{1857DA63-3335-428F-84D8-1FA4F8178643}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyType", "..\Samples\AnyType\AnyType.csproj", "{1857DA63-3335-428F-84D8-1FA4F8178643}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.OPC", "Modbus.Net.OPC\Modbus.Net.OPC.csproj", "{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "h-opc", "..\Libraries\h-opc\h-opc.csproj", "{347D0027-45F6-48C9-A917-1B1DF6C66DC5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossLamp", "..\Samples\CrossLamp\CrossLamp.csproj", "{AA3A42D2-0502-41D3-929A-BAB729DF07D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -66,6 +74,22 @@ Global
|
||||
{1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{347D0027-45F6-48C9-A917-1B1DF6C66DC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{347D0027-45F6-48C9-A917-1B1DF6C66DC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{347D0027-45F6-48C9-A917-1B1DF6C66DC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{347D0027-45F6-48C9-A917-1B1DF6C66DC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -75,6 +99,8 @@ Global
|
||||
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
{4B946C56-D09F-4EEB-BE88-4A2C97815A77} = {D8DD32FC-CF39-4A1A-8FBF-9E82C5278C34}
|
||||
{1857DA63-3335-428F-84D8-1FA4F8178643} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
{414956B8-DBD4-414C-ABD3-565580739646} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaProvider xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider Automation-ObjectFrame-ChangeList-Provider-v1.0.xsd">
|
||||
<Package name="Siemens.Automation.DomainModel" version="1.1.0.0">
|
||||
<ProviderObject name="Siemens.Automation.DomainModel.ReferenceTagData">
|
||||
<Interface name="IBlTag">
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.ControllerRootTagBaseData">
|
||||
<TrackedRelation name="ReferencedByRefTagAsRoot">
|
||||
<Property name="CompileTime">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.CompileTime" />
|
||||
</Property>
|
||||
<Property name="IsValid">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.IsValid" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.EAMTZTagData">
|
||||
<TrackedRelation name="ReferencedByRefTag">
|
||||
<Property name="DataTypeName">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureItem.DisplayTypeName" />
|
||||
</Property>
|
||||
<Property name="LogicalAddress">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainModel.ITagAddress.LogicalAddress" />
|
||||
</Property>
|
||||
<Property name="Name">
|
||||
<TrackedAttribute name="Siemens.Automation.ObjectFrame.ICoreAttributes.Name" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
</Interface>
|
||||
</ProviderObject>
|
||||
</Package>
|
||||
</ClMetaProvider>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaProvider xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider Automation-ObjectFrame-ChangeList-Provider-v1.0.xsd">
|
||||
<Package name="Siemens.Automation.DomainModel" version="1.1.0.0">
|
||||
<ProviderObject name="Siemens.Automation.DomainModel.ReferenceTagData">
|
||||
<Interface name="IBlTag">
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.ControllerRootTagBaseData">
|
||||
<TrackedRelation name="ReferencedByRefTagAsRoot">
|
||||
<Property name="CompileTime">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.CompileTime" />
|
||||
</Property>
|
||||
<Property name="IsValid">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.IsValid" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.EAMTZTagData">
|
||||
<TrackedRelation name="ReferencedByRefTag">
|
||||
<Property name="DataTypeName">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureItem.DisplayTypeName" />
|
||||
</Property>
|
||||
<Property name="LogicalAddress">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainModel.ITagAddress.LogicalAddress" />
|
||||
</Property>
|
||||
<Property name="Name">
|
||||
<TrackedAttribute name="Siemens.Automation.ObjectFrame.ICoreAttributes.Name" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
</Interface>
|
||||
</ProviderObject>
|
||||
</Package>
|
||||
</ClMetaProvider>
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaConsumer xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaConsumer Automation-ObjectFrame-ChangeList-Consumer-v1.0.xsd">
|
||||
<Package name="HmiBase" version="2.0.0.0">
|
||||
<TargetCompiler name="Siemens.Simatic.Hmi.Es2rt" version="2.0.0.0">
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.SystemTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Automation.DomainModel.UserTextRangeListData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.AlarmServices.ObjectFrame.Private.SimpleControllerAlarmData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Common.Base.HmiBaseBL" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.Hmi.Utah.Globalization.HmiTextData" filter="False" resolveTextualReference="False">
|
||||
<Interface name="Siemens.Automation.ObjectFrame.ICoreAttributes">
|
||||
<IncludeProperty name="Author" />
|
||||
<IncludeProperty name="Comment" />
|
||||
<IncludeProperty name="Name" />
|
||||
</Interface>
|
||||
</ConsumedObject>
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ComponentTextlistData" filter="False" resolveTextualReference="False" />
|
||||
<ConsumedObject name="Siemens.Simatic.SystemDiagnosis.Model.ErrorTextlistProxyData" filter="False" resolveTextualReference="False" />
|
||||
</TargetCompiler>
|
||||
</Package>
|
||||
</ClMetaConsumer>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClMetaProvider xmlns="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.siemens.com/Automation/2004/04/ObjectFrame/ClMetaProvider Automation-ObjectFrame-ChangeList-Provider-v1.0.xsd">
|
||||
<Package name="Siemens.Automation.DomainModel" version="1.1.0.0">
|
||||
<ProviderObject name="Siemens.Automation.DomainModel.ReferenceTagData">
|
||||
<Interface name="IBlTag">
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.ControllerRootTagBaseData">
|
||||
<TrackedRelation name="ReferencedByRefTagAsRoot">
|
||||
<Property name="CompileTime">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.CompileTime" />
|
||||
</Property>
|
||||
<Property name="IsValid">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureRoot.IsValid" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
<TrackedObject name="Siemens.Automation.DomainModel.EAMTZTagData">
|
||||
<TrackedRelation name="ReferencedByRefTag">
|
||||
<Property name="DataTypeName">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainServices.CommonTypeSystem.IStructureItem.DisplayTypeName" />
|
||||
</Property>
|
||||
<Property name="LogicalAddress">
|
||||
<TrackedAttribute name="Siemens.Automation.DomainModel.ITagAddress.LogicalAddress" />
|
||||
</Property>
|
||||
<Property name="Name">
|
||||
<TrackedAttribute name="Siemens.Automation.ObjectFrame.ICoreAttributes.Name" />
|
||||
</Property>
|
||||
</TrackedRelation>
|
||||
</TrackedObject>
|
||||
</Interface>
|
||||
</ProviderObject>
|
||||
</Package>
|
||||
</ClMetaProvider>
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
PROGRAM
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user