diff --git a/MP.MONO.ADAPTER.OPC.sln b/MP.MONO.ADAPTER.OPC.sln
new file mode 100644
index 0000000..f328032
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32510.428
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MP.MONO.ADAPTER.OPC", "MP.MONO.ADAPTER.OPC\MP.MONO.ADAPTER.OPC.csproj", "{458FD824-7804-4CF9-8C43-463C1F5659DA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3811E8B4-1DD3-4841-9840-1B1A08782CD3}
+ EndGlobalSection
+EndGlobal
diff --git a/MP.MONO.ADAPTER.OPC/ConsoleUtils.cs b/MP.MONO.ADAPTER.OPC/ConsoleUtils.cs
new file mode 100644
index 0000000..92bcf42
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/ConsoleUtils.cs
@@ -0,0 +1,382 @@
+/* ========================================================================
+ * Copyright (c) 2005-2021 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.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Mono.Options;
+using Opc.Ua;
+using Opc.Ua.Configuration;
+using Serilog;
+using Serilog.Events;
+using Serilog.Templates;
+using static Opc.Ua.Utils;
+
+namespace MP.MONO.ADAPTER.OPC
+{
+ ///
+ /// The log output implementation of a TextWriter.
+ ///
+ public class LogWriter : TextWriter
+ {
+ private StringBuilder m_builder = new StringBuilder();
+
+ public override void Write(char value)
+ {
+ m_builder.Append(value);
+ }
+
+ public override void WriteLine(char value)
+ {
+ m_builder.Append(value);
+ LogInfo("{0}", m_builder.ToString());
+ m_builder.Clear();
+ }
+
+ public override void WriteLine()
+ {
+ LogInfo("{0}", m_builder.ToString());
+ m_builder.Clear();
+ }
+
+ public override void WriteLine(string format, object arg0)
+ {
+ m_builder.Append(format);
+ LogInfo(m_builder.ToString(), arg0);
+ m_builder.Clear();
+ }
+
+ public override void WriteLine(string format, object arg0, object arg1)
+ {
+ m_builder.Append(format);
+ LogInfo(m_builder.ToString(), arg0, arg1);
+ m_builder.Clear();
+ }
+
+ public override void WriteLine(string format, params object[] arg)
+ {
+ m_builder.Append(format);
+ LogInfo(m_builder.ToString(), arg);
+ m_builder.Clear();
+ }
+
+ public override void Write(string value)
+ {
+ m_builder.Append(value);
+ }
+
+ public override void WriteLine(string value)
+ {
+ m_builder.Append(value);
+ LogInfo("{0}", m_builder.ToString());
+ m_builder.Clear();
+ }
+
+ public override Encoding Encoding
+ {
+ get { return Encoding.Default; }
+ }
+ }
+
+ ///
+ /// The error code why the application exit.
+ ///
+ public enum ExitCode : int
+ {
+ Ok = 0,
+ ErrorNotStarted = 0x80,
+ ErrorRunning = 0x81,
+ ErrorException = 0x82,
+ ErrorStopping = 0x83,
+ ErrorCertificate = 0x84,
+ ErrorInvalidCommandLine = 0x100
+ };
+
+ ///
+ /// An exception that occured and caused an exit of the application.
+ ///
+ public class ErrorExitException : Exception
+ {
+ public ExitCode ExitCode { get; }
+
+ public ErrorExitException(ExitCode exitCode)
+ {
+ ExitCode = exitCode;
+ }
+
+ public ErrorExitException()
+ {
+ ExitCode = ExitCode.Ok;
+ }
+
+ public ErrorExitException(string message) : base(message)
+ {
+ ExitCode = ExitCode.Ok;
+ }
+
+ public ErrorExitException(string message, ExitCode exitCode) : base(message)
+ {
+ ExitCode = exitCode;
+ }
+
+ public ErrorExitException(string message, Exception innerException) : base(message, innerException)
+ {
+ ExitCode = ExitCode.Ok;
+ }
+
+ public ErrorExitException(string message, Exception innerException, ExitCode exitCode) : base(message, innerException)
+ {
+ ExitCode = exitCode;
+ }
+ }
+
+ ///
+ /// A dialog which asks for user input.
+ ///
+ public class ApplicationMessageDlg : IApplicationMessageDlg
+ {
+ private TextWriter m_output;
+ private string m_message = string.Empty;
+ private bool m_ask;
+
+ public ApplicationMessageDlg(TextWriter output)
+ {
+ m_output = output;
+ }
+
+ public override void Message(string text, bool ask)
+ {
+ m_message = text;
+ m_ask = ask;
+ }
+
+ public override async Task ShowAsync()
+ {
+ if (m_ask)
+ {
+ var message = new StringBuilder(m_message);
+ message.Append(" (y/n, default y): ");
+ m_output.Write(message.ToString());
+
+ try
+ {
+ ConsoleKeyInfo result = Console.ReadKey();
+ m_output.WriteLine();
+ return await Task.FromResult((result.KeyChar == 'y') ||
+ (result.KeyChar == 'Y') || (result.KeyChar == '\r')).ConfigureAwait(false);
+ }
+ catch
+ {
+ // intentionally fall through
+ }
+ }
+ else
+ {
+ m_output.WriteLine(m_message);
+ }
+
+ return await Task.FromResult(true).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Helper functions shared in various console applications.
+ ///
+ public static class ConsoleUtils
+ {
+ ///
+ /// Process a command line of the console sample application.
+ ///
+ public static string ProcessCommandLine(
+ TextWriter output,
+ string[] args,
+ Mono.Options.OptionSet options,
+ ref bool showHelp,
+ bool noExtraArgs = true)
+ {
+ IList extraArgs = null;
+ try
+ {
+ extraArgs = options.Parse(args);
+ if (noExtraArgs)
+ {
+ foreach (string extraArg in extraArgs)
+ {
+ output.WriteLine("Error: Unknown option: {0}", extraArg);
+ showHelp = true;
+ }
+ }
+ }
+ catch (OptionException e)
+ {
+ output.WriteLine(e.Message);
+ showHelp = true;
+ }
+
+ if (showHelp)
+ {
+ options.WriteOptionDescriptions(output);
+ throw new ErrorExitException("Invalid Commandline or help requested.", ExitCode.ErrorInvalidCommandLine);
+ }
+
+ return extraArgs.FirstOrDefault();
+ }
+
+ ///
+ /// Configure the logging providers.
+ ///
+ ///
+ /// Replaces the Opc.Ua.Core default ILogger with a
+ /// Microsoft.Extension.Logger with a Serilog file, debug and console logger.
+ /// The debug logger is only enabled for debug builds.
+ /// The console logger is enabled by the logConsole flag at the consoleLogLevel.
+ /// The file logger uses the setting in the ApplicationConfiguration.
+ /// The Trace logLevel is chosen if required by the Tracemasks.
+ ///
+ /// The application configuration.
+ /// The context name for the logger.
+ /// Enable logging to the console.
+ /// The LogLevel to use for the console/debug.<
+ /// /param>
+ public static void ConfigureLogging(
+ ApplicationConfiguration configuration,
+ string context,
+ bool logConsole,
+ LogLevel consoleLogLevel)
+ {
+ var loggerConfiguration = new LoggerConfiguration()
+ .Enrich.FromLogContext();
+
+ if (logConsole)
+ {
+ loggerConfiguration.WriteTo.Console(
+ restrictedToMinimumLevel: (LogEventLevel)consoleLogLevel
+ );
+ }
+#if DEBUG
+ else
+ {
+ loggerConfiguration
+ .WriteTo.Debug(restrictedToMinimumLevel: (LogEventLevel)consoleLogLevel);
+ }
+#endif
+ LogLevel fileLevel = LogLevel.Information;
+
+ // switch for Trace/Verbose output
+ var traceMasks = configuration.TraceConfiguration.TraceMasks;
+ if ((traceMasks & ~(TraceMasks.Information | TraceMasks.Error |
+ TraceMasks.Security | TraceMasks.StartStop | TraceMasks.StackTrace)) != 0)
+ {
+ fileLevel = LogLevel.Trace;
+ }
+
+ // add file logging if configured
+ var outputFilePath = configuration.TraceConfiguration.OutputFilePath;
+ if (!string.IsNullOrWhiteSpace(outputFilePath))
+ {
+ loggerConfiguration.WriteTo.File(
+ new ExpressionTemplate("{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss.fff} [{@l:u3}] {@m}\n{@x}"),
+ ReplaceSpecialFolderNames(outputFilePath),
+ restrictedToMinimumLevel: (LogEventLevel)fileLevel,
+ rollOnFileSizeLimit: true);
+ }
+
+ // adjust minimum level
+ if (fileLevel < LogLevel.Information || consoleLogLevel < LogLevel.Information)
+ {
+ loggerConfiguration.MinimumLevel.Verbose();
+ }
+
+ // create the serilog logger
+ var serilogger = loggerConfiguration
+ .CreateLogger();
+
+ // create the ILogger for Opc.Ua.Core
+ var logger = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Trace))
+ .AddSerilog(serilogger)
+ .CreateLogger(context);
+
+ // set logger interface, disables TraceEvent
+ SetLogger(logger);
+ }
+
+ ///
+ /// Output log messages.
+ ///
+ public static void LogTest()
+ {
+ // print legacy logging output, for testing
+ Trace(TraceMasks.Error, "This is an Error message: {0}", TraceMasks.Error);
+ Trace(TraceMasks.Information, "This is a Information message: {0}", TraceMasks.Information);
+ Trace(TraceMasks.StackTrace, "This is a StackTrace message: {0}", TraceMasks.StackTrace);
+ Trace(TraceMasks.Service, "This is a Service message: {0}", TraceMasks.Service);
+ Trace(TraceMasks.ServiceDetail, "This is a ServiceDetail message: {0}", TraceMasks.ServiceDetail);
+ Trace(TraceMasks.Operation, "This is a Operation message: {0}", TraceMasks.Operation);
+ Trace(TraceMasks.OperationDetail, "This is a OperationDetail message: {0}", TraceMasks.OperationDetail);
+ Trace(TraceMasks.StartStop, "This is a StartStop message: {0}", TraceMasks.StartStop);
+ Trace(TraceMasks.ExternalSystem, "This is a ExternalSystem message: {0}", TraceMasks.ExternalSystem);
+ Trace(TraceMasks.Security, "This is a Security message: {0}", TraceMasks.Security);
+
+ // print ILogger logging output
+ LogTrace("This is a Trace message: {0}", LogLevel.Trace);
+ LogDebug("This is a Debug message: {0}", LogLevel.Debug);
+ LogInfo("This is a Info message: {0}", LogLevel.Information);
+ LogWarning("This is a Warning message: {0}", LogLevel.Warning);
+ LogError("This is a Error message: {0}", LogLevel.Error);
+ LogCritical("This is a Critical message: {0}", LogLevel.Critical);
+ }
+
+ ///
+ /// Create an event which is set if a user
+ /// enters the Ctrl-C key combination.
+ ///
+ public static ManualResetEvent CtrlCHandler()
+ {
+ var quitEvent = new ManualResetEvent(false);
+ try
+ {
+ Console.CancelKeyPress += (_, eArgs) => {
+ quitEvent.Set();
+ eArgs.Cancel = true;
+ };
+ }
+ catch
+ {
+ // intentionally left blank
+ }
+ return quitEvent;
+ }
+ }
+}
+
diff --git a/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj b/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj
new file mode 100644
index 0000000..b4a2e95
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net6.0
+ MP.MONO.ADAPTER.OPC
+ Exe
+ MP.MONO.ADAPTER.OPC
+ EgalWare
+ OPC UA Console Client
+ Copyright © 2020+ EgalWare
+ MP.MONO.ADAPTER.OPC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
diff --git a/MP.MONO.ADAPTER/Program.cs b/MP.MONO.ADAPTER.OPC/Program.cs
similarity index 99%
rename from MP.MONO.ADAPTER/Program.cs
rename to MP.MONO.ADAPTER.OPC/Program.cs
index 50961cb..a949e0d 100644
--- a/MP.MONO.ADAPTER/Program.cs
+++ b/MP.MONO.ADAPTER.OPC/Program.cs
@@ -36,7 +36,7 @@ using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Configuration;
-namespace Quickstarts.ConsoleReferenceClient
+namespace MP.MONO.ADAPTER.OPC
{
///
/// The program.
diff --git a/MP.MONO.ADAPTER.OPC/Quickstarts.ReferenceClient.Config.xml b/MP.MONO.ADAPTER.OPC/Quickstarts.ReferenceClient.Config.xml
new file mode 100644
index 0000000..d9f67d5
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/Quickstarts.ReferenceClient.Config.xml
@@ -0,0 +1,89 @@
+
+
+ Egalware Console Adapter OPC Client
+ urn:localhost:UA:Egalware:MP:MONO:ADAPTER
+ uri:egalware.com:MP:MONO:ADAPTER
+ Client_1
+
+
+
+
+
+ Directory
+ %LocalApplicationData%/EgalWare/pki/own
+ CN=EgalWare Adapter Client, C=IT, S=Bergamo, O=EgalWare, DC=localhost
+
+
+
+
+ Directory
+ %LocalApplicationData%/EgalWare/pki/issuer
+
+
+
+
+ Directory
+ %LocalApplicationData%/EgalWare/pki/trusted
+
+
+
+
+ Directory
+ %LocalApplicationData%/EgalWare/pki/rejected
+
+
+
+ false
+
+
+
+
+
+
+ 600000
+ 1048576
+ 1048576
+ 65535
+ 4194304
+ 65535
+ 300000
+ 3600000
+
+
+
+ 60000
+
+ opc.tcp://{0}:4840
+ http://{0}:52601/UADiscovery
+ http://{0}/UADiscovery/Default.svc
+
+
+ 10000
+
+
+
+
+
+
+ %LocalApplicationData%/EgalWare/Logs/MP.MONO.ADAPTER.OPC.log.txt
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MP.MONO.ADAPTER/UAClient.cs b/MP.MONO.ADAPTER.OPC/UAClient.cs
similarity index 99%
rename from MP.MONO.ADAPTER/UAClient.cs
rename to MP.MONO.ADAPTER.OPC/UAClient.cs
index 66290eb..3b815f5 100644
--- a/MP.MONO.ADAPTER/UAClient.cs
+++ b/MP.MONO.ADAPTER.OPC/UAClient.cs
@@ -35,7 +35,7 @@ using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Client;
-namespace Quickstarts
+namespace MP.MONO.ADAPTER.OPC
{
///
/// OPC UA Client with examples of basic functionality.
diff --git a/MP.MONO.ADAPTER/dotnettrace.cmd b/MP.MONO.ADAPTER.OPC/dotnettrace.cmd
similarity index 100%
rename from MP.MONO.ADAPTER/dotnettrace.cmd
rename to MP.MONO.ADAPTER.OPC/dotnettrace.cmd
diff --git a/MP.MONO.ADAPTER/MP.MONO.ADAPTER.csproj b/MP.MONO.ADAPTER.OPC2/MP - Backup.MONO.ADAPTER.OPC.csproj
similarity index 78%
rename from MP.MONO.ADAPTER/MP.MONO.ADAPTER.csproj
rename to MP.MONO.ADAPTER.OPC2/MP - Backup.MONO.ADAPTER.OPC.csproj
index 65bfedf..a47c3ec 100644
--- a/MP.MONO.ADAPTER/MP.MONO.ADAPTER.csproj
+++ b/MP.MONO.ADAPTER.OPC2/MP - Backup.MONO.ADAPTER.OPC.csproj
@@ -2,13 +2,13 @@
$(AppTargetFrameWorks)
- ConsoleReferenceClient
+ MP.MONO.ADAPTER.OPC
Exe
- ConsoleReferenceClient
- OPC Foundation
- .NET Console Reference Client
- Copyright © 2004-2022 OPC Foundation, Inc
- Quickstarts.ConsoleReferenceClient
+ MP.MONO.ADAPTER.OPC
+ EgalWare
+ OPC UA Console Client
+ Copyright © 2020+ EgalWare
+ MP.MONO.ADAPTER.OPC
@@ -37,7 +37,7 @@
-
+
PreserveNewest
diff --git a/MP.MONO.ADAPTER/Quickstarts.ReferenceClient.Config.xml b/MP.MONO.ADAPTER.OPC2/MP.MONO.ADAPTER.OPC.Config.xml
similarity index 100%
rename from MP.MONO.ADAPTER/Quickstarts.ReferenceClient.Config.xml
rename to MP.MONO.ADAPTER.OPC2/MP.MONO.ADAPTER.OPC.Config.xml
diff --git a/MP.MONO.ADAPTER.OPC2/MP.MONO.ADAPTER.OPC.csproj b/MP.MONO.ADAPTER.OPC2/MP.MONO.ADAPTER.OPC.csproj
new file mode 100644
index 0000000..a47c3ec
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC2/MP.MONO.ADAPTER.OPC.csproj
@@ -0,0 +1,45 @@
+
+
+
+ $(AppTargetFrameWorks)
+ MP.MONO.ADAPTER.OPC
+ Exe
+ MP.MONO.ADAPTER.OPC
+ EgalWare
+ OPC UA Console Client
+ Copyright © 2020+ EgalWare
+ MP.MONO.ADAPTER.OPC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/MP.MONO.ADAPTER.OPC2/Program.cs b/MP.MONO.ADAPTER.OPC2/Program.cs
new file mode 100644
index 0000000..a949e0d
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC2/Program.cs
@@ -0,0 +1,186 @@
+/* ========================================================================
+ * Copyright (c) 2005-2021 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.Globalization;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Opc.Ua;
+using Opc.Ua.Configuration;
+
+namespace MP.MONO.ADAPTER.OPC
+{
+ ///
+ /// The program.
+ ///
+ public static class Program
+ {
+ ///
+ /// Main entry point.
+ ///
+ public static async Task Main(string[] args)
+ {
+ TextWriter output = Console.Out;
+ output.WriteLine("OPC UA Console Reference Client");
+
+ output.WriteLine("OPC UA library: {0} @ {1} -- {2}",
+ Utils.GetAssemblyBuildNumber(),
+ Utils.GetAssemblyTimestamp().ToString("G", CultureInfo.InvariantCulture),
+ Utils.GetAssemblySoftwareVersion());
+
+ // The application name and config file names
+ var applicationName = "ConsoleReferenceClient";
+ var configSectionName = "Quickstarts.ReferenceClient";
+ var usage = $"Usage: dotnet {applicationName}.dll [OPTIONS]";
+
+ // command line options
+ bool showHelp = false;
+ bool autoAccept = false;
+ bool logConsole = false;
+ bool appLog = false;
+ bool renewCertificate = false;
+ string password = null;
+ int timeout = Timeout.Infinite;
+
+ Mono.Options.OptionSet options = new Mono.Options.OptionSet {
+ usage,
+ { "h|help", "show this message and exit", h => showHelp = h != null },
+ { "a|autoaccept", "auto accept certificates (for testing only)", a => autoAccept = a != null },
+ { "c|console", "log to console", c => logConsole = c != null },
+ { "l|log", "log app output", c => appLog = c != null },
+ { "p|password=", "optional password for private key", (string p) => password = p },
+ { "r|renew", "renew application certificate", r => renewCertificate = r != null },
+ { "t|timeout=", "timeout in seconds to exit application", (int t) => timeout = t * 1000 },
+ };
+
+ try
+ {
+ // parse command line and set options
+ var extraArg = ConsoleUtils.ProcessCommandLine(output, args, options, ref showHelp, false);
+
+ // connect Url?
+ Uri serverUrl = new Uri("opc.tcp://localhost:62541/Quickstarts/ReferenceServer");
+ if (!string.IsNullOrEmpty(extraArg))
+ {
+ serverUrl = new Uri(extraArg);
+ }
+
+ // log console output to logger
+ if (logConsole && appLog)
+ {
+ output = new LogWriter();
+ }
+
+ // Define the UA Client application
+ ApplicationInstance.MessageDlg = new ApplicationMessageDlg(output);
+ CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(password);
+ ApplicationInstance application = new ApplicationInstance {
+ ApplicationName = applicationName,
+ ApplicationType = ApplicationType.Client,
+ ConfigSectionName = configSectionName,
+ CertificatePasswordProvider = PasswordProvider
+ };
+
+ // load the application configuration.
+ var config = await application.LoadApplicationConfiguration(silent: false);
+
+ // setup the logging
+ ConsoleUtils.ConfigureLogging(config, applicationName, logConsole, LogLevel.Information);
+
+ // delete old certificate
+ if (renewCertificate)
+ {
+ await application.DeleteApplicationInstanceCertificate().ConfigureAwait(false);
+ }
+
+ // check the application certificate.
+ bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, minimumKeySize: 0).ConfigureAwait(false);
+ if (!haveAppCertificate)
+ {
+ throw new ErrorExitException("Application instance certificate invalid!", ExitCode.ErrorCertificate);
+ }
+
+ // wait for timeout or Ctrl-C
+ var quitEvent = ConsoleUtils.CtrlCHandler();
+
+ // connect to a server until application stopped
+ bool quit = false;
+ DateTime start = DateTime.UtcNow;
+ int waitTime = int.MaxValue;
+ do
+ {
+ if (timeout > 0)
+ {
+ waitTime = timeout - (int)DateTime.UtcNow.Subtract(start).TotalMilliseconds;
+ if (waitTime <= 0)
+ {
+ break;
+ }
+ }
+
+ // create the UA Client object and connect to configured server.
+ UAClient uaClient = new UAClient(application.ApplicationConfiguration, output, ClientBase.ValidateResponse) {
+ AutoAccept = autoAccept
+ };
+
+ bool connected = await uaClient.ConnectAsync(serverUrl.ToString());
+ if (connected)
+ {
+ // Run tests for available methods.
+ uaClient.ReadNodes();
+ uaClient.WriteNodes();
+ uaClient.Browse();
+ uaClient.CallMethod();
+
+ uaClient.SubscribeToDataChanges();
+
+ // Wait for some DataChange notifications from MonitoredItems
+ quit = quitEvent.WaitOne(Math.Min(30_000, waitTime));
+
+ uaClient.Disconnect();
+ }
+ else
+ {
+ output.WriteLine("Could not connect to server! Retry in 10 seconds or Ctrl-C to quit.");
+ quit = quitEvent.WaitOne(Math.Min(10_000, waitTime));
+ }
+
+ } while (!quit);
+
+ output.WriteLine("\nClient stopped.");
+ }
+ catch (Exception ex)
+ {
+ output.WriteLine(ex.Message);
+ }
+ }
+ }
+}
diff --git a/MP.MONO.ADAPTER.OPC2/UAClient.cs b/MP.MONO.ADAPTER.OPC2/UAClient.cs
new file mode 100644
index 0000000..3b815f5
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC2/UAClient.cs
@@ -0,0 +1,501 @@
+/* ========================================================================
+ * 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 MP.MONO.ADAPTER.OPC
+{
+ ///
+ /// OPC UA Client with examples of basic functionality.
+ ///
+ class UAClient
+ {
+ #region Constructors
+ ///
+ /// Initializes a new instance of the UAClient class.
+ ///
+ public UAClient(ApplicationConfiguration configuration, TextWriter writer, Action validateResponse)
+ {
+ m_validateResponse = validateResponse;
+ m_output = writer;
+ m_configuration = configuration;
+ m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;
+ }
+ #endregion
+
+ #region Public Properties
+ ///
+ /// Gets the client session.
+ ///
+ public Session Session => m_session;
+
+ ///
+ /// Auto accept untrusted certificates.
+ ///
+ public bool AutoAccept { get; set; } = false;
+ #endregion
+
+ #region Public Methods
+ ///
+ /// Creates a session with the UA server
+ ///
+ public async Task 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;
+ }
+ }
+
+ ///
+ /// Disconnects the session.
+ ///
+ 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}");
+ }
+ }
+
+ ///
+ /// Read a list of nodes from Server
+ ///
+ 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}.");
+ }
+ }
+
+ ///
+ /// Write a list of nodes to the Server
+ ///
+ 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}.");
+ }
+ }
+
+ ///
+ /// Browse Server nodes
+ ///
+ 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}.");
+ }
+ }
+
+ ///
+ /// Call UA method
+ ///
+ 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