fix(ablegacy): final review I3 — default reverts to libplctag; Native is opt-in
On a wire the native PCCC path is proven only for N-file reads/writes and one F8 read (ab_server). An absent transport key now resolves to Libplctag for every family; explicit Native still wins and still warns once per instance on PLC-5/LogixPccc. AdminUI dropdown, CLI help, evidence claims (PcccFileType, AbLegacy.md wire-evidence table, release note) corrected: S:0 (default probe) and B/T/C/R/S/L/ST/A/O/I are unverified natively. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -27,8 +27,9 @@ public abstract class AbLegacyCommandBase : DriverCommandBase
|
||||
|
||||
/// <summary>Gets the transport stack used to reach the PLC.</summary>
|
||||
[CommandOption("transport", Description =
|
||||
"Transport stack: default, native or libplctag. default = native, except libplctag for --plc-type " +
|
||||
"Plc5 / LogixPccc (native there is PLC-5 N-file reads only, no status-bit writes, LogixPccc unverified).")]
|
||||
"Transport stack: default, native or libplctag. default = libplctag for every --plc-type; native is " +
|
||||
"opt-in (wire-proven only for N-file reads/writes and an F8 read; PLC-5 native is N-file reads only, " +
|
||||
"LogixPccc unverified).")]
|
||||
public AbLegacyCliTransport Transport { get; init; } = AbLegacyCliTransport.Default;
|
||||
|
||||
/// <summary>Gets the per-operation timeout in milliseconds.</summary>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Pccc;
|
||||
|
||||
/// <summary>PCCC file-type codes for the protected typed logical read/write commands (0xA2/0xAA).
|
||||
/// N (0x89) and F (0x8A) are verified against the golden captures; the rest follow the PCCC
|
||||
/// data-table file-type table.</summary>
|
||||
/// <para>Wire evidence (final review I3), all against the <c>ab_server</c> simulator, never a real PLC:
|
||||
/// N (0x89) — byte parity with libplctag captures (SLC 500 read + write, MicroLogix read, PLC-5 read) and
|
||||
/// native ab_server round trips (N7:0 read on all three profiles, N7:5 write on SLC 500); F (0x8A) — one
|
||||
/// SLC 500 F8 read capture only (no native write on a wire).</para>
|
||||
/// <para>S, B, T, C, R, ST, A, L, O and I are encoded from the PCCC data-table file-type table and are
|
||||
/// UNVERIFIED natively — no capture and no simulator test. That includes S:0, the default probe address,
|
||||
/// and the T/C/R sub-element (PRE/ACC/LEN/POS) word offsets.</para></summary>
|
||||
public static class PcccFileType
|
||||
{
|
||||
/// <summary>Returns the file-type code for a file letter (case-insensitive), or null when unknown.</summary>
|
||||
|
||||
@@ -27,9 +27,9 @@ public sealed class AbLegacyDriverOptions
|
||||
|
||||
/// <summary>
|
||||
/// Which transport stack the driver, its Test Connect probe and the CLI use. <c>null</c> (the
|
||||
/// <c>transport</c> key absent) means "use the default": <see cref="AbLegacyTransport.Native"/>, UNLESS
|
||||
/// any configured device is a PLC-5 or LogixPccc (limited native support), in which case the whole
|
||||
/// driver resolves to <see cref="AbLegacyTransport.Libplctag"/>. An explicit value always wins. The
|
||||
/// <c>transport</c> key absent) means "use the default": <see cref="AbLegacyTransport.Libplctag"/> for
|
||||
/// every family. <see cref="AbLegacyTransport.Native"/> is opt-in (final review I3: on a wire it is proven
|
||||
/// only for N-file reads/writes and one F8 read). An explicit value always wins. The
|
||||
/// effective transport is resolved in one place, <c>AbLegacyTransportResolver</c>.
|
||||
/// <para>Changing this takes effect when the driver instance is RECREATED, not on
|
||||
/// <c>ReinitializeAsync</c>: the tag factory is chosen once in the constructor and held across
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
// 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 = AbLegacyTransportResolver.Resolve(options);
|
||||
_transportDescription = AbLegacyTransportResolver.Describe(options.Transport, _transport);
|
||||
_transportDescription = AbLegacyTransportResolver.Describe(options.Transport);
|
||||
_tagFactory = tagFactory ?? (_transport == AbLegacyTransport.Native
|
||||
? new NativeAbLegacyTagFactory()
|
||||
: new LibplctagLegacyTagFactory());
|
||||
@@ -103,7 +103,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
// from the node log which stack serves this instance.
|
||||
private readonly AbLegacyTransport _transport;
|
||||
|
||||
// Configured-vs-effective rendering of _transport for the init log, e.g. "Native (default)".
|
||||
// Configured-vs-effective rendering of _transport for the init log, e.g. "Libplctag (default)".
|
||||
private readonly string _transportDescription;
|
||||
|
||||
// Set once the explicit-Native-on-a-limited-family Warning has been logged for this instance.
|
||||
@@ -157,8 +157,8 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
if (HasConfigBody(driverConfigJson))
|
||||
_options = AbLegacyDriverFactoryExtensions.ParseOptions(_driverInstanceId, driverConfigJson);
|
||||
|
||||
// The resolver only defaults a PLC-5 / LogixPccc driver to libplctag; an operator may still pick
|
||||
// Native explicitly. Allowed, but the limitation is client-visible, so name it ONCE per instance —
|
||||
// Native is opt-in for every family (the default is libplctag); an operator may pick it explicitly
|
||||
// even for PLC-5 / LogixPccc. Allowed, but the limitation is client-visible, so name it ONCE per instance —
|
||||
// not on every init, which includes every comms-loss reconnect retry (Task 22 review r1 M4).
|
||||
if (!_limitedFamilyWarned
|
||||
&& _transport == AbLegacyTransport.Native
|
||||
|
||||
@@ -97,7 +97,7 @@ public static class AbLegacyDriverFactoryExtensions
|
||||
ProbeAddress = dto.Probe?.ProbeAddress ?? "S:0",
|
||||
},
|
||||
Timeout = PositiveTimeoutOrDefault(dto.TimeoutMs, 2_000),
|
||||
// Absent ⇒ null ("use the default", resolved by AbLegacyTransportResolver against the devices);
|
||||
// Absent ⇒ null ("use the default" = Libplctag, resolved by AbLegacyTransportResolver);
|
||||
// a bad value still fails exactly as any other bad enum field does.
|
||||
Transport = string.IsNullOrWhiteSpace(dto.Transport)
|
||||
? null
|
||||
@@ -153,7 +153,7 @@ public static class AbLegacyDriverFactoryExtensions
|
||||
public int? TimeoutMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transport (<c>Libplctag</c> or <c>Native</c>, case-insensitive); absent means Libplctag.
|
||||
/// Gets or sets the transport (<c>Libplctag</c> or <c>Native</c>, case-insensitive); absent means Libplctag (the default).
|
||||
/// </summary>
|
||||
public string? Transport { get; init; }
|
||||
|
||||
|
||||
@@ -4,16 +4,13 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
||||
|
||||
/// <summary>
|
||||
/// The ONE place an AbLegacy driver's effective transport is decided from its configured
|
||||
/// <see cref="AbLegacyDriverOptions.Transport"/> and its devices. The driver constructor, the Test
|
||||
/// Connect probe and the CLI (through the driver) all resolve through here.
|
||||
/// <para>An absent key means <see cref="AbLegacyTransport.Native"/> — UNLESS any configured device is a
|
||||
/// <see cref="AbLegacyPlcFamily.Plc5"/> or <see cref="AbLegacyPlcFamily.LogixPccc"/>, whose native support
|
||||
/// is limited (PLC-5: N-file reads only; LogixPccc: framing unverified). The tag factory is chosen once
|
||||
/// per DRIVER, not per device, so one such device resolves the whole driver to
|
||||
/// <see cref="AbLegacyTransport.Libplctag"/>. An explicit value always wins (an operator choice).</para>
|
||||
/// <para>The probe resolves from the config it is GIVEN. The AdminUI merges only the probed device into
|
||||
/// that config, so in a mixed-family defaulted driver Test Connect on an SLC device can resolve Native
|
||||
/// while the deployed driver resolves Libplctag (documented in docs/drivers/AbLegacy.md).</para>
|
||||
/// <see cref="AbLegacyDriverOptions.Transport"/>. The driver constructor, the Test Connect probe and the
|
||||
/// CLI (through the driver) all resolve through here.
|
||||
/// <para>An absent key means <see cref="Default"/> = <see cref="AbLegacyTransport.Libplctag"/> for EVERY
|
||||
/// family (final review I3): on a wire the native PCCC path is proven only for N-file reads and writes
|
||||
/// and one F8 read, so Native stays opt-in. An explicit value always wins (an operator choice). An
|
||||
/// explicit Native on a PLC-5 / LogixPccc device is allowed but logs a once-per-instance Warning
|
||||
/// (<see cref="NativeLimitedFamilies"/>).</para>
|
||||
/// </summary>
|
||||
public static class AbLegacyTransportResolver
|
||||
{
|
||||
@@ -32,16 +29,13 @@ public static class AbLegacyTransportResolver
|
||||
return [.. devices.Select(d => d.PlcFamily).Where(IsNativeLimited).Distinct()];
|
||||
}
|
||||
|
||||
/// <summary>Returns the effective transport for a configured value and device set.</summary>
|
||||
/// <summary>The transport an absent / null <c>transport</c> key selects (all families).</summary>
|
||||
public const AbLegacyTransport Default = AbLegacyTransport.Libplctag;
|
||||
|
||||
/// <summary>Returns the effective transport: an explicit value always wins; null means <see cref="Default"/>.</summary>
|
||||
/// <param name="configured">The configured transport, or null for "use the default".</param>
|
||||
/// <param name="devices">The configured devices.</param>
|
||||
/// <returns>The transport the driver runs on.</returns>
|
||||
public static AbLegacyTransport Resolve(AbLegacyTransport? configured, IEnumerable<AbLegacyDeviceOptions> devices)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(devices);
|
||||
if (configured is { } explicitValue) return explicitValue;
|
||||
return devices.Any(d => IsNativeLimited(d.PlcFamily)) ? AbLegacyTransport.Libplctag : AbLegacyTransport.Native;
|
||||
}
|
||||
public static AbLegacyTransport Resolve(AbLegacyTransport? configured) => configured ?? Default;
|
||||
|
||||
/// <summary>Returns the effective transport for <paramref name="options"/>.</summary>
|
||||
/// <param name="options">The driver options.</param>
|
||||
@@ -49,19 +43,15 @@ public static class AbLegacyTransportResolver
|
||||
public static AbLegacyTransport Resolve(AbLegacyDriverOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
return Resolve(options.Transport, options.Devices);
|
||||
return Resolve(options.Transport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operator-facing description of the configured-vs-effective transport for the init log line, e.g.
|
||||
/// <c>Native (default)</c>, <c>Libplctag (default; PLC-5/LogixPccc device present)</c> or
|
||||
/// <c>Native (configured)</c>.
|
||||
/// Operator-facing description of the configured-vs-effective transport for the init log line:
|
||||
/// <c>Libplctag (default)</c> when the key was absent, <c>Native (configured)</c> when explicit.
|
||||
/// </summary>
|
||||
/// <param name="configured">The configured transport, or null.</param>
|
||||
/// <param name="effective">The effective transport.</param>
|
||||
/// <returns>A short description.</returns>
|
||||
public static string Describe(AbLegacyTransport? configured, AbLegacyTransport effective) =>
|
||||
configured is not null ? $"{effective} (configured)"
|
||||
: effective == AbLegacyTransport.Libplctag ? $"{effective} (default; PLC-5/LogixPccc device present)"
|
||||
: $"{effective} (default)";
|
||||
public static string Describe(AbLegacyTransport? configured) =>
|
||||
configured is null ? $"{Resolve(configured)} (default)" : $"{configured} (configured)";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user