502 lines
19 KiB
C#
502 lines
19 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
|
|
namespace Quickstarts
|
|
{
|
|
/// <summary>
|
|
/// OPC UA Client with examples of basic functionality.
|
|
/// </summary>
|
|
class UAClient
|
|
{
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Initializes a new instance of the UAClient class.
|
|
/// </summary>
|
|
public UAClient(ApplicationConfiguration configuration, TextWriter writer, Action<IList, IList> validateResponse)
|
|
{
|
|
m_validateResponse = validateResponse;
|
|
m_output = writer;
|
|
m_configuration = configuration;
|
|
m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;
|
|
}
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
/// <summary>
|
|
/// Gets the client session.
|
|
/// </summary>
|
|
public Session Session => m_session;
|
|
|
|
/// <summary>
|
|
/// Auto accept untrusted certificates.
|
|
/// </summary>
|
|
public bool AutoAccept { get; set; } = false;
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// Creates a session with the UA server
|
|
/// </summary>
|
|
public async Task<bool> ConnectAsync(string serverUrl)
|
|
{
|
|
if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl));
|
|
|
|
try
|
|
{
|
|
if (m_session != null && m_session.Connected == true)
|
|
{
|
|
m_output.WriteLine("Session already connected!");
|
|
}
|
|
else
|
|
{
|
|
m_output.WriteLine("Connecting to... {0}", serverUrl);
|
|
|
|
// Get the endpoint by connecting to server's discovery endpoint.
|
|
// Try to find the first endopint with security.
|
|
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, true);
|
|
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
|
|
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
|
|
|
|
// Create the session
|
|
Session session = await Session.Create(
|
|
m_configuration,
|
|
endpoint,
|
|
false,
|
|
false,
|
|
m_configuration.ApplicationName,
|
|
30 * 60 * 1000,
|
|
new UserIdentity(),
|
|
null
|
|
);
|
|
|
|
// Assign the created session
|
|
if (session != null && session.Connected)
|
|
{
|
|
m_session = session;
|
|
}
|
|
|
|
// Session created successfully.
|
|
m_output.WriteLine("New Session Created with SessionName = {0}", m_session.SessionName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log Error
|
|
m_output.WriteLine("Create Session Error : {0}", ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects the session.
|
|
/// </summary>
|
|
public void Disconnect()
|
|
{
|
|
try
|
|
{
|
|
if (m_session != null)
|
|
{
|
|
m_output.WriteLine("Disconnecting...");
|
|
|
|
m_session.Close();
|
|
m_session.Dispose();
|
|
m_session = null;
|
|
|
|
// Log Session Disconnected event
|
|
m_output.WriteLine("Session Disconnected.");
|
|
}
|
|
else
|
|
{
|
|
m_output.WriteLine("Session not created!");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log Error
|
|
m_output.WriteLine($"Disconnect Error : {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a list of nodes from Server
|
|
/// </summary>
|
|
public void ReadNodes()
|
|
{
|
|
if (m_session == null || m_session.Connected == false)
|
|
{
|
|
m_output.WriteLine("Session not connected!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
#region Read a node by calling the Read Service
|
|
|
|
// build a list of nodes to be read
|
|
ReadValueIdCollection nodesToRead = new ReadValueIdCollection()
|
|
{
|
|
// Value of ServerStatus
|
|
new ReadValueId() { NodeId = Variables.Server_ServerStatus, AttributeId = Attributes.Value },
|
|
// BrowseName of ServerStatus_StartTime
|
|
new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.BrowseName },
|
|
// Value of ServerStatus_StartTime
|
|
new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.Value }
|
|
};
|
|
|
|
// Read the node attributes
|
|
m_output.WriteLine("Reading nodes...");
|
|
|
|
// Call Read Service
|
|
m_session.Read(
|
|
null,
|
|
0,
|
|
TimestampsToReturn.Both,
|
|
nodesToRead,
|
|
out DataValueCollection resultsValues,
|
|
out DiagnosticInfoCollection diagnosticInfos);
|
|
|
|
// Validate the results
|
|
m_validateResponse(resultsValues, nodesToRead);
|
|
|
|
// Display the results.
|
|
foreach (DataValue result in resultsValues)
|
|
{
|
|
m_output.WriteLine("Read Value = {0} , StatusCode = {1}", result.Value, result.StatusCode);
|
|
}
|
|
#endregion
|
|
|
|
#region Read the Value attribute of a node by calling the Session.ReadValue method
|
|
// Read Server NamespaceArray
|
|
m_output.WriteLine("Reading Value of NamespaceArray node...");
|
|
DataValue namespaceArray = m_session.ReadValue(Variables.Server_NamespaceArray);
|
|
// Display the result
|
|
m_output.WriteLine($"NamespaceArray Value = {namespaceArray}");
|
|
#endregion
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log Error
|
|
m_output.WriteLine($"Read Nodes Error : {ex.Message}.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a list of nodes to the Server
|
|
/// </summary>
|
|
public void WriteNodes()
|
|
{
|
|
if (m_session == null || m_session.Connected == false)
|
|
{
|
|
m_output.WriteLine("Session not connected!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Write the configured nodes
|
|
WriteValueCollection nodesToWrite = new WriteValueCollection();
|
|
|
|
// Int32 Node - Objects\CTT\Scalar\Scalar_Static\Int32
|
|
WriteValue intWriteVal = new WriteValue();
|
|
intWriteVal.NodeId = new NodeId("ns=2;s=Scalar_Static_Int32");
|
|
intWriteVal.AttributeId = Attributes.Value;
|
|
intWriteVal.Value = new DataValue();
|
|
intWriteVal.Value.Value = (int)100;
|
|
nodesToWrite.Add(intWriteVal);
|
|
|
|
// Float Node - Objects\CTT\Scalar\Scalar_Static\Float
|
|
WriteValue floatWriteVal = new WriteValue();
|
|
floatWriteVal.NodeId = new NodeId("ns=2;s=Scalar_Static_Float");
|
|
floatWriteVal.AttributeId = Attributes.Value;
|
|
floatWriteVal.Value = new DataValue();
|
|
floatWriteVal.Value.Value = (float)100.5;
|
|
nodesToWrite.Add(floatWriteVal);
|
|
|
|
// String Node - Objects\CTT\Scalar\Scalar_Static\String
|
|
WriteValue stringWriteVal = new WriteValue();
|
|
stringWriteVal.NodeId = new NodeId("ns=2;s=Scalar_Static_String");
|
|
stringWriteVal.AttributeId = Attributes.Value;
|
|
stringWriteVal.Value = new DataValue();
|
|
stringWriteVal.Value.Value = "String Test";
|
|
nodesToWrite.Add(stringWriteVal);
|
|
|
|
// Write the node attributes
|
|
StatusCodeCollection results = null;
|
|
DiagnosticInfoCollection diagnosticInfos;
|
|
m_output.WriteLine("Writing nodes...");
|
|
|
|
// Call Write Service
|
|
m_session.Write(null,
|
|
nodesToWrite,
|
|
out results,
|
|
out diagnosticInfos);
|
|
|
|
// Validate the response
|
|
m_validateResponse(results, nodesToWrite);
|
|
|
|
// Display the results.
|
|
m_output.WriteLine("Write Results :");
|
|
|
|
foreach (StatusCode writeResult in results)
|
|
{
|
|
m_output.WriteLine(" {0}", writeResult);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log Error
|
|
m_output.WriteLine($"Write Nodes Error : {ex.Message}.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Browse Server nodes
|
|
/// </summary>
|
|
public void Browse()
|
|
{
|
|
if (m_session == null || m_session.Connected == false)
|
|
{
|
|
m_output.WriteLine("Session not connected!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Create a Browser object
|
|
Browser browser = new Browser(m_session);
|
|
|
|
// Set browse parameters
|
|
browser.BrowseDirection = BrowseDirection.Forward;
|
|
browser.NodeClassMask = (int)NodeClass.Object | (int)NodeClass.Variable;
|
|
browser.ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences;
|
|
|
|
NodeId nodeToBrowse = ObjectIds.Server;
|
|
|
|
// Call Browse service
|
|
m_output.WriteLine("Browsing {0} node...", nodeToBrowse);
|
|
ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse);
|
|
|
|
// Display the results
|
|
m_output.WriteLine("Browse returned {0} results:", browseResults.Count);
|
|
|
|
foreach (ReferenceDescription result in browseResults)
|
|
{
|
|
m_output.WriteLine(" DisplayName = {0}, NodeClass = {1}", result.DisplayName.Text, result.NodeClass);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log Error
|
|
m_output.WriteLine($"Browse Error : {ex.Message}.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UA method
|
|
/// </summary>
|
|
public void CallMethod()
|
|
{
|
|
if (m_session == null || m_session.Connected == false)
|
|
{
|
|
m_output.WriteLine("Session not connected!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Define the UA Method to call
|
|
// Parent node - Objects\CTT\Methods
|
|
// Method node - Objects\CTT\Methods\Add
|
|
NodeId objectId = new NodeId("ns=2;s=Methods");
|
|
NodeId methodId = new NodeId("ns=2;s=Methods_Add");
|
|
|
|
// Define the method parameters
|
|
// Input argument requires a Float and an UInt32 value
|
|
object[] inputArguments = new object[] { (float)10.5, (uint)10 };
|
|
IList<object> outputArguments = null;
|
|
|
|
// Invoke Call service
|
|
m_output.WriteLine("Calling UAMethod for node {0} ...", methodId);
|
|
outputArguments = m_session.Call(objectId, methodId, inputArguments);
|
|
|
|
// Display results
|
|
m_output.WriteLine("Method call returned {0} output argument(s):", outputArguments.Count);
|
|
|
|
foreach (var outputArgument in outputArguments)
|
|
{
|
|
m_output.WriteLine(" OutputValue = {0}", outputArgument.ToString());
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_output.WriteLine("Method call error: {0}", ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Subscription and MonitoredItems for DataChanges
|
|
/// </summary>
|
|
public void SubscribeToDataChanges()
|
|
{
|
|
if (m_session == null || m_session.Connected == false)
|
|
{
|
|
m_output.WriteLine("Session not connected!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Create a subscription for receiving data change notifications
|
|
|
|
// Define Subscription parameters
|
|
Subscription subscription = new Subscription(m_session.DefaultSubscription);
|
|
|
|
subscription.DisplayName = "Console ReferenceClient Subscription";
|
|
subscription.PublishingEnabled = true;
|
|
subscription.PublishingInterval = 1000;
|
|
|
|
m_session.AddSubscription(subscription);
|
|
|
|
// Create the subscription on Server side
|
|
subscription.Create();
|
|
m_output.WriteLine("New Subscription created with SubscriptionId = {0}.", subscription.Id);
|
|
|
|
// Create MonitoredItems for data changes (Reference Server)
|
|
|
|
MonitoredItem intMonitoredItem = new MonitoredItem(subscription.DefaultItem);
|
|
// Int32 Node - Objects\CTT\Scalar\Simulation\Int32
|
|
intMonitoredItem.StartNodeId = new NodeId("ns=2;s=Scalar_Simulation_Int32");
|
|
intMonitoredItem.AttributeId = Attributes.Value;
|
|
intMonitoredItem.DisplayName = "Int32 Variable";
|
|
intMonitoredItem.SamplingInterval = 1000;
|
|
intMonitoredItem.Notification += OnMonitoredItemNotification;
|
|
|
|
subscription.AddItem(intMonitoredItem);
|
|
|
|
MonitoredItem floatMonitoredItem = new MonitoredItem(subscription.DefaultItem);
|
|
// Float Node - Objects\CTT\Scalar\Simulation\Float
|
|
floatMonitoredItem.StartNodeId = new NodeId("ns=2;s=Scalar_Simulation_Float");
|
|
floatMonitoredItem.AttributeId = Attributes.Value;
|
|
floatMonitoredItem.DisplayName = "Float Variable";
|
|
floatMonitoredItem.SamplingInterval = 1000;
|
|
floatMonitoredItem.Notification += OnMonitoredItemNotification;
|
|
|
|
subscription.AddItem(floatMonitoredItem);
|
|
|
|
MonitoredItem stringMonitoredItem = new MonitoredItem(subscription.DefaultItem);
|
|
// String Node - Objects\CTT\Scalar\Simulation\String
|
|
stringMonitoredItem.StartNodeId = new NodeId("ns=2;s=Scalar_Simulation_String");
|
|
stringMonitoredItem.AttributeId = Attributes.Value;
|
|
stringMonitoredItem.DisplayName = "String Variable";
|
|
stringMonitoredItem.SamplingInterval = 1000;
|
|
stringMonitoredItem.Notification += OnMonitoredItemNotification;
|
|
|
|
subscription.AddItem(stringMonitoredItem);
|
|
|
|
// Create the monitored items on Server side
|
|
subscription.ApplyChanges();
|
|
m_output.WriteLine("MonitoredItems created for SubscriptionId = {0}.", subscription.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_output.WriteLine("Subscribe error: {0}", ex.Message);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
/// <summary>
|
|
/// Handle DataChange notifications from Server
|
|
/// </summary>
|
|
private void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Log MonitoredItem Notification event
|
|
MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
|
|
m_output.WriteLine("Notification Received for Variable \"{0}\" and Value = {1}.", monitoredItem.DisplayName, notification.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_output.WriteLine("OnMonitoredItemNotification error: {0}", ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the certificate validation event.
|
|
/// This event is triggered every time an untrusted certificate is received from the server.
|
|
/// </summary>
|
|
private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e)
|
|
{
|
|
bool certificateAccepted = false;
|
|
|
|
// ****
|
|
// Implement a custom logic to decide if the certificate should be
|
|
// accepted or not and set certificateAccepted flag accordingly.
|
|
// The certificate can be retrieved from the e.Certificate field
|
|
// ***
|
|
|
|
ServiceResult error = e.Error;
|
|
m_output.WriteLine(error);
|
|
if (error.StatusCode == StatusCodes.BadCertificateUntrusted && AutoAccept)
|
|
{
|
|
certificateAccepted = true;
|
|
}
|
|
|
|
if (certificateAccepted)
|
|
{
|
|
m_output.WriteLine("Untrusted Certificate accepted. Subject = {0}", e.Certificate.Subject);
|
|
e.Accept = true;
|
|
}
|
|
else
|
|
{
|
|
m_output.WriteLine("Untrusted Certificate rejected. Subject = {0}", e.Certificate.Subject);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
private ApplicationConfiguration m_configuration;
|
|
private Session m_session;
|
|
private readonly TextWriter m_output;
|
|
private readonly Action<IList, IList> m_validateResponse;
|
|
#endregion
|
|
}
|
|
}
|