Files
lmxopcua/src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriverOptions.cs
2026-04-26 08:44:53 -04:00

114 lines
5.2 KiB
C#

using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
/// <summary>
/// AB Legacy (PCCC) driver configuration. One instance supports N devices (SLC 500 /
/// MicroLogix / PLC-5 / LogixPccc). Per plan decision #41 AbLegacy ships separately from
/// AbCip because PCCC's file-based addressing (<c>N7:0</c>) and Logix's symbolic addressing
/// (<c>Motor1.Speed</c>) pull the abstraction in different directions.
/// </summary>
public sealed class AbLegacyDriverOptions
{
public IReadOnlyList<AbLegacyDeviceOptions> Devices { get; init; } = [];
public IReadOnlyList<AbLegacyTagDefinition> Tags { get; init; } = [];
public AbLegacyProbeOptions Probe { get; init; } = new();
/// <summary>
/// Driver-wide default per-operation timeout. Applies to every device unless that device
/// overrides it via <see cref="AbLegacyDeviceOptions.Timeout"/> (PR 9).
/// </summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
/// <summary>
/// PR 9 — driver-wide default retry count for transient
/// <c>BadCommunicationError</c> reads. <c>null</c> ≡ <c>0</c> (single attempt). Applies
/// to every device unless that device overrides it via
/// <see cref="AbLegacyDeviceOptions.Retries"/>.
/// </summary>
public int? Retries { get; init; }
}
/// <summary>
/// Per-device options for the AB Legacy driver. PR 9 added optional <see cref="Timeout"/>
/// and <see cref="Retries"/> overrides — chassis families have very different per-operation
/// latency floors (SLC 5/01 RS-232 ~5 s; SLC 5/05 ~2 s; ML1100 ~3 s) so a single driver-wide
/// timeout always misfires on at least one device. Both fields are optional and fall back
/// to the driver-wide default on <see cref="AbLegacyDriverOptions"/>.
/// </summary>
public sealed record AbLegacyDeviceOptions(
string HostAddress,
AbLegacyPlcFamily PlcFamily = AbLegacyPlcFamily.Slc500,
string? DeviceName = null,
TimeSpan? Timeout = null,
int? Retries = null,
AbLegacyDemoteOptions? Demote = null);
/// <summary>
/// PR ablegacy-12 / #255 — auto-demote knobs. After
/// <see cref="FailureThreshold"/> consecutive read / probe failures the driver
/// marks the device <c>Demoted</c> for <see cref="DemoteFor"/>; reads against
/// a demoted device short-circuit with <c>BadCommunicationError</c> instead
/// of dispatching through libplctag, so one slow PLC can't starve faster
/// peers sharing the same driver. A successful probe clears the demotion
/// early; a successful read just resets the consecutive-failure counter
/// without leaving the demoted window.
/// </summary>
/// <param name="FailureThreshold">Consecutive read or probe failures that trip
/// the demotion. Default <c>3</c>.</param>
/// <param name="DemoteFor">Cool-down window before reads are dispatched again
/// without a successful probe in between. Default <c>30s</c>.</param>
/// <param name="Enabled">When <c>false</c> the failure tally still ticks but the
/// driver never sets the demoted window — useful when an operator wants the
/// diagnostic counters without the throttling behaviour.</param>
public sealed record AbLegacyDemoteOptions(
int FailureThreshold = 3,
TimeSpan? DemoteFor = null,
bool Enabled = true)
{
/// <summary>
/// Effective demote window. Records can't have <c>TimeSpan</c> defaults
/// because <c>TimeSpan.FromSeconds(30)</c> isn't a compile-time constant;
/// callers that pass <c>null</c> get the documented 30-second default
/// here.
/// </summary>
public TimeSpan EffectiveDemoteFor => DemoteFor ?? TimeSpan.FromSeconds(30);
}
/// <summary>
/// One PCCC-backed OPC UA variable. <c>Address</c> is the canonical PCCC file-address
/// string that parses via <see cref="AbLegacyAddress.TryParse(string?)"/>.
/// </summary>
/// <remarks>
/// PR 8 deadband fields:
/// <list type="bullet">
/// <item><c>AbsoluteDeadband</c> — when set, suppresses <c>OnDataChange</c> for numeric
/// tags unless <c>|new - prev| &gt;= AbsoluteDeadband</c>.</item>
/// <item><c>PercentDeadband</c> — when set, suppresses unless
/// <c>|new - prev| &gt;= |prev * Percent / 100|</c>; <c>prev == 0</c> always publishes.</item>
/// </list>
/// Booleans bypass deadband entirely (every transition publishes); strings + status
/// changes always publish; first-seen always publishes; both set → logical-OR (Kepware
/// semantics).
/// </remarks>
public sealed record AbLegacyTagDefinition(
string Name,
string DeviceHostAddress,
string Address,
AbLegacyDataType DataType,
bool Writable = true,
bool WriteIdempotent = false,
int? ArrayLength = null,
double? AbsoluteDeadband = null,
double? PercentDeadband = null);
public sealed class AbLegacyProbeOptions
{
public bool Enabled { get; init; } = true;
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
/// <summary>Probe address — defaults to <c>S:0</c> (status file, first word) when null.</summary>
public string? ProbeAddress { get; init; } = "S:0";
}