Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

Commit

Permalink
* background worker
Browse files Browse the repository at this point in the history
  for OPC UA client is
  difficult ..
  • Loading branch information
festo-i40 committed Jan 9, 2024
1 parent 1670c15 commit c7a6699
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 2 deletions.
324 changes: 324 additions & 0 deletions src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
/*
Copyright (c) 2018-2023 Festo SE & Co. KG <https://www.festo.com/net/de_de/Forms/web/contact_international>
Author: Michael Hoffmeister
This source code is licensed under the Apache License 2.0 (see LICENSE.txt).
This source code may use other Open Source software components (see LICENSE.txt).
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;

// Note: this is a DUPLICATE from WpfMtpControl

namespace AasxPluginAssetInterfaceDescription
{
public enum AasOpcUaClientStatus
{
ErrorCreateApplication = 0x11,
ErrorDiscoverEndpoints = 0x12,
ErrorCreateSession = 0x13,
ErrorBrowseNamespace = 0x14,
ErrorCreateSubscription = 0x15,
ErrorMonitoredItem = 0x16,
ErrorAddSubscription = 0x17,
ErrorRunning = 0x18,
ErrorReadConfigFile = 0x19,
ErrorNoKeepAlive = 0x30,
ErrorInvalidCommandLine = 0x100,
Running = 0x1000,
Quitting = 0x8000,
Quitted = 0x8001
};

public class AasOpcUaClient
{
const int ReconnectPeriod = 10;
Session session;
SessionReconnectHandler reconnectHandler;
string endpointURL;
static bool autoAccept = true;
static AasOpcUaClientStatus ClientStatus;
string userName;
string password;

public AasOpcUaClient(string _endpointURL, bool _autoAccept,
string _userName, string _password)
{
endpointURL = _endpointURL;
autoAccept = _autoAccept;
userName = _userName;
password = _password;
}

private BackgroundWorker worker = null;

public void Run()
{
// start server as a worker (will start in the background)
// ReSharper disable once LocalVariableHidesMember
var worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s1, e1) =>
{
try
{
while (true)
{
StartClientAsync().Wait();
// keep running
if (ClientStatus == AasOpcUaClientStatus.Running)
while (true)
Thread.Sleep(200);
// restart
Thread.Sleep(200);
}
}
catch (Exception ex)
{
AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex);
}
};
worker.RunWorkerCompleted += (s1, e1) =>
{
;
};
worker.RunWorkerAsync();
}

public void Cancel()
{
if (worker != null && worker.IsBusy)
try
{
worker.CancelAsync();
worker.Dispose();
}
catch (Exception ex)
{
AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex);
}
}

public void Close()
{
if (session == null)
return;
session.Close(1);
session = null;
}

public AasOpcUaClientStatus StatusCode { get => ClientStatus; }

public async Task StartClientAsync()
{
Console.WriteLine("1 - Create an Application Configuration.");
ClientStatus = AasOpcUaClientStatus.ErrorCreateApplication;

ApplicationInstance application = new ApplicationInstance
{
ApplicationName = "UA Core Sample Client",
ApplicationType = ApplicationType.Client,
ConfigSectionName = Utils.IsRunningOnMono() ? "Opc.Ua.MonoSampleClient" : "Opc.Ua.SampleClient"
};

// load the application configuration.
ApplicationConfiguration config = null;
try
{
config = await application.LoadApplicationConfiguration(false);
}
catch (Exception ex)
{
AdminShellNS.LogInternally.That.Error(ex, "Error reading the config file");
ClientStatus = AasOpcUaClientStatus.ErrorReadConfigFile;
return;
}

// check the application certificate.
bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);
if (!haveAppCertificate)
{
throw new Exception("Application instance certificate invalid!");
}

// ReSharper disable HeuristicUnreachableCode
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (haveAppCertificate)
{
config.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(
config.SecurityConfiguration.ApplicationCertificate.Certificate);

if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
{
autoAccept = true;
}
// ReSharper disable once RedundantDelegateCreation
config.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(
CertificateValidator_CertificateValidation);
}
else
{
Console.WriteLine(" WARN: missing application certificate, using unsecure connection.");
}
// ReSharper enable HeuristicUnreachableCode

Console.WriteLine("2 - Discover endpoints of {0}.", endpointURL);
ClientStatus = AasOpcUaClientStatus.ErrorDiscoverEndpoints;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
var selectedEndpoint = CoreClientUtils.SelectEndpoint(endpointURL, haveAppCertificate, 15000);
Console.WriteLine(" Selected endpoint uses: {0}",
selectedEndpoint.SecurityPolicyUri.Substring(selectedEndpoint.SecurityPolicyUri.LastIndexOf('#') + 1));

Console.WriteLine("3 - Create a session with OPC UA server.");
ClientStatus = AasOpcUaClientStatus.ErrorCreateSession;
var endpointConfiguration = EndpointConfiguration.Create(config);
var endpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);

session = await Session.Create(config, endpoint, false, "OPC UA Console Client", 60000,
new UserIdentity(userName, password), null);

// register keep alive handler
session.KeepAlive += Client_KeepAlive;

// ok
ClientStatus = AasOpcUaClientStatus.Running;
}

private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
{
if (e.Status != null && ServiceResult.IsNotGood(e.Status))
{
Console.WriteLine("{0} {1}/{2}", e.Status, sender.OutstandingRequestCount, sender.DefunctRequestCount);

if (reconnectHandler == null)
{
Console.WriteLine("--- RECONNECTING ---");
reconnectHandler = new SessionReconnectHandler();
reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
}
}
}

private void Client_ReconnectComplete(object sender, EventArgs e)
{
// ignore callbacks from discarded objects.
if (!Object.ReferenceEquals(sender, reconnectHandler))
{
return;
}

if (reconnectHandler != null)
{
session = reconnectHandler.Session;
reconnectHandler.Dispose();
}

reconnectHandler = null;

Console.WriteLine("--- RECONNECTED ---");
}

private static void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e)
{
// ReSharper disable once UnusedVariable
foreach (var value in item.DequeueValues())
{
//// Console.WriteLine("{0}: {1}, {2}, {3}", item.DisplayName, value.Value,
//// value.SourceTimestamp, value.StatusCode);
}
}

private static void CertificateValidator_CertificateValidation(
CertificateValidator validator, CertificateValidationEventArgs e)
{
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
{
e.Accept = autoAccept;
if (autoAccept)
{
Console.WriteLine("Accepted Certificate: {0}", e.Certificate.Subject);
}
else
{
Console.WriteLine("Rejected Certificate: {0}", e.Certificate.Subject);
}
}
}

public NodeId CreateNodeId(string nodeName, int index)
{
return new NodeId(nodeName, (ushort)index);
}

private Dictionary<string, ushort> nsDict = null;

public NodeId CreateNodeId(string nodeName, string ns)
{
if (session == null || session.NamespaceUris == null)
return null;

// build up?
if (nsDict == null)
{
nsDict = new Dictionary<string, ushort>();
for (ushort i = 0; i < session.NamespaceUris.Count; i++)
nsDict.Add(session.NamespaceUris.GetString(i), i);
}

// find?
if (nsDict == null || !nsDict.ContainsKey(ns))
return null;

return new NodeId(nodeName, nsDict[ns]);
}

public string ReadSubmodelElementValueAsString(string nodeName, int index)
{
if (session == null)
return "";

NodeId node = new NodeId(nodeName, (ushort)index);
return (session.ReadValue(node).ToString());
}

public DataValue ReadNodeId(NodeId nid)
{
if (session == null || nid == null || !session.Connected)
return null;
return (session.ReadValue(nid));
}

public void SubscribeNodeIds(NodeId[] nids, MonitoredItemNotificationEventHandler handler,
int publishingInteral = 1000)
{
if (session == null || nids == null || !session.Connected || handler == null)
return;

var subscription = new Subscription(session.DefaultSubscription)
{ PublishingInterval = publishingInteral };

foreach (var nid in nids)
{
var mi = new MonitoredItem(subscription.DefaultItem);
mi.StartNodeId = nid;
mi.Notification += handler;
subscription.AddItem(mi);
}

session.AddSubscription(subscription);
subscription.Create();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<None Remove="Resources\logo-http.png" />
<None Remove="Resources\logo-modbus.png" />
<None Remove="Resources\logo-mqtt.png" />
<None Remove="Resources\logo-opc-ua.png" />
</ItemGroup>
<ItemGroup>
<None Update="AasxPluginAssetInterfaceDesc.options.json">
Expand All @@ -31,6 +32,7 @@
<EmbeddedResource Include="Resources\logo-http.png" />
<EmbeddedResource Include="Resources\logo-modbus.png" />
<EmbeddedResource Include="Resources\logo-mqtt.png" />
<EmbeddedResource Include="Resources\logo-opc-ua.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AasxIntegrationBaseGdi\AasxIntegrationBaseGdi.csproj" />
Expand All @@ -48,5 +50,6 @@
<PackageReference Include="FluentModbus" Version="5.0.3" />
<PackageReference Include="MQTTnet" Version="4.1.1.318" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.370.12" />
</ItemGroup>
</Project>
Loading

0 comments on commit c7a6699

Please sign in to comment.