Samples Reappend

This commit is contained in:
luosheng
2023-02-13 19:58:15 +08:00
parent 4e70553bfc
commit eb4a7cffd6
471 changed files with 151790 additions and 6 deletions

View 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;
}
}
}
}

View 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;
}
}
}
}

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

View 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;
}
}
}

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

View 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
}
}

View 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
}
}

View 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; }
}
}

View 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
View 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;
}
}

View 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 clients 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
View 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;
}
}
}

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

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

View 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;
}
}
}

View 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; }
}
}

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

View 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;
}
}
}
}

View 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();
}
}
}

View 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
}

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

View 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];
}
}
}

View 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)
{
}
}
}

View 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;
}
}
}

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

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

View 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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
Modbus.Net.OPC
===================
[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.OPC.svg)](https://www.nuget.org/packages/Modbus.Net.OPC/)
OPC Implementation of Modbus.Net
Doc has been moved to wiki.

View File

@@ -20,13 +20,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Tests", "..\Test
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}"
EndProject 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 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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.PersistedTests", "..\Tests\Modbus.Net.PersistedTests\Modbus.Net.PersistedTests.csproj", "{4B946C56-D09F-4EEB-BE88-4A2C97815A77}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.PersistedTests", "..\Tests\Modbus.Net.PersistedTests\Modbus.Net.PersistedTests.csproj", "{4B946C56-D09F-4EEB-BE88-4A2C97815A77}"
EndProject 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.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.ActiveCfg = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -75,6 +99,8 @@ Global
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {22A35CA8-CDCF-416D-BA84-08C933B4A3DE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
{4B946C56-D09F-4EEB-BE88-4A2C97815A77} = {D8DD32FC-CF39-4A1A-8FBF-9E82C5278C34} {4B946C56-D09F-4EEB-BE88-4A2C97815A77} = {D8DD32FC-CF39-4A1A-8FBF-9E82C5278C34}
{1857DA63-3335-428F-84D8-1FA4F8178643} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5} SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5}

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More