mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 22:55:08 +08:00
d525bc3f28
# Description This PR fixes bounded ManagedSession reconnect cleanup and reorganizes the sample applications into domain-focused folders. ## Reconnect cleanup - Bounds reconnect integration cleanup with independent 30-second close and dispose limits. - Sends `CloseSession` through the awaited transport request path during `Session.DisposeAsync`. - Preserves explicit `Session.CloseAsync` behavior that waits for or cancels outstanding publish requests. - Covers the disposal request path with a regression test. ## Sample organization - `samples/OpenUsd`: `GeneratorServer`, `SiteCompositionServer` - `samples/Redundancy`: `RedundantClient`, `RedundantServer`, `RedundantPubSub` - `samples/Isa95`: `MinimalIsa95Server` - `samples/MinimalApi`: `MinimalBoilerServer`, `MinimalCalcServer`, `MinimalClient` - `samples/DI`: `PumpDeviceIntegrationServer` - `samples/Lds`: `ConsoleLdsServer` - `samples/Reference`: `ConsoleReferenceClient`, `ConsoleReferenceServer` - `samples/PubSub`: `ConsoleReferencePubSubClient` `UA.slnx` now exposes the same requested domain groups. Project references, tests, runtime sample probes, CI workflows, Dockerfiles, Compose files, helper scripts, and documentation were updated to the new paths. `samples/Robotics/MinimalRobotServer` remains under `Robotics`. ## Validation - `dotnet build UA.slnx` - 3 Redundancy short-haul sample tests - 70 DI sample tests - 914 OpenUSD tests - 1 WotCon model-materialization test - 137 AOT tests - `docker compose config` for all 6 moved Redundancy Compose files ## Related Issues - Fixes #4273 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce new build or analyzer warnings. - [x] I built the complete `UA.slnx` solution locally. - [ ] I ran all tests locally against at least .NET Framework and .NET 10. - [ ] I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings. - [x] I have addressed all PR feedback received. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <mschier@microsoft.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Copilot-Session: a2c2838f-74ae-4997-a1d4-50574d9e0636
139 lines
5.2 KiB
C#
139 lines
5.2 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 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.Tasks;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Di;
|
|
using Opc.Ua.Di.Server.Builders;
|
|
using Opc.Ua.Pumps;
|
|
using Opc.Ua.Server.Fluent;
|
|
using Pumps;
|
|
|
|
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
|
|
|
builder.Logging.ClearProviders();
|
|
builder.Logging.AddConsole();
|
|
|
|
int port = int.TryParse(builder.Configuration["port"], out int p) ? p : 62542;
|
|
|
|
if (!TryReadPumpCount(builder.Configuration["pumps"], out int pumpCount, out string? pumpError))
|
|
{
|
|
Console.Error.WriteLine(pumpError);
|
|
return 2;
|
|
}
|
|
|
|
// Bind host for the OPC UA endpoint. Defaults to 0.0.0.0 so the server is
|
|
// reachable from outside a container; override with --host / host env var
|
|
// (e.g. "localhost" for local-only development).
|
|
string host = builder.Configuration["host"] is { Length: > 0 } h ? h : "0.0.0.0";
|
|
|
|
builder.Services.Configure<PumpDeviceIntegrationOptions>(options =>
|
|
{
|
|
options.PumpCount = pumpCount;
|
|
});
|
|
|
|
builder.Services
|
|
.AddOpcUa()
|
|
.AddServer(o =>
|
|
{
|
|
o.ApplicationName = "PumpDeviceIntegrationServer";
|
|
o.ApplicationUri = "urn:localhost:OPCFoundation:PumpDeviceIntegrationServer";
|
|
o.ProductUri = "uri:opcfoundation.org:PumpDeviceIntegrationServer";
|
|
// Sample convenience only; never auto-accept untrusted certificates in production.
|
|
o.AutoAcceptUntrustedCertificates = true;
|
|
o.PkiRoot = Path.Combine(AppContext.BaseDirectory, "pki");
|
|
o.RejectSHA1Certificates = true;
|
|
o.MinCertificateKeySize = 2048;
|
|
o.EndpointUrls.Add($"opc.tcp://{host}:{port}/PumpDeviceIntegrationServer");
|
|
})
|
|
.AddNodeManager<PumpNodeManagerFactory>()
|
|
// Demonstrate the declarative DI topology-element builder after the
|
|
// node manager has materialised and fluently wired every pump. The
|
|
// ad-hoc Diagnostics group is added identically to each pump.
|
|
.ConfigureDevicesFor<PumpNodeManager>(ctx =>
|
|
{
|
|
var manager = (PumpNodeManager)ctx.Manager;
|
|
foreach (NodeId pumpNodeId in manager.PumpNodeIds)
|
|
{
|
|
ITopologyElementBuilder<PumpState> pump =
|
|
ctx.TopologyElement<PumpState>(pumpNodeId);
|
|
|
|
pump.WithFunctionalGroup(
|
|
new QualifiedName("Diagnostics", ctx.Manager.InstanceNamespaceIndex),
|
|
fg => fg.Configure(node =>
|
|
node.WithProperty("LastError", Variant.From(string.Empty), p => p.Writable())
|
|
.WithProperty("ErrorCount", 0)
|
|
.WithProperty("LastSelfTest", (DateTimeUtc)DateTime.UtcNow)));
|
|
}
|
|
|
|
return new ValueTask();
|
|
});
|
|
|
|
await builder.Build().RunAsync().ConfigureAwait(false);
|
|
return 0;
|
|
|
|
static bool TryReadPumpCount(string? value, out int pumpCount, out string? error)
|
|
{
|
|
const int minPumpCount = 1;
|
|
const int maxPumpCount = 100;
|
|
|
|
pumpCount = 2;
|
|
error = null;
|
|
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed))
|
|
{
|
|
error = "Invalid --pumps value '" + value + "'. Specify an integer between " +
|
|
minPumpCount.ToString(CultureInfo.InvariantCulture) + " and " +
|
|
maxPumpCount.ToString(CultureInfo.InvariantCulture) + ".";
|
|
return false;
|
|
}
|
|
|
|
if (parsed < minPumpCount || parsed > maxPumpCount)
|
|
{
|
|
error = "Invalid --pumps value '" + value + "'. Specify an integer between " +
|
|
minPumpCount.ToString(CultureInfo.InvariantCulture) + " and " +
|
|
maxPumpCount.ToString(CultureInfo.InvariantCulture) + ".";
|
|
return false;
|
|
}
|
|
|
|
pumpCount = parsed;
|
|
return true;
|
|
}
|