Rimesso adapter in proj da Ref Client

This commit is contained in:
Samuele Locatelli
2022-06-14 14:01:13 +02:00
parent 03b618f64d
commit 2fe6f46a85
19 changed files with 1321 additions and 27 deletions
+25
View File
@@ -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
+382
View File
@@ -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
{
/// <summary>
/// The log output implementation of a TextWriter.
/// </summary>
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; }
}
}
/// <summary>
/// The error code why the application exit.
/// </summary>
public enum ExitCode : int
{
Ok = 0,
ErrorNotStarted = 0x80,
ErrorRunning = 0x81,
ErrorException = 0x82,
ErrorStopping = 0x83,
ErrorCertificate = 0x84,
ErrorInvalidCommandLine = 0x100
};
/// <summary>
/// An exception that occured and caused an exit of the application.
/// </summary>
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;
}
}
/// <summary>
/// A dialog which asks for user input.
/// </summary>
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<bool> 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);
}
}
/// <summary>
/// Helper functions shared in various console applications.
/// </summary>
public static class ConsoleUtils
{
/// <summary>
/// Process a command line of the console sample application.
/// </summary>
public static string ProcessCommandLine(
TextWriter output,
string[] args,
Mono.Options.OptionSet options,
ref bool showHelp,
bool noExtraArgs = true)
{
IList<string> 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();
}
/// <summary>
/// Configure the logging providers.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="configuration">The application configuration.</param>
/// <param name="context">The context name for the logger. </param>
/// <param name="logConsole">Enable logging to the console.</param>
/// <param name="consoleLogLevel">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);
}
/// <summary>
/// Output log messages.
/// </summary>
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);
}
/// <summary>
/// Create an event which is set if a user
/// enters the Ctrl-C key combination.
/// </summary>
public static ManualResetEvent CtrlCHandler()
{
var quitEvent = new ManualResetEvent(false);
try
{
Console.CancelKeyPress += (_, eArgs) => {
quitEvent.Set();
eArgs.Cancel = true;
};
}
catch
{
// intentionally left blank
}
return quitEvent;
}
}
}
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>MP.MONO.ADAPTER.OPC</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>MP.MONO.ADAPTER.OPC</PackageId>
<Company>EgalWare</Company>
<Description>OPC UA Console Client</Description>
<Copyright>Copyright © 2020+ EgalWare</Copyright>
<RootNamespace>MP.MONO.ADAPTER.OPC</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.4.368.58" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.4.368.58" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.4.368.58" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.4.368.58" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Expressions" Version="3.4.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="Quickstarts.ReferenceClient.Config.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -36,7 +36,7 @@ using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Configuration;
namespace Quickstarts.ConsoleReferenceClient
namespace MP.MONO.ADAPTER.OPC
{
/// <summary>
/// The program.
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
>
<ApplicationName>Egalware Console Adapter OPC Client</ApplicationName>
<ApplicationUri>urn:localhost:UA:Egalware:MP:MONO:ADAPTER</ApplicationUri>
<ProductUri>uri:egalware.com:MP:MONO:ADAPTER</ProductUri>
<ApplicationType>Client_1</ApplicationType>
<SecurityConfiguration>
<!-- Where the application instance certificate is stored (MachineDefault) -->
<ApplicationCertificate>
<StoreType>Directory</StoreType>
<StorePath>%LocalApplicationData%/EgalWare/pki/own</StorePath>
<SubjectName>CN=EgalWare Adapter Client, C=IT, S=Bergamo, O=EgalWare, DC=localhost</SubjectName>
</ApplicationCertificate>
<!-- Where the issuer certificate are stored (certificate authorities) -->
<TrustedIssuerCertificates>
<StoreType>Directory</StoreType>
<StorePath>%LocalApplicationData%/EgalWare/pki/issuer</StorePath>
</TrustedIssuerCertificates>
<!-- Where the trust list is stored -->
<TrustedPeerCertificates>
<StoreType>Directory</StoreType>
<StorePath>%LocalApplicationData%/EgalWare/pki/trusted</StorePath>
</TrustedPeerCertificates>
<!-- The directory used to store invalid certficates for later review by the administrator. -->
<RejectedCertificateStore>
<StoreType>Directory</StoreType>
<StorePath>%LocalApplicationData%/EgalWare/pki/rejected</StorePath>
</RejectedCertificateStore>
<!-- WARNING: The following setting (to automatically accept untrusted certificates) should be used
for easy debugging purposes ONLY and turned off for production deployments! -->
<AutoAcceptUntrustedCertificates>false</AutoAcceptUntrustedCertificates>
</SecurityConfiguration>
<TransportConfigurations></TransportConfigurations>
<TransportQuotas>
<OperationTimeout>600000</OperationTimeout>
<MaxStringLength>1048576</MaxStringLength>
<MaxByteStringLength>1048576</MaxByteStringLength>
<MaxArrayLength>65535</MaxArrayLength>
<MaxMessageSize>4194304</MaxMessageSize>
<MaxBufferSize>65535</MaxBufferSize>
<ChannelLifetime>300000</ChannelLifetime>
<SecurityTokenLifetime>3600000</SecurityTokenLifetime>
</TransportQuotas>
<ClientConfiguration>
<DefaultSessionTimeout>60000</DefaultSessionTimeout>
<WellKnownDiscoveryUrls>
<ua:String>opc.tcp://{0}:4840</ua:String>
<ua:String>http://{0}:52601/UADiscovery</ua:String>
<ua:String>http://{0}/UADiscovery/Default.svc</ua:String>
</WellKnownDiscoveryUrls>
<DiscoveryServers></DiscoveryServers>
<MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
</ClientConfiguration>
<Extensions>
</Extensions>
<TraceConfiguration>
<OutputFilePath>%LocalApplicationData%/EgalWare/Logs/MP.MONO.ADAPTER.OPC.log.txt</OutputFilePath>
<DeleteOnLoad>true</DeleteOnLoad>
<!-- Show Only Errors -->
<!-- <TraceMasks>1</TraceMasks> -->
<!-- Show Only Security and Errors -->
<!-- <TraceMasks>513</TraceMasks> -->
<!-- Show Only Security, Errors and Trace -->
<!-- <TraceMasks>515</TraceMasks> -->
<!-- Show Only Security, COM Calls, Errors and Trace -->
<!-- <TraceMasks>771</TraceMasks> -->
<!-- Show Only Security, Service Calls, Errors and Trace -->
<!-- <TraceMasks>523</TraceMasks> -->
<!-- Show Only Security, ServiceResultExceptions, Errors and Trace -->
<!-- <TraceMasks>519</TraceMasks> -->
</TraceConfiguration>
</ApplicationConfiguration>
@@ -35,7 +35,7 @@ using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Client;
namespace Quickstarts
namespace MP.MONO.ADAPTER.OPC
{
/// <summary>
/// OPC UA Client with examples of basic functionality.
@@ -2,13 +2,13 @@
<PropertyGroup>
<TargetFrameworks>$(AppTargetFrameWorks)</TargetFrameworks>
<AssemblyName>ConsoleReferenceClient</AssemblyName>
<AssemblyName>MP.MONO.ADAPTER.OPC</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>ConsoleReferenceClient</PackageId>
<Company>OPC Foundation</Company>
<Description>.NET Console Reference Client</Description>
<Copyright>Copyright © 2004-2022 OPC Foundation, Inc</Copyright>
<RootNamespace>Quickstarts.ConsoleReferenceClient</RootNamespace>
<PackageId>MP.MONO.ADAPTER.OPC</PackageId>
<Company>EgalWare</Company>
<Description>OPC UA Console Client</Description>
<Copyright>Copyright © 2020+ EgalWare</Copyright>
<RootNamespace>MP.MONO.ADAPTER.OPC</RootNamespace>
</PropertyGroup>
<ItemGroup Condition=" '$(NoHttps)' != 'true' ">
@@ -37,7 +37,7 @@
</ItemGroup>
<ItemGroup>
<None Update="Quickstarts.ReferenceClient.Config.xml">
<None Update="MP.MONO.ADAPTER.OPC.Config.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(AppTargetFrameWorks)</TargetFrameworks>
<AssemblyName>MP.MONO.ADAPTER.OPC</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>MP.MONO.ADAPTER.OPC</PackageId>
<Company>EgalWare</Company>
<Description>OPC UA Console Client</Description>
<Copyright>Copyright © 2020+ EgalWare</Copyright>
<RootNamespace>MP.MONO.ADAPTER.OPC</RootNamespace>
</PropertyGroup>
<ItemGroup Condition=" '$(NoHttps)' != 'true' ">
<ProjectReference Include="..\..\Stack\Opc.Ua.Bindings.Https\Opc.Ua.Bindings.Https.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ConsoleReferenceServer\ConsoleUtils.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Expressions" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Stack\Opc.Ua.Core\Opc.Ua.Core.csproj" />
<ProjectReference Include="..\..\Libraries\Opc.Ua.Configuration\Opc.Ua.Configuration.csproj" />
<ProjectReference Include="..\..\Libraries\Opc.Ua.Client\Opc.Ua.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="MP.MONO.ADAPTER.OPC.Config.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+186
View File
@@ -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
{
/// <summary>
/// The program.
/// </summary>
public static class Program
{
/// <summary>
/// Main entry point.
/// </summary>
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);
}
}
}
}
+501
View File
@@ -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
{
/// <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
}
}
+3
View File
@@ -0,0 +1,3 @@
REM collect a trace using the EventSource provider OPC-UA-Core
dotnet tool install --global dotnet-trace
dotnet-trace collect --name consolereferenceclient --providers OPC-UA-Core,OPC-UA-Client
+44
View File
@@ -0,0 +1,44 @@
<Project>
<!-- Uncomment to suppress warning that .NET Core 2.1 is used with .NET 6 library. -->
<!--
<PropertyGroup>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup>
-->
<Choose>
<!-- Note: .NET Core 2.x is end of life, removed netcoreapp2.1 from any target. -->
<!-- Visual Studio 2022, supports .NET Framework 4.6.2, .NET Core 3.1 and .NET 6 -->
<When Condition="'$(VisualStudioVersion)' == '17.0'">
<PropertyGroup>
<AppTargetFrameworks>net6.0;netcoreapp3.1;net462</AppTargetFrameworks>
<AppTargetFramework>net6.0</AppTargetFramework>
<TestsTargetFrameworks>net462;netcoreapp3.1;net6.0</TestsTargetFrameworks>
<LibTargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0</LibTargetFrameworks>
<LibxTargetFrameworks>net462;netstandard2.1;net6.0</LibxTargetFrameworks>
<HttpsTargetFrameworks>net462;netstandard2.0;netcoreapp3.1;net6.0</HttpsTargetFrameworks>
</PropertyGroup>
</When>
<!-- Visual Studio 2019, supports .NET Framework 4.6.2 and .NET Core 3.1 -->
<When Condition="'$(VisualStudioVersion)' == '16.0'">
<PropertyGroup>
<AppTargetFrameworks>netcoreapp3.1;net462</AppTargetFrameworks>
<AppTargetFramework>netcoreapp3.1</AppTargetFramework>
<TestsTargetFrameworks>net462;netcoreapp3.1</TestsTargetFrameworks>
<LibTargetFrameworks>net462;netstandard2.0;netstandard2.1</LibTargetFrameworks>
<LibxTargetFrameworks>net462;netstandard2.1</LibxTargetFrameworks>
<HttpsTargetFrameworks>net462;netstandard2.0;netcoreapp3.1</HttpsTargetFrameworks>
</PropertyGroup>
</When>
<!-- Visual Studio 2017 and earlier, support only .NET Framework 4.6.2 because .NET Core 2.x is end of life. -->
<Otherwise>
<PropertyGroup>
<AppTargetFrameworks>net462</AppTargetFrameworks>
<AppTargetFramework>net462</AppTargetFramework>
<TestsTargetFrameworks>net462</TestsTargetFrameworks>
<LibTargetFrameworks>net462</LibTargetFrameworks>
<LibxTargetFrameworks>net462</LibxTargetFrameworks>
<HttpsTargetFrameworks>net462</HttpsTargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
</Project>
-14
View File
@@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.DECODER", "MP.MONO.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.ANALYZER", "MP.MONO.ANALYZER\MP.MONO.ANALYZER.csproj", "{4C9BEAED-1A33-41A7-B9D6-7C173A43FDB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.ADAPTER", "MP.MONO.ADAPTER\MP.MONO.ADAPTER.csproj", "{873736BA-CDB6-4CE5-A340-6D904C11C07C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -99,18 +97,6 @@ Global
{4C9BEAED-1A33-41A7-B9D6-7C173A43FDB8}.Release|x64.Build.0 = Release|x64
{4C9BEAED-1A33-41A7-B9D6-7C173A43FDB8}.Release|x86.ActiveCfg = Release|x86
{4C9BEAED-1A33-41A7-B9D6-7C173A43FDB8}.Release|x86.Build.0 = Release|x86
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|x64.ActiveCfg = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|x64.Build.0 = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|x86.ActiveCfg = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Debug|x86.Build.0 = Debug|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|Any CPU.Build.0 = Release|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|x64.ActiveCfg = Release|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|x64.Build.0 = Release|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|x86.ActiveCfg = Release|Any CPU
{873736BA-CDB6-4CE5-A340-6D904C11C07C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x86;x64</Platforms>
<Version>1.12206.1319</Version>
<Version>1.12206.1411</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>MAPO-MONO</i>
<h4>Version: 1.12206.1319</h4>
<h4>Version: 1.12206.1411</h4>
<br /> Release Note:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
1.12206.1319
1.12206.1411
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.12206.1319</version>
<version>1.12206.1411</version>
<url>http://nexus.steamware.net/repository/SWS/MP.MONO.UI/stable/LAST/MP.Mon.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/MP.MONO.UI/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>