diff --git a/README.md b/README.md index e7b214cb..e3d17fc7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ products: urlFragment: azure-iot-sample-opc-ua-server --- - # OPC PLC server Implements an OPC-UA server with different nodes generating random data, anomalies and configuration of user defined nodes. @@ -242,6 +241,13 @@ More information about this feature can be found [here](deterministic-alarms.md) | StartUpdateSlowNodes | Start the increase of value of slow nodes | slow nodes activated | | StartUpdateFastNodes | Start the increase of value of fast nodes | fast nodes activated | +## NuGet +- The OPC PLC build generates a NuGet package that can be used to add the OPC PLC server to your own project, e.g. for unit tests +- Sample base class for unit tests: `./samples/OpcPlcBase.cs` +- Sample unit test file that uses the base class: `./samples/OpcUaUnitTests.cs` +- Sample NuGet config to consume a local package (needs to be next to the solution file): `./samples/nuget.config` +- Sample project file that shows how to import the local OPC PLC nuget package: `./samples/OpcUaUnitTests.prj` + ## Build The build scripts are for Azure DevOps and the container build is done in ACR. To use your own ACR you need to: diff --git a/docs/media/icon.png b/docs/media/icon.png new file mode 100644 index 00000000..73b3fb17 Binary files /dev/null and b/docs/media/icon.png differ diff --git a/samples/OpcPlcBase.cs b/samples/OpcPlcBase.cs new file mode 100644 index 00000000..646dffae --- /dev/null +++ b/samples/OpcPlcBase.cs @@ -0,0 +1,54 @@ +namespace UnitTests; + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +public class OpcPlcBase +{ + public OpcPlcBase(string[] args, int port = 51234) + { + // Passed args override the following defaults. + var serverTask = Task.Run(() => OpcPlc.Program.MainAsync( + args.Concat( + new[] + { + "--autoaccept", + $"--portnum={port}", + "--fn=25", + "--fr=1", + "--ft=uint", + }).ToArray(), + CancellationToken.None) + .GetAwaiter().GetResult()); + + OpcPlcEndpointUrl = WaitForServerUpAsync(serverTask).GetAwaiter().GetResult(); + } + + public string OpcPlcEndpointUrl { get; } + + private static async Task WaitForServerUpAsync(Task serverTask) + { + while (true) + { + if (serverTask.IsFaulted) + { + throw serverTask.Exception!; + } + + if (serverTask.IsCompleted) + { + throw new Exception("Server failed to start."); + } + + if (!OpcPlc.Program.Ready) + { + await Task.Delay(1000).ConfigureAwait(false); + continue; + } + + return OpcPlc.Program.PlcServer.GetEndpoints()[0].EndpointUrl; + } + } +} diff --git a/samples/OpcUaUnitTests.cs b/samples/OpcUaUnitTests.cs new file mode 100644 index 00000000..cead39a7 --- /dev/null +++ b/samples/OpcUaUnitTests.cs @@ -0,0 +1,22 @@ +namespace UnitTests; + +using System.Threading.Tasks; + +public class OpcUaUnitTests : OpcPlcBase +{ + public OpcUaUnitTests() + : base(new[] { "--gn=2" }) // Additional arguments. + { + } + + [Test] + public async Task TestConnectedClientSession() + { + var opcUaClient = await OpcUaClientFactory + .GetConnectedClient(OpcPlcEndpointUrl) + .ConfigureAwait(false); + + opcUaClient.Session.Connected.Should().BeTrue(); + opcUaClient.Session.Close(); + } +} diff --git a/samples/OpcUaUnitTests.prj b/samples/OpcUaUnitTests.prj new file mode 100644 index 00000000..bf383771 --- /dev/null +++ b/samples/OpcUaUnitTests.prj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/nuget.config b/samples/nuget.config new file mode 100644 index 00000000..653e12cb --- /dev/null +++ b/samples/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.IoTEdge.OpcPlc.targets b/src/Microsoft.IoTEdge.OpcPlc.targets new file mode 100644 index 00000000..9f4a433d --- /dev/null +++ b/src/Microsoft.IoTEdge.OpcPlc.targets @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 10c19f19..da06cf18 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -344,7 +344,7 @@ private static void InitLogging() return; } - LogLevel logLevel = LogLevel.Information; + LogLevel logLevel; // set the log level switch (LogLevelCli) @@ -402,8 +402,8 @@ public static IHost CreateHostBuilder(string[] args) }).Build(); return host; - } - + } + private static void LogLogo() { Logger.LogInformation( diff --git a/src/opc-plc.csproj b/src/opc-plc.csproj index a04aeb4f..0791b6b5 100644 --- a/src/opc-plc.csproj +++ b/src/opc-plc.csproj @@ -4,11 +4,20 @@ net8.0 opcplc Exe - OpcPlc + true + Microsoft.IoTEdge.OpcPlc Microsoft + 1.0.0 OpcPlc bdf7b209-fef3-44d4-9aba-17035fda5efd - 10.0 + Preview + True + Microsoft IoT Edge OPC PLC server + Azure Industrial IoT OPC UA PLC Server + Implements an OPC-UA server with different nodes generating random data, anomalies and configuration of user defined nodes. + https://github.com/Azure-Samples/iot-edge-opc-plc + $(OutputPath) + icon.png @@ -68,4 +77,20 @@ + + + + contentFiles\Boilers + + + contentFiles\CompanionSpecs + + + contentFiles\SimpleEvent + + + contentFiles + + + diff --git a/tests/PlcSimulatorFixture.cs b/tests/PlcSimulatorFixture.cs index 2f095cd9..ed149a4e 100644 --- a/tests/PlcSimulatorFixture.cs +++ b/tests/PlcSimulatorFixture.cs @@ -134,7 +134,7 @@ public async Task StartAsync() _serverCancellationTokenSource.Token) .GetAwaiter().GetResult()); - string endpointUrl = WaitForServerUp(); + string endpointUrl = await WaitForServerUpAsync().ConfigureAwait(false); await _log.WriteAsync($"Found server at: {endpointUrl}").ConfigureAwait(false); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) @@ -284,7 +284,7 @@ private ConfiguredEndpoint GetServerEndpoint(string endpointUrl) } } - private string WaitForServerUp() + private async Task WaitForServerUpAsync() { while (true) { @@ -295,13 +295,13 @@ private string WaitForServerUp() if (_serverTask.IsCompleted) { - throw new Exception("Server failed to start"); + throw new Exception("Server failed to start."); } if (!Program.Ready) { - _log.WriteLine("Waiting for server to start..."); - Thread.Sleep(1000); + _log.WriteLine("Waiting for server to start ..."); + await Task.Delay(1000).ConfigureAwait(false); continue; } diff --git a/tests/SimulatorTestsBase.cs b/tests/SimulatorTestsBase.cs index 6cd050a9..ff1ac262 100644 --- a/tests/SimulatorTestsBase.cs +++ b/tests/SimulatorTestsBase.cs @@ -48,7 +48,7 @@ public async Task Setup() [OneTimeTearDown] public async Task TearDown() { - Session.Close(); + Session?.Close(); await _simulator.StopAsync().ConfigureAwait(false); } diff --git a/tests/opc-plc-tests.csproj b/tests/opc-plc-tests.csproj index 663a19d9..cbcbc22f 100644 --- a/tests/opc-plc-tests.csproj +++ b/tests/opc-plc-tests.csproj @@ -4,6 +4,7 @@ net8.0 OpcPlc.Tests false + Preview diff --git a/version.json b/version.json index e9bc44b9..5a19e948 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.9.15", + "version": "2.10.0", "versionHeightOffset": -1, "publicReleaseRefSpec": [ "^refs/heads/main$",