Revert OPC
This commit is contained in:
@@ -1,100 +0,0 @@
|
|||||||
using Opc.Ua;
|
|
||||||
using Opc.Ua.Client;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
using Hylasoft.Opc.Common;
|
|
||||||
using Opc.Ua;
|
|
||||||
using Opc.Ua.Client;
|
|
||||||
using Opc.Ua.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
|
||||||
<AssemblyName>h-opc-ua</AssemblyName>
|
|
||||||
<RootNamespace>Hylasoft.Opc</RootNamespace>
|
|
||||||
<PackageId>h-opc-ua</PackageId>
|
|
||||||
<Version>0.9.3</Version>
|
|
||||||
<Product>Modbus.Net</Product>
|
|
||||||
<Authors>Chris L.(Luo Sheng)</Authors>
|
|
||||||
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
|
|
||||||
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net</PackageProjectUrl>
|
|
||||||
<RepositoryUrl>https://github.com/parallelbgls/Modbus.Net/</RepositoryUrl>
|
|
||||||
<Description>High extensible hardware communication implementation platform.</Description>
|
|
||||||
<Copyright>Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.</Copyright>
|
|
||||||
<PackageTags>hardware communicate protocol Delian</PackageTags>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
|
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
|
||||||
<IncludeSymbols>True</IncludeSymbols>
|
|
||||||
<IncludeSource>True</IncludeSource>
|
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.4.371.60" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ namespace Modbus.Net.Modbus.NA200H
|
|||||||
var head = splitString[0];
|
var head = splitString[0];
|
||||||
var tail = splitString[1];
|
var tail = splitString[1];
|
||||||
string sub;
|
string sub;
|
||||||
if (tail.Contains('.'))
|
if (tail.Contains("."))
|
||||||
{
|
{
|
||||||
var splitString2 = tail.Split('.');
|
var splitString2 = tail.Split('.');
|
||||||
sub = splitString2[1];
|
sub = splitString2[1];
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus.NA200H</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus.NA200H</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus.NA200H</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus.NA200H</RootNamespace>
|
||||||
<PackageId>Modbus.Net.Modbus.NA200H</PackageId>
|
<PackageId>Modbus.Net.Modbus.NA200H</PackageId>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus.SelfDefinedSample</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus.SelfDefinedSample</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus.SelfDefinedSample</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus.SelfDefinedSample</RootNamespace>
|
||||||
<PackageId>Modbus.Net.Modbus.SelfDefinedSample</PackageId>
|
<PackageId>Modbus.Net.Modbus.SelfDefinedSample</PackageId>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace Modbus.Net.Modbus
|
|||||||
var head = splitString[0];
|
var head = splitString[0];
|
||||||
var tail = splitString[1];
|
var tail = splitString[1];
|
||||||
string sub;
|
string sub;
|
||||||
if (tail.Contains('.'))
|
if (tail.Contains("."))
|
||||||
{
|
{
|
||||||
var splitString2 = tail.Split('.');
|
var splitString2 = tail.Split('.');
|
||||||
sub = splitString2[1];
|
sub = splitString2[1];
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus</RootNamespace>
|
||||||
<PackageId>Modbus.Net.Modbus</PackageId>
|
<PackageId>Modbus.Net.Modbus</PackageId>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Modbus.Net.Modbus
|
namespace Modbus.Net.Modbus
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1260,7 +1260,7 @@ namespace Modbus.Net.Modbus
|
|||||||
SlaveAddress = slaveAddress;
|
SlaveAddress = slaveAddress;
|
||||||
FunctionCode = (byte)ModbusProtocolFunctionCode.WriteFileRecord;
|
FunctionCode = (byte)ModbusProtocolFunctionCode.WriteFileRecord;
|
||||||
byte count = 0;
|
byte count = 0;
|
||||||
foreach(var writeRecord in writeRecords)
|
foreach (var writeRecord in writeRecords)
|
||||||
{
|
{
|
||||||
count += (byte)(writeRecord.RecordData.Length * 2 + 7);
|
count += (byte)(writeRecord.RecordData.Length * 2 + 7);
|
||||||
}
|
}
|
||||||
@@ -1674,8 +1674,8 @@ namespace Modbus.Net.Modbus
|
|||||||
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
||||||
var functionCode = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
var functionCode = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
||||||
var byteCount = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
var byteCount = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
||||||
ushort[] readValues= new ushort[byteCount / 2];
|
ushort[] readValues = new ushort[byteCount / 2];
|
||||||
for (int i = 0; i< byteCount / 2; i++)
|
for (int i = 0; i < byteCount / 2; i++)
|
||||||
{
|
{
|
||||||
readValues[i] = ValueHelper.GetInstance(Endian).GetUShort(messageBytes, ref flag);
|
readValues[i] = ValueHelper.GetInstance(Endian).GetUShort(messageBytes, ref flag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ namespace Modbus.Net.Modbus
|
|||||||
inputStruct);
|
inputStruct);
|
||||||
return new ReturnStruct<CommEventLogData>()
|
return new ReturnStruct<CommEventLogData>()
|
||||||
{
|
{
|
||||||
Datas = new CommEventLogData() { Status = outputStruct.Status, Events = outputStruct.Events},
|
Datas = new CommEventLogData() { Status = outputStruct.Status, Events = outputStruct.Events },
|
||||||
IsSuccess = true,
|
IsSuccess = true,
|
||||||
ErrorCode = 0,
|
ErrorCode = 0,
|
||||||
ErrorMsg = null
|
ErrorMsg = null
|
||||||
@@ -444,7 +444,7 @@ namespace Modbus.Net.Modbus
|
|||||||
inputStruct);
|
inputStruct);
|
||||||
return new ReturnStruct<SlaveIdData>()
|
return new ReturnStruct<SlaveIdData>()
|
||||||
{
|
{
|
||||||
Datas = new SlaveIdData() { SlaveId = outputStruct.SlaveId, IndicatorStatus = outputStruct.RunIndicatorStatus, AdditionalData = outputStruct.AdditionalData},
|
Datas = new SlaveIdData() { SlaveId = outputStruct.SlaveId, IndicatorStatus = outputStruct.RunIndicatorStatus, AdditionalData = outputStruct.AdditionalData },
|
||||||
IsSuccess = true,
|
IsSuccess = true,
|
||||||
ErrorCode = 0,
|
ErrorCode = 0,
|
||||||
ErrorMsg = null
|
ErrorMsg = null
|
||||||
@@ -534,7 +534,7 @@ namespace Modbus.Net.Modbus
|
|||||||
inputStruct);
|
inputStruct);
|
||||||
return new ReturnStruct<MaskRegisterData>()
|
return new ReturnStruct<MaskRegisterData>()
|
||||||
{
|
{
|
||||||
Datas = new MaskRegisterData() { ReferenceAddress = outputStruct.ReferenceAddress, AndMask = outputStruct.AndMask, OrMask = outputStruct.OrMask},
|
Datas = new MaskRegisterData() { ReferenceAddress = outputStruct.ReferenceAddress, AndMask = outputStruct.AndMask, OrMask = outputStruct.OrMask },
|
||||||
IsSuccess = true,
|
IsSuccess = true,
|
||||||
ErrorCode = 0,
|
ErrorCode = 0,
|
||||||
ErrorMsg = null
|
ErrorMsg = null
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Hylasoft.Opc.Common;
|
using Hylasoft.Opc.Common;
|
||||||
|
using Hylasoft.Opc.Da;
|
||||||
using Hylasoft.Opc.Ua;
|
using Hylasoft.Opc.Ua;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -19,7 +20,7 @@ namespace Modbus.Net.OPC
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect the client to the OPC Server
|
/// Connect the client to the OPC Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task Connect();
|
void Connect();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a tag
|
/// Read a tag
|
||||||
@@ -64,6 +65,25 @@ namespace Modbus.Net.OPC
|
|||||||
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
|
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UaClient Extend
|
||||||
|
/// </summary>
|
||||||
|
public class MyDaClient : DaClient, IClientExtend
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UaClient Extend
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverUrl">Url address of Opc UA server</param>
|
||||||
|
public MyDaClient(Uri serverUrl) : base(serverUrl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unified root node
|
||||||
|
/// </summary>
|
||||||
|
public Node RootNodeBase => RootNode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DaClient Extend
|
/// DaClient Extend
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
61
Modbus.Net/Modbus.Net.OPC/FBox/FBoxOpcDaManchine.cs
Normal file
61
Modbus.Net/Modbus.Net.OPC/FBox/FBoxOpcDaManchine.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace Modbus.Net.OPC.FBox
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FBox的Opc服务设备
|
||||||
|
/// </summary>
|
||||||
|
public class FBoxOpcDaMachine : OpcDaMachine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备id</param>
|
||||||
|
/// <param name="localSequence">页名称</param>
|
||||||
|
/// <param name="linkerName">设备名称</param>
|
||||||
|
/// <param name="getAddresses">获取地址</param>
|
||||||
|
/// <param name="keepConnect">是否保持连接</param>
|
||||||
|
public FBoxOpcDaMachine(string id, string localSequence, string linkerName,
|
||||||
|
IEnumerable<AddressUnit> getAddresses, bool keepConnect)
|
||||||
|
: base(
|
||||||
|
id,
|
||||||
|
ConfigurationManager.AppSettings["FBoxOpcDaHost"] ?? "opcda://localhost/FBoxOpcServer", getAddresses,
|
||||||
|
keepConnect, true)
|
||||||
|
{
|
||||||
|
LocalSequence = localSequence;
|
||||||
|
LinkerName = linkerName;
|
||||||
|
AddressFormater =
|
||||||
|
new AddressFormaterOpc<string, string>(
|
||||||
|
(machine, unit) =>
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"(.*)", ((FBoxOpcDaMachine) machine).LinkerName, ((FBoxOpcDaMachine) machine).LocalSequence,
|
||||||
|
unit.Name
|
||||||
|
}, this, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备id</param>
|
||||||
|
/// <param name="localSequence">页名称</param>
|
||||||
|
/// <param name="linkerName">设备名称</param>
|
||||||
|
/// <param name="getAddresses">获取地址</param>
|
||||||
|
public FBoxOpcDaMachine(string id, string localSequence, string linkerName,
|
||||||
|
IEnumerable<AddressUnit> getAddresses)
|
||||||
|
: this(id, localSequence, linkerName, getAddresses, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 页名称
|
||||||
|
/// </summary>
|
||||||
|
public string LocalSequence { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备名称
|
||||||
|
/// </summary>
|
||||||
|
public string LinkerName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFramework>net462</TargetFramework>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.OPC</AssemblyName>
|
<AssemblyName>Modbus.Net.OPC</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.OPC</RootNamespace>
|
<RootNamespace>Modbus.Net.OPC</RootNamespace>
|
||||||
<PackageId>Modbus.Net.OPC</PackageId>
|
<PackageId>Modbus.Net.OPC</PackageId>
|
||||||
@@ -29,15 +30,18 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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" />
|
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
<Reference Include="System.Configuration" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="README.md" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="H.Opc" Version="0.9.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -191,15 +191,11 @@ namespace Modbus.Net.OPC
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private bool Connect()
|
||||||
/// 连接PLC
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>是否连接成功</returns>
|
|
||||||
public override async Task<bool> ConnectAsync()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Client.Connect();
|
Client.Connect();
|
||||||
_connect = true;
|
_connect = true;
|
||||||
logger.LogInformation("opc client {ConnectionToken} connect success", ConnectionToken);
|
logger.LogInformation("opc client {ConnectionToken} connect success", ConnectionToken);
|
||||||
return true;
|
return true;
|
||||||
@@ -211,5 +207,14 @@ namespace Modbus.Net.OPC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接PLC,异步
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否连接成功</returns>
|
||||||
|
public override Task<bool> ConnectAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(Connect());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
42
Modbus.Net/Modbus.Net.OPC/OpcDaConnector.cs
Normal file
42
Modbus.Net/Modbus.Net.OPC/OpcDaConnector.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Modbus.Net.OPC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opc DA连接实现
|
||||||
|
/// </summary>
|
||||||
|
public class OpcDaConnector : OpcConnector
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DA单例管理
|
||||||
|
/// </summary>
|
||||||
|
protected static Dictionary<string, OpcDaConnector> _instances = new Dictionary<string, OpcDaConnector>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Opc DA 服务地址</param>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
protected OpcDaConnector(string host, bool isRegexOn) : base(host, isRegexOn)
|
||||||
|
{
|
||||||
|
Client = new MyDaClient(new Uri(ConnectionToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据服务地址生成DA单例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Opc DA 服务地址</param>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
/// <returns>Opc DA 连接器实例</returns>
|
||||||
|
public static OpcDaConnector Instance(string host, bool isRegexOn)
|
||||||
|
{
|
||||||
|
if (!_instances.ContainsKey(host))
|
||||||
|
{
|
||||||
|
var connector = new OpcDaConnector(host, isRegexOn);
|
||||||
|
_instances.Add(host, connector);
|
||||||
|
}
|
||||||
|
return _instances[host];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Modbus.Net/Modbus.Net.OPC/OpcDaMachine.cs
Normal file
74
Modbus.Net/Modbus.Net.OPC/OpcDaMachine.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Modbus.Net.OPC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opc DA设备
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">设备Id类型</typeparam>
|
||||||
|
/// <typeparam name="TUnitKey">设备包含的地址的Id类型</typeparam>
|
||||||
|
public class OpcDaMachine<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 OpcDaMachine(TKey id, string connectionString, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, bool isRegexOn = false)
|
||||||
|
: base(id, getAddresses, keepConnect)
|
||||||
|
{
|
||||||
|
BaseUtility = new OpcDaUtility(connectionString, isRegexOn);
|
||||||
|
((OpcUtility)BaseUtility).GetSeperator +=
|
||||||
|
() => ((AddressFormaterOpc<TKey, TUnitKey>)AddressFormater).Seperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备的ID号</param>
|
||||||
|
/// <param name="connectionString">连接地址</param>
|
||||||
|
/// <param name="getAddresses">需要读写的数据</param>
|
||||||
|
public OpcDaMachine(TKey id, string connectionString, IEnumerable<AddressUnit<TUnitKey>> getAddresses)
|
||||||
|
: this(id, connectionString, getAddresses, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opc DA设备
|
||||||
|
/// </summary>
|
||||||
|
public class OpcDaMachine : 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 OpcDaMachine(string id, string connectionString, IEnumerable<AddressUnit> getAddresses, bool keepConnect, bool isRegexOn = false)
|
||||||
|
: base(id, getAddresses, keepConnect)
|
||||||
|
{
|
||||||
|
BaseUtility = new OpcDaUtility(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 OpcDaMachine(string id, string connectionString, IEnumerable<AddressUnit> getAddresses)
|
||||||
|
: this(id, connectionString, getAddresses, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Modbus.Net/Modbus.Net.OPC/OpcDaProtocal.cs
Normal file
38
Modbus.Net/Modbus.Net.OPC/OpcDaProtocal.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Modbus.Net.OPC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opc Da协议
|
||||||
|
/// </summary>
|
||||||
|
public class OpcDaProtocol : OpcProtocol
|
||||||
|
{
|
||||||
|
private readonly string _host;
|
||||||
|
|
||||||
|
private readonly bool _isRegexOn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Opc DA服务地址</param>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
public OpcDaProtocol(string host, bool isRegexOn)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_isRegexOn = isRegexOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接设备
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否连接成功</returns>
|
||||||
|
public override async Task<bool> ConnectAsync()
|
||||||
|
{
|
||||||
|
ProtocolLinker = new OpcDaProtocolLinker(_host, _isRegexOn);
|
||||||
|
if (!await ProtocolLinker.ConnectAsync())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Modbus.Net/Modbus.Net.OPC/OpcDaProtocalLinker.cs
Normal file
28
Modbus.Net/Modbus.Net.OPC/OpcDaProtocalLinker.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace Modbus.Net.OPC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opc Da协议连接器
|
||||||
|
/// </summary>
|
||||||
|
public class OpcDaProtocolLinker : OpcProtocolLinker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
public OpcDaProtocolLinker(bool isRegexOn) : this(ConfigurationManager.AppSettings["OpcDaHost"], isRegexOn)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Opc DA服务地址</param>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
public OpcDaProtocolLinker(string host, bool isRegexOn)
|
||||||
|
{
|
||||||
|
BaseConnector = OpcDaConnector.Instance(host, isRegexOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Modbus.Net/Modbus.Net.OPC/OpcDaUtility.cs
Normal file
18
Modbus.Net/Modbus.Net.OPC/OpcDaUtility.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Modbus.Net.OPC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opc Da协议Api入口
|
||||||
|
/// </summary>
|
||||||
|
public class OpcDaUtility : OpcUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionString">连接地址</param>
|
||||||
|
/// <param name="isRegexOn">是否开启正则匹配</param>
|
||||||
|
public OpcDaUtility(string connectionString, bool isRegexOn = false) : base(connectionString)
|
||||||
|
{
|
||||||
|
Wrapper = new OpcDaProtocol(ConnectionString, isRegexOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ namespace Modbus.Net.Siemens
|
|||||||
var head = splitString[0];
|
var head = splitString[0];
|
||||||
var tail = splitString[1];
|
var tail = splitString[1];
|
||||||
string sub;
|
string sub;
|
||||||
if (tail.Contains('.'))
|
if (tail.Contains("."))
|
||||||
{
|
{
|
||||||
var splitString2 = tail.Split('.');
|
var splitString2 = tail.Split('.');
|
||||||
sub = splitString2[1];
|
sub = splitString2[1];
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Siemens</AssemblyName>
|
<AssemblyName>Modbus.Net.Siemens</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Siemens</RootNamespace>
|
<RootNamespace>Modbus.Net.Siemens</RootNamespace>
|
||||||
<PackageId>Modbus.Net.Siemens</PackageId>
|
<PackageId>Modbus.Net.Siemens</PackageId>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using FastEnumUtility;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,7 +19,7 @@ namespace Modbus.Net.Siemens
|
|||||||
public SiemensPpiProtocolLinker(string com, int slaveAddress)
|
public SiemensPpiProtocolLinker(string com, int slaveAddress)
|
||||||
: base(com, slaveAddress, parity:
|
: base(com, slaveAddress, parity:
|
||||||
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
|
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
|
||||||
? Enum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
|
? FastEnum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
|
||||||
: null
|
: null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyType", "..\Samples\AnyTy
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.OPC", "Modbus.Net.OPC\Modbus.Net.OPC.csproj", "{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.OPC", "Modbus.Net.OPC\Modbus.Net.OPC.csproj", "{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h-opc", "..\Libraries\h-opc\h-opc.csproj", "{347D0027-45F6-48C9-A917-1B1DF6C66DC5}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrossLamp", "..\Samples\CrossLamp\CrossLamp.csproj", "{AA3A42D2-0502-41D3-929A-BAB729DF07D6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrossLamp", "..\Samples\CrossLamp\CrossLamp.csproj", "{AA3A42D2-0502-41D3-929A-BAB729DF07D6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}"
|
||||||
@@ -74,10 +72,6 @@ Global
|
|||||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Modbus.Net
|
|||||||
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder()
|
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
.AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true)
|
.AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true)
|
||||||
.AddJsonFile("appsettings.json", optional:false, reloadOnChange: true)
|
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
|
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ namespace Modbus.Net
|
|||||||
/// <returns>元素的值</returns>
|
/// <returns>元素的值</returns>
|
||||||
public static string? GetValue(string path, string key)
|
public static string? GetValue(string path, string key)
|
||||||
{
|
{
|
||||||
var split = path.Split(":");
|
var split = path.Split(':');
|
||||||
string? ans = null;
|
string? ans = null;
|
||||||
while (split.Length > 0)
|
while (split.Length > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using FastEnumUtility;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -66,7 +67,7 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
case "endian":
|
case "endian":
|
||||||
{
|
{
|
||||||
paramsSet.Add(Enum.Parse<Endian>(dic["endian"]));
|
paramsSet.Add(FastEnum.Parse<Endian>(dic["endian"]));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
65
Modbus.Net/Modbus.Net/Helper/EnumHelper.cs
Normal file
65
Modbus.Net/Modbus.Net/Helper/EnumHelper.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Modbus.Net
|
||||||
|
{
|
||||||
|
#if NET462
|
||||||
|
#pragma warning disable 1591
|
||||||
|
public static partial class EnumearbleExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
|
||||||
|
{
|
||||||
|
if (null == source)
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
if (count < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
|
||||||
|
if (0 == count)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
// Optimization (see JonasH's comment)
|
||||||
|
if (source is ICollection<T>)
|
||||||
|
{
|
||||||
|
foreach (T item in source.Skip(((ICollection<T>)source).Count - count))
|
||||||
|
yield return item;
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source is IReadOnlyCollection<T>)
|
||||||
|
{
|
||||||
|
foreach (T item in source.Skip(((IReadOnlyCollection<T>)source).Count - count))
|
||||||
|
yield return item;
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General case, we have to enumerate source
|
||||||
|
Queue<T> result = new Queue<T>();
|
||||||
|
|
||||||
|
foreach (T item in source)
|
||||||
|
{
|
||||||
|
if (result.Count == count)
|
||||||
|
result.Dequeue();
|
||||||
|
|
||||||
|
result.Enqueue(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (T item in result)
|
||||||
|
yield return result.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<T> Append<T>(this IEnumerable<T> collection, T item)
|
||||||
|
{
|
||||||
|
if (collection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("Collection should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection.Concat(Enumerable.Repeat(item, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using FastEnumUtility;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
|
||||||
namespace Modbus.Net
|
namespace Modbus.Net
|
||||||
@@ -63,11 +63,11 @@ namespace Modbus.Net
|
|||||||
protected ComProtocolLinker(string com, int slaveAddress, BaudRate? baudRate = null, Parity? parity = null, StopBits? stopBits = null, DataBits? dataBits = null, Handshake? handshake = null,
|
protected ComProtocolLinker(string com, int slaveAddress, BaudRate? baudRate = null, Parity? parity = null, StopBits? stopBits = null, DataBits? dataBits = null, Handshake? handshake = null,
|
||||||
int? connectionTimeout = null, bool? isFullDuplex = null)
|
int? connectionTimeout = null, bool? isFullDuplex = null)
|
||||||
{
|
{
|
||||||
baudRate = Enum.Parse<BaudRate>(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate"));
|
baudRate = FastEnum.Parse<BaudRate>(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate"));
|
||||||
parity = Enum.Parse<Parity>(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity"));
|
parity = FastEnum.Parse<Parity>(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity"));
|
||||||
stopBits = Enum.Parse<StopBits>(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits"));
|
stopBits = FastEnum.Parse<StopBits>(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits"));
|
||||||
dataBits = Enum.Parse<DataBits>(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits"));
|
dataBits = FastEnum.Parse<DataBits>(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits"));
|
||||||
handshake = Enum.Parse<Handshake>(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake"));
|
handshake = FastEnum.Parse<Handshake>(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake"));
|
||||||
connectionTimeout = int.Parse(connectionTimeout != null ? connectionTimeout.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "ConnectionTimeout"));
|
connectionTimeout = int.Parse(connectionTimeout != null ? connectionTimeout.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "ConnectionTimeout"));
|
||||||
isFullDuplex = bool.Parse(isFullDuplex != null ? isFullDuplex.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "FullDuplex"));
|
isFullDuplex = bool.Parse(isFullDuplex != null ? isFullDuplex.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "FullDuplex"));
|
||||||
BaseConnector = new ComConnector(com + ":" + slaveAddress, baudRate.Value, parity.Value, stopBits.Value, dataBits.Value, handshake.Value, connectionTimeout.Value, isFullDuplex.Value);
|
BaseConnector = new ComConnector(com + ":" + slaveAddress, baudRate.Value, parity.Value, stopBits.Value, dataBits.Value, handshake.Value, connectionTimeout.Value, isFullDuplex.Value);
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ namespace Modbus.Net
|
|||||||
/// <returns>返回的数据</returns>
|
/// <returns>返回的数据</returns>
|
||||||
public static Task<ReturnStruct<T>> InvokeGet<TMachineMethod, T>(this IMachineMethod machineMethod, object[] parameters) where TMachineMethod : IMachineMethod
|
public static Task<ReturnStruct<T>> InvokeGet<TMachineMethod, T>(this IMachineMethod machineMethod, object[] parameters) where TMachineMethod : IMachineMethod
|
||||||
{
|
{
|
||||||
if (typeof(TMachineMethod).Name[..14] != "IMachineMethod")
|
if (typeof(TMachineMethod).Name.Substring(0, 14) != "IMachineMethod")
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("IMachineMethod type name not begin with IMachineMethod");
|
throw new NotSupportedException("IMachineMethod type name not begin with IMachineMethod");
|
||||||
}
|
}
|
||||||
var functionName = "Get" + typeof(TMachineMethod).Name[14..] + "Async";
|
var functionName = "Get" + typeof(TMachineMethod).Name.Substring(14) + "Async";
|
||||||
return InvokeGet<T>(machineMethod, functionName, parameters);
|
return InvokeGet<T>(machineMethod, functionName, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +54,11 @@ namespace Modbus.Net
|
|||||||
/// <returns>设置是否成功</returns>
|
/// <returns>设置是否成功</returns>
|
||||||
public static Task<ReturnStruct<bool>> InvokeSet<TMachineMethod, T>(this IMachineMethod machineMethod, object[] parameters, T datas) where TMachineMethod : IMachineMethod
|
public static Task<ReturnStruct<bool>> InvokeSet<TMachineMethod, T>(this IMachineMethod machineMethod, object[] parameters, T datas) where TMachineMethod : IMachineMethod
|
||||||
{
|
{
|
||||||
if (typeof(TMachineMethod).Name[..14] != "IMachineMethod")
|
if (typeof(TMachineMethod).Name.Substring(0, 14) != "IMachineMethod")
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("IMachineMethod type name not begin with IMachineMethod");
|
throw new NotSupportedException("IMachineMethod type name not begin with IMachineMethod");
|
||||||
}
|
}
|
||||||
var functionName = "Set" + typeof(TMachineMethod).Name[14..] + "Async";
|
var functionName = "Set" + typeof(TMachineMethod).Name.Substring(14) + "Async";
|
||||||
return InvokeSet(machineMethod, functionName, parameters, datas);
|
return InvokeSet(machineMethod, functionName, parameters, datas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net</AssemblyName>
|
<AssemblyName>Modbus.Net</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net</RootNamespace>
|
<RootNamespace>Modbus.Net</RootNamespace>
|
||||||
<PackageId>Modbus.Net</PackageId>
|
<PackageId>Modbus.Net</PackageId>
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
|
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
|
||||||
|
<PackageReference Include="FastEnum" Version="1.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ namespace Modbus.Net
|
|||||||
/// <returns>返回的数据</returns>
|
/// <returns>返回的数据</returns>
|
||||||
public static Task<ReturnStruct<T>> InvokeGet<TUtilityMethod, T>(this IUtilityMethod utilityMethod, object[] parameters) where TUtilityMethod : IUtilityMethod
|
public static Task<ReturnStruct<T>> InvokeGet<TUtilityMethod, T>(this IUtilityMethod utilityMethod, object[] parameters) where TUtilityMethod : IUtilityMethod
|
||||||
{
|
{
|
||||||
if (typeof(TUtilityMethod).Name[..14] != "IUtilityMethod")
|
if (typeof(TUtilityMethod).Name.Substring(0, 14) != "IUtilityMethod")
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("IUtilityMethod type name not begin with IUtilityMethod");
|
throw new NotSupportedException("IUtilityMethod type name not begin with IUtilityMethod");
|
||||||
}
|
}
|
||||||
var functionName = "Get" + typeof(TUtilityMethod).Name[14..] + "Async";
|
var functionName = "Get" + typeof(TUtilityMethod).Name.Substring(14) + "Async";
|
||||||
return InvokeGet<T>(utilityMethod, functionName, parameters);
|
return InvokeGet<T>(utilityMethod, functionName, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +53,11 @@ namespace Modbus.Net
|
|||||||
/// <returns>设置是否成功</returns>
|
/// <returns>设置是否成功</returns>
|
||||||
public static Task<ReturnStruct<bool>> InvokeSet<TUtilityMethod, T>(this IUtilityMethod utilityMethod, object[] parameters, T datas) where TUtilityMethod : IUtilityMethod
|
public static Task<ReturnStruct<bool>> InvokeSet<TUtilityMethod, T>(this IUtilityMethod utilityMethod, object[] parameters, T datas) where TUtilityMethod : IUtilityMethod
|
||||||
{
|
{
|
||||||
if (typeof(TUtilityMethod).Name[..14] != "IUtilityMethod")
|
if (typeof(TUtilityMethod).Name.Substring(0, 14) != "IUtilityMethod")
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("IUtilityMethod type name not begin with IUtilityMethod");
|
throw new NotSupportedException("IUtilityMethod type name not begin with IUtilityMethod");
|
||||||
}
|
}
|
||||||
var functionName = "Set" + typeof(TUtilityMethod).Name[14..] + "Async";
|
var functionName = "Set" + typeof(TUtilityMethod).Name.Substring(14) + "Async";
|
||||||
return InvokeSet(utilityMethod, functionName, parameters, datas);
|
return InvokeSet(utilityMethod, functionName, parameters, datas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user