feat(adminui): Transport dropdown on AbCip/AbLegacy driver forms (live-verified on docker-dev)
- AbCip/AbLegacy driver forms gain a Transport InputSelect; FromOptions/ToOptions now carry Transport, so saving any other field no longer silently reverts Native to libplctag. - Both drivers log the constructed transport once per init. - AbCip factory now takes the host ILoggerFactory (it ran on NullLogger in production, hiding every AbCip log line); DriverFactoryBootstrap passes it. - Gate results: AdminUI /run (T19) native, inverted (dead port) and libplctag legs.
This commit is contained in:
@@ -152,6 +152,7 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
_driverInstanceId = driverInstanceId;
|
||||
// Chosen once here and held across ReinitializeAsync (Shutdown+Initialize): a changed
|
||||
// Transport in a reinit config is inert; a deploy recreates the instance instead.
|
||||
_transport = options.Transport;
|
||||
var defaults = AbCipTransportFactories.For(options.Transport);
|
||||
_tagFactory = tagFactory ?? defaults.Tags;
|
||||
_enumeratorFactory = enumeratorFactory ?? defaults.Enumerators;
|
||||
@@ -178,6 +179,10 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
/// <summary>The tag factory chosen at construction; exposed so tests can pin transport selection.</summary>
|
||||
internal IAbCipTagFactory TagFactory => _tagFactory;
|
||||
|
||||
// The transport chosen at construction (see the ctor); logged at every init so an operator can tell
|
||||
// from the node log which stack serves this instance.
|
||||
private readonly AbCipTransport _transport;
|
||||
|
||||
/// <summary>Upper bound on the poll-loop failure backoff — adopts the S7-proven 30 s cap fleet-wide (05/STAB-8).</summary>
|
||||
private static readonly TimeSpan PollBackoffCap = TimeSpan.FromSeconds(30);
|
||||
|
||||
@@ -282,6 +287,9 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
_health = new DriverHealth(DriverState.Initializing, null, null);
|
||||
_logger.LogInformation(
|
||||
"AbCip driver {DriverInstanceId} initializing: transport={Transport} tagFactory={TagFactory}",
|
||||
_driverInstanceId, _transport, _tagFactory.GetType().Name);
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(driverConfigJson))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
|
||||
|
||||
@@ -15,13 +16,18 @@ public static class AbCipDriverFactoryExtensions
|
||||
public const string DriverTypeName = "AbCip";
|
||||
|
||||
/// <summary>
|
||||
/// Registers the AB CIP driver factory with the driver registry.
|
||||
/// Registers the AB CIP driver factory with the driver registry. The optional
|
||||
/// <paramref name="loggerFactory"/> is captured at registration time and used to construct an
|
||||
/// <see cref="ILogger{AbCipDriver}"/> per driver instance — without it the driver runs with the
|
||||
/// null logger, so none of its diagnostics (including the per-init transport line) reach the
|
||||
/// node log. Mirrors the AbLegacy / Modbus registration pattern.
|
||||
/// </summary>
|
||||
/// <param name="registry">The driver factory registry to register with.</param>
|
||||
public static void Register(DriverFactoryRegistry registry)
|
||||
/// <param name="loggerFactory">Optional logger factory for driver instances.</param>
|
||||
public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
registry.Register(DriverTypeName, CreateInstance);
|
||||
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,15 +37,24 @@ public static class AbCipDriverFactoryExtensions
|
||||
/// <param name="driverConfigJson">The driver configuration as a JSON string.</param>
|
||||
/// <returns>A configured <see cref="AbCipDriver"/> instance.</returns>
|
||||
internal static AbCipDriver CreateInstance(string driverInstanceId, string driverConfigJson)
|
||||
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null);
|
||||
|
||||
/// <summary>Creates an instance of the AB CIP driver with an optional logger.</summary>
|
||||
/// <param name="driverInstanceId">The unique identifier for this driver instance.</param>
|
||||
/// <param name="driverConfigJson">The driver configuration as a JSON string.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for the driver instance.</param>
|
||||
/// <returns>A configured <see cref="AbCipDriver"/> instance.</returns>
|
||||
internal static AbCipDriver CreateInstance(
|
||||
string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
|
||||
var options = ParseOptions(driverInstanceId, driverConfigJson);
|
||||
return new AbCipDriver(options, driverInstanceId);
|
||||
return new AbCipDriver(options, driverInstanceId, logger: loggerFactory?.CreateLogger<AbCipDriver>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialise an AB CIP driver-config JSON document into <see cref="AbCipDriverOptions"/>.
|
||||
/// Shared by <see cref="CreateInstance"/> (first construction) and
|
||||
/// Shared by <see cref="CreateInstance(string, string, ILoggerFactory?)"/> (first construction) and
|
||||
/// <see cref="AbCipDriver.InitializeAsync"/> / <see cref="AbCipDriver.ReinitializeAsync"/>
|
||||
/// so a reinitialize with a changed config JSON (new device, new tag, changed timeout)
|
||||
/// actually takes effect rather than being silently discarded.
|
||||
|
||||
@@ -70,6 +70,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
_driverInstanceId = driverInstanceId;
|
||||
// Chosen once here and held across ReinitializeAsync (Shutdown+Initialize): a changed
|
||||
// Transport in a reinit config is inert; a deploy recreates the instance instead.
|
||||
_transport = options.Transport;
|
||||
_tagFactory = tagFactory ?? (options.Transport == AbLegacyTransport.Native
|
||||
? new NativeAbLegacyTagFactory()
|
||||
: new LibplctagLegacyTagFactory());
|
||||
@@ -92,6 +93,10 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
/// <summary>The tag factory chosen at construction; exposed so tests can pin transport selection.</summary>
|
||||
internal IAbLegacyTagFactory TagFactory => _tagFactory;
|
||||
|
||||
// The transport chosen at construction (see the ctor); logged at every init so an operator can tell
|
||||
// from the node log which stack serves this instance.
|
||||
private readonly AbLegacyTransport _transport;
|
||||
|
||||
/// <summary>Upper bound on the poll-loop failure backoff — adopts the S7-proven 30 s cap fleet-wide (05/STAB-8).</summary>
|
||||
private static readonly TimeSpan PollBackoffCap = TimeSpan.FromSeconds(30);
|
||||
|
||||
@@ -129,6 +134,9 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
_health = new DriverHealth(DriverState.Initializing, null, null);
|
||||
_logger.LogInformation(
|
||||
"AbLegacy driver {DriverInstanceId} initializing: transport={Transport} tagFactory={TagFactory}",
|
||||
_driverInstanceId, _transport, _tagFactory.GetType().Name);
|
||||
try
|
||||
{
|
||||
// #516: re-parse the supplied DriverConfig so a config change delivered through the IDriver
|
||||
|
||||
Reference in New Issue
Block a user