From c7a6699ae08d4874f5f10c7ccc8bd3dfaeb23284 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Tue, 9 Jan 2024 22:00:57 +0100 Subject: [PATCH] * background worker for OPC UA client is difficult .. --- .../AasOpcUaClient.cs | 324 ++++++++++++++++++ .../AasxPluginAssetInterfaceDesc.csproj | 3 + .../AidInterfaceStatus.cs | 21 +- .../AidOpcUaConnection.cs | 181 ++++++++++ .../AssetInterfaceAnyUiControl.cs | 5 + .../Resources/logo-opc-ua.png | Bin 0 -> 4103 bytes .../DefinitionsAssetInterfacesDescription.cs | 4 + 7 files changed, 536 insertions(+), 2 deletions(-) create mode 100644 src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs create mode 100644 src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs create mode 100644 src/AasxPluginAssetInterfaceDesc/Resources/logo-opc-ua.png diff --git a/src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs b/src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs new file mode 100644 index 00000000..4772f6d5 --- /dev/null +++ b/src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs @@ -0,0 +1,324 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +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 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(); + 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(); + } + } +} diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj index b72c7a71..edfa5bd1 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj @@ -14,6 +14,7 @@ + @@ -31,6 +32,7 @@ + @@ -48,5 +50,6 @@ + diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index d4142373..3ad973f4 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -69,7 +69,7 @@ public class AidIfxItemStatus public AnyUiUIElement RenderedUiElement = null; } - public enum AidInterfaceTechnology { HTTP, Modbus, MQTT } + public enum AidInterfaceTechnology { HTTP, Modbus, MQTT, OPCUA } public class AidInterfaceStatus { @@ -177,6 +177,16 @@ public class AidBaseConnection { public Uri TargetUri; + /// + /// For initiating the connection. Right now, not foreseen/ encouraged by the SMT. + /// + public string User = null; + + /// + /// For initiating the connection. Right now, not foreseen/ encouraged by the SMT. + /// + public string Password = null; + public DateTime LastActive = default(DateTime); public Action MessageReceived = null; @@ -246,7 +256,7 @@ public T GetOrCreate(string target) /// public class AidAllInterfaceStatus { - public bool[] UseTech = { true, false, false }; + public bool[] UseTech = { false, false, false, true }; /// /// Will hold connections steady and continously update values, either by @@ -265,6 +275,9 @@ public class AidAllInterfaceStatus public AidGenericConnections MqttConnections = new AidGenericConnections(); + public AidGenericConnections OpcUaConnections = + new AidGenericConnections(); + protected AidBaseConnection GetOrCreate(AidInterfaceTechnology tech, string endpointBase) { // find connection by factory @@ -282,6 +295,10 @@ protected AidBaseConnection GetOrCreate(AidInterfaceTechnology tech, string endp case AidInterfaceTechnology.MQTT: conn = MqttConnections.GetOrCreate(endpointBase); break; + + case AidInterfaceTechnology.OPCUA: + conn = OpcUaConnections.GetOrCreate(endpointBase); + break; } return conn; } diff --git a/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs new file mode 100644 index 00000000..8ffdb2df --- /dev/null +++ b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs @@ -0,0 +1,181 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +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.Linq; +using System.Text; +using System.Threading.Tasks; +using AasxPredefinedConcepts; +using Aas = AasCore.Aas3_0; +using AdminShellNS; +using Extensions; +using WpfMtpControl; +using AasxIntegrationBase; +using AasxPredefinedConcepts.AssetInterfacesDescription; +using FluentModbus; +using System.Net; +using System.Text.RegularExpressions; +using System.Globalization; +using System.Net.Http; +using MQTTnet; +using MQTTnet.Client; +using System.Web.Services.Description; +using Opc.Ua; + +namespace AasxPluginAssetInterfaceDescription +{ + public class AidOpcUaConnection : AidBaseConnection + { + public AasOpcUaClient Client; + + // protected Dictionary _subscribedTopics = new Dictionary(); + + override public bool Open() + { + try + { + // make client + // use the full target uri as endpoint (first) + Client = new AasOpcUaClient( + TargetUri.ToString(), + _autoAccept: true, + _userName: this.User, + _password: this.Password); + Client.Run(); + + // ok + return IsConnected(); + } + catch (Exception ex) + { + Client = null; + // _subscribedTopics.Clear(); + return false; + } + } + + override public bool IsConnected() + { + // simple + return Client != null && Client.StatusCode == AasOpcUaClientStatus.Running; + } + + override public void Close() + { + if (IsConnected()) + { + try + { + Client.Cancel(); + Client.Close(); + } catch (Exception ex) + { + ; + } + // _subscribedTopics.Clear(); + } + } + + protected NodeId ParseAndCreateNodeId (string input) + { + if (input?.HasContent() != true) + return null; + + { + var match = Regex.Match(input, @"^\s*ns\s*=\s*(\d+)\s*;\s*i\s*=\s*(\d+)\s*$"); + if (match.Success + && ushort.TryParse(match.Groups[1].ToString(), out var ns) + && uint.TryParse(match.Groups[2].ToString(), out var i)) + return new NodeId(i, ns); + } + { + var match = Regex.Match(input, @"^\s*NS\s*(\d+)\s*\|\s*Numeric\s*\|\s*(\d+)\s*$"); + if (match.Success + && ushort.TryParse(match.Groups[1].ToString(), out var ns) + && uint.TryParse(match.Groups[2].ToString(), out var i)) + return new NodeId(i, ns); + } + { + var match = Regex.Match(input, @"^\s*ns\s*=\s*(\d+)\s*;\s*s\s*=\s*(.*)$"); + if (match.Success + && ushort.TryParse(match.Groups[1].ToString(), out var ns)) + return new NodeId("" + match.Groups[2].ToString(), ns); + } + { + var match = Regex.Match(input, @"^\s*NS\s*(\d+)\s*\|\s*Alphanumeric\s*\|\s*(.+)$"); + if (match.Success + && ushort.TryParse(match.Groups[1].ToString(), out var ns)) + return new NodeId("" + match.Groups[2].ToString(), ns); + } + + // no + return null; + } + + override public int UpdateItemValue(AidIfxItemStatus item) + { + // access + if (!IsConnected()) + return 0; + + // careful + try + { + // get an node id? + var nid = ParseAndCreateNodeId(item?.FormData?.Href); + + // direct read possible? + var dv = Client.ReadNodeId(nid); + item.Value = "" + dv?.Value; + LastActive = DateTime.Now; + } + catch (Exception ex) + { + ; + } + + + return 0; + } + + //override public void PrepareContinousRun(IEnumerable items) + //{ + // // access + // if (!IsConnected() || items == null) + // return; + + // foreach (var item in items) + // { + // // valid topic? + // var topic = "" + item.FormData?.Href; + // if (topic.StartsWith("/")) + // topic = topic.Remove(0, 1); + // if (!topic.HasContent()) + // continue; + + // // need only "subscribe" + // if (item.FormData?.Mqv_controlPacket?.HasContent() != true) + // continue; + // if (item.FormData.Mqv_controlPacket.Trim().ToLower() != "subscribe") + // continue; + + // // is topic already subscribed? + // if (_subscribedTopics.ContainsKey(topic)) + // continue; + + // // ok, subscribe + // var task = Task.Run(() => Client.SubscribeAsync(topic)); + // task.Wait(); + // _subscribedTopics.Add(topic, topic); + // } + //} + + } +} diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index 36a2f312..d016d953 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -120,6 +120,10 @@ public void Start( AnyUiGdiHelper.CreateAnyUiBitmapFromResource( "AasxPluginAssetInterfaceDesc.Resources.logo-mqtt.png", assembly: Assembly.GetExecutingAssembly())); + _dictTechnologyToBitmap.Add(AidInterfaceTechnology.OPCUA, + AnyUiGdiHelper.CreateAnyUiBitmapFromResource( + "AasxPluginAssetInterfaceDesc.Resources.logo-opc-ua.png", + assembly: Assembly.GetExecutingAssembly())); } // fill given panel @@ -454,6 +458,7 @@ protected List PrepareAidInformation(Aas.Submodel sm) var ifxs = data?.InterfaceHTTP; if (tech == AidInterfaceTechnology.Modbus) ifxs = data?.InterfaceMODBUS; if (tech == AidInterfaceTechnology.MQTT) ifxs = data?.InterfaceMQTT; + if (tech == AidInterfaceTechnology.OPCUA) ifxs = data?.InterfaceOPCUA; if (ifxs == null || ifxs.Count < 1) continue; foreach (var ifx in ifxs) diff --git a/src/AasxPluginAssetInterfaceDesc/Resources/logo-opc-ua.png b/src/AasxPluginAssetInterfaceDesc/Resources/logo-opc-ua.png new file mode 100644 index 0000000000000000000000000000000000000000..4adf42ca93f73c0e412a0485ed65b55aeff09a2e GIT binary patch literal 4103 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 literal 0 HcmV?d00001 diff --git a/src/AasxPredefinedConcepts/DefinitionsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/DefinitionsAssetInterfacesDescription.cs index a318cc88..5084549d 100644 --- a/src/AasxPredefinedConcepts/DefinitionsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/DefinitionsAssetInterfacesDescription.cs @@ -516,6 +516,10 @@ public class CD_AssetInterfacesDescription SupplSemId = "http://www.w3.org/2011/mqtt")] public List InterfaceMQTT = new List(); + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, + SupplSemId = "http://www.w3.org/2011/opc-ua")] + public List InterfaceOPCUA = new List(); + // auto-generated informations public AasClassMapperInfo __Info__ = null; }