431 lines
27 KiB
C#
431 lines
27 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||
|
||
/// <summary>
|
||
/// AB CIP / EtherNet-IP driver configuration, bound from the driver's <c>DriverConfig</c>
|
||
/// JSON at <c>DriverHost.RegisterAsync</c>. One instance supports N devices (PLCs) behind
|
||
/// the same driver; per-device routing is keyed on <see cref="AbCipDeviceOptions.HostAddress"/>
|
||
/// via <c>IPerCallHostResolver</c>.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Per v2 plan decisions #11 (libplctag), #41 (AbCip vs AbLegacy split), #143–144 (per-call
|
||
/// host resolver + resilience keys), #144 (bulkhead keyed on <c>(DriverInstanceId, HostName)</c>).
|
||
/// </remarks>
|
||
public sealed class AbCipDriverOptions
|
||
{
|
||
/// <summary>
|
||
/// PLCs this driver instance talks to. Each device contributes its own <see cref="AbCipHostAddress"/>
|
||
/// string as the <c>hostName</c> key used by resilience pipelines and the Admin UI.
|
||
/// </summary>
|
||
public IReadOnlyList<AbCipDeviceOptions> Devices { get; init; } = [];
|
||
|
||
/// <summary>Pre-declared tag map across all devices — AB discovery lands in PR 5.</summary>
|
||
public IReadOnlyList<AbCipTagDefinition> Tags { get; init; } = [];
|
||
|
||
/// <summary>
|
||
/// L5K (Studio 5000 controller export) imports merged into <see cref="Tags"/> at
|
||
/// <c>InitializeAsync</c>. Each entry points at one L5K file + the device whose tags it
|
||
/// describes; the parser extracts <c>TAG</c> + <c>DATATYPE</c> blocks and produces
|
||
/// <see cref="AbCipTagDefinition"/> records (alias tags + ExternalAccess=None tags
|
||
/// skipped — see <see cref="Import.L5kIngest"/>). Pre-declared <see cref="Tags"/> entries
|
||
/// win on <c>Name</c> conflicts so operators can override import results without
|
||
/// editing the L5K source.
|
||
/// </summary>
|
||
public IReadOnlyList<AbCipL5kImportOptions> L5kImports { get; init; } = [];
|
||
|
||
/// <summary>
|
||
/// L5X (Studio 5000 XML controller export) imports merged into <see cref="Tags"/> at
|
||
/// <c>InitializeAsync</c>. Same shape and merge semantics as <see cref="L5kImports"/> —
|
||
/// the entries differ only in source format. Pre-declared <see cref="Tags"/> entries win
|
||
/// on <c>Name</c> conflicts; entries already produced by <see cref="L5kImports"/> also win
|
||
/// so an L5X re-export of the same controller doesn't double-emit. See
|
||
/// <see cref="Import.L5xParser"/> for the format-specific mechanics.
|
||
/// </summary>
|
||
public IReadOnlyList<AbCipL5xImportOptions> L5xImports { get; init; } = [];
|
||
|
||
/// <summary>
|
||
/// Kepware-format CSV imports merged into <see cref="Tags"/> at <c>InitializeAsync</c>.
|
||
/// Same merge semantics as <see cref="L5kImports"/> / <see cref="L5xImports"/> —
|
||
/// pre-declared <see cref="Tags"/> entries win on <c>Name</c> conflicts, and tags
|
||
/// produced by earlier import collections (L5K → L5X → CSV in call order) also win
|
||
/// so an Excel-edited copy of the same controller does not double-emit. See
|
||
/// <see cref="Import.CsvTagImporter"/> for the column layout + parse rules.
|
||
/// </summary>
|
||
public IReadOnlyList<AbCipCsvImportOptions> CsvImports { get; init; } = [];
|
||
|
||
/// <summary>Per-device probe settings. Falls back to defaults when omitted.</summary>
|
||
public AbCipProbeOptions Probe { get; init; } = new();
|
||
|
||
/// <summary>
|
||
/// Default libplctag call timeout applied to reads/writes/discovery when the caller does
|
||
/// not pass a more specific value. Matches the Modbus driver's 2-second default.
|
||
/// </summary>
|
||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||
|
||
/// <summary>
|
||
/// When <c>true</c>, <c>DiscoverAsync</c> walks each device's Logix symbol table via
|
||
/// the <c>@tags</c> pseudo-tag + surfaces controller-resident globals under a
|
||
/// <c>Discovered/</c> sub-folder. Pre-declared tags always emit regardless. Default
|
||
/// <c>false</c> to keep the strict-config path for deployments where only declared tags
|
||
/// should appear in the address space.
|
||
/// </summary>
|
||
public bool EnableControllerBrowse { get; init; }
|
||
|
||
/// <summary>
|
||
/// Task #177 — when <c>true</c>, declared ALMD tags are surfaced as alarm conditions
|
||
/// via <see cref="Core.Abstractions.IAlarmSource"/>; the driver polls each subscribed
|
||
/// alarm's <c>InFaulted</c> + <c>Severity</c> members + fires <c>OnAlarmEvent</c> on
|
||
/// state transitions. Default <c>false</c> — operators explicitly opt in because
|
||
/// projection semantics don't exactly mirror Rockwell FT Alarm & Events; shops
|
||
/// running FT Live should keep this off + take alarms through the native route.
|
||
/// </summary>
|
||
public bool EnableAlarmProjection { get; init; }
|
||
|
||
/// <summary>
|
||
/// Poll interval for the ALMD projection loop. Shorter intervals catch faster edges
|
||
/// at the cost of PLC round-trips; edges shorter than this interval are invisible to
|
||
/// the projection (a 0→1→0 transition within one tick collapses to no event). Default
|
||
/// 1 second — matches typical SCADA alarm-refresh conventions.
|
||
/// </summary>
|
||
public TimeSpan AlarmPollInterval { get; init; } = TimeSpan.FromSeconds(1);
|
||
|
||
/// <summary>
|
||
/// PR abcip-3.1 — optional sink for non-fatal driver warnings (legacy-firmware
|
||
/// <c>ConnectionSize</c> mis-match, etc.). Production hosting wires this to Serilog;
|
||
/// unit tests pin a list-collecting lambda to assert which warnings fired. <c>null</c>
|
||
/// swallows warnings — convenient for back-compat deployments that don't care.
|
||
/// </summary>
|
||
public Action<string>? OnWarning { get; init; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// One PLC endpoint. <see cref="HostAddress"/> must parse via
|
||
/// <see cref="AbCipHostAddress.TryParse"/>; misconfigured devices fail driver
|
||
/// initialization rather than silently connecting to nothing.
|
||
/// </summary>
|
||
/// <param name="HostAddress">Canonical <c>ab://gateway[:port]/cip-path</c> string.</param>
|
||
/// <param name="PlcFamily">Which per-family profile to apply. Determines ConnectionSize,
|
||
/// request-packing support, unconnected-only hint, and other quirks.</param>
|
||
/// <param name="DeviceName">Optional display label for Admin UI. Falls back to <see cref="HostAddress"/>.</param>
|
||
/// <param name="ConnectionSize">PR abcip-3.1 — optional override for the family-default
|
||
/// <see cref="PlcFamilies.AbCipPlcFamilyProfile.DefaultConnectionSize"/>. Threads through to
|
||
/// libplctag's <c>connection_size</c> attribute on the underlying tag handle so operators can
|
||
/// dial the CIP Forward Open buffer down for legacy firmware (v19-and-earlier ControlLogix
|
||
/// caps at 504) or up for high-throughput shops on FW20+. Validated against the Kepware
|
||
/// supported range [500..4002] at <c>InitializeAsync</c>; out-of-range values fault the
|
||
/// driver. <c>null</c> uses the family default — back-compat with deployments that haven't
|
||
/// touched the knob.</param>
|
||
/// <param name="AddressingMode">PR abcip-3.2 — controls whether the driver addresses tags by
|
||
/// ASCII symbolic path (the default), by CIP logical-segment instance ID, or asks the driver
|
||
/// to pick. Logical addressing skips per-poll ASCII parsing on every read and unlocks
|
||
/// symbol-table-cached scans for 500+-tag projects, but requires a one-time symbol-table
|
||
/// walk at first read + is unsupported on Micro800 / SLC500 / PLC5 (their CIP firmware does
|
||
/// not honour Symbol Object instance IDs). When the user picks <see cref="AbCip.AddressingMode.Logical"/>
|
||
/// against an unsupported family the driver logs a warning + falls back to symbolic so
|
||
/// misconfiguration does not fault the driver. <see cref="AbCip.AddressingMode.Auto"/> currently
|
||
/// resolves to symbolic — a future PR will plumb a real auto-detection heuristic; the docs
|
||
/// in <c>docs/drivers/AbCip-Performance.md</c> §"Addressing mode" call this out.</param>
|
||
/// <param name="ReadStrategy">PR abcip-3.3 — picks how a multi-member UDT batch is read on this
|
||
/// device. <see cref="AbCip.ReadStrategy.WholeUdt"/> issues one read per parent UDT and decodes
|
||
/// each subscribed member from the buffer in-memory (the historical behaviour that ships in
|
||
/// task #194 — best when a large fraction of a UDT's members are subscribed).
|
||
/// <see cref="AbCip.ReadStrategy.MultiPacket"/> bundles per-member reads into one CIP
|
||
/// Multi-Service Packet — best for sparse UDT subscriptions where reading the whole UDT
|
||
/// buffer just to extract one or two fields wastes wire bandwidth. <see cref="AbCip.ReadStrategy.Auto"/>
|
||
/// (the default) lets the planner pick per-batch using
|
||
/// <paramref name="MultiPacketSparsityThreshold"/>: if the subscribed-member fraction is below
|
||
/// the threshold MultiPacket wins, otherwise WholeUdt wins. Family compatibility — Micro800 /
|
||
/// SLC500 / PLC5 lack Multi-Service-Packet support per
|
||
/// <see cref="PlcFamilies.AbCipPlcFamilyProfile.SupportsRequestPacking"/>; user-forced
|
||
/// <see cref="AbCip.ReadStrategy.MultiPacket"/> against those families logs a warning + falls
|
||
/// back to <see cref="AbCip.ReadStrategy.WholeUdt"/> at device-init time. The libplctag .NET
|
||
/// wrapper (1.5.x) does not expose a public knob for explicit Multi-Service-Packet bundling,
|
||
/// so today's MultiPacket runtime issues one libplctag read per member; the planner's grouping
|
||
/// is still load-bearing because it gives the runtime the right plan to execute when an
|
||
/// upstream wrapper release exposes wire-level bundling.</param>
|
||
/// <param name="MultiPacketSparsityThreshold">PR abcip-3.3 — sparsity-threshold knob the planner
|
||
/// uses when <paramref name="ReadStrategy"/> is <see cref="AbCip.ReadStrategy.Auto"/>. The
|
||
/// planner divides <c>subscribedMembers / totalMembers</c> for each parent UDT in a batch;
|
||
/// a fraction strictly less than the threshold picks
|
||
/// <see cref="AbCip.ReadStrategy.MultiPacket"/>, else <see cref="AbCip.ReadStrategy.WholeUdt"/>.
|
||
/// Default <c>0.25</c> — picked because reading 1/4 of a UDT's members is the rough break-even
|
||
/// where the wire-cost of one whole-UDT read still beats N member reads on ControlLogix's
|
||
/// 4002-byte connection size; see <c>docs/drivers/AbCip-Performance.md</c> §"Read strategy".
|
||
/// Clamped to <c>[0..1]</c> at planner time; values outside the range silently saturate.</param>
|
||
public sealed record AbCipDeviceOptions(
|
||
string HostAddress,
|
||
AbCipPlcFamily PlcFamily = AbCipPlcFamily.ControlLogix,
|
||
string? DeviceName = null,
|
||
int? ConnectionSize = null,
|
||
AddressingMode AddressingMode = AddressingMode.Auto,
|
||
ReadStrategy ReadStrategy = ReadStrategy.Auto,
|
||
double MultiPacketSparsityThreshold = 0.25);
|
||
|
||
/// <summary>
|
||
/// PR abcip-3.3 — per-device strategy for reading multi-member UDT batches. <see cref="WholeUdt"/>
|
||
/// mirrors the task #194 behaviour: one libplctag read on the parent tag, each subscribed member
|
||
/// decoded from the buffer at its computed offset. <see cref="MultiPacket"/> bundles per-member
|
||
/// reads into one CIP Multi-Service Packet so sparse UDT subscriptions don't pay for the whole
|
||
/// UDT buffer. <see cref="Auto"/> lets the planner pick per-batch using
|
||
/// <see cref="AbCipDeviceOptions.MultiPacketSparsityThreshold"/>.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>Strategy resolution lives at two layers:</para>
|
||
/// <list type="bullet">
|
||
/// <item><b>Device init</b> — user-forced <see cref="MultiPacket"/> against a family whose
|
||
/// profile sets <see cref="PlcFamilies.AbCipPlcFamilyProfile.SupportsRequestPacking"/>
|
||
/// = <c>false</c> (Micro800, SLC500, PLC5) falls back to <see cref="WholeUdt"/> with a
|
||
/// warning. <see cref="Auto"/> stays as-is (the planner re-evaluates per batch).</item>
|
||
/// <item><b>Per-batch (Auto only)</b> — for each parent UDT in the request set, the planner
|
||
/// computes <c>subscribedMembers / totalMembers</c> and routes the group through
|
||
/// <see cref="MultiPacket"/> when the fraction is below the threshold, else
|
||
/// <see cref="WholeUdt"/>.</item>
|
||
/// </list>
|
||
/// <para>libplctag .NET wrapper (1.5.x) does not expose explicit Multi-Service-Packet bundling,
|
||
/// so today's runtime issues one libplctag read per member when the planner picks MultiPacket —
|
||
/// the same wrapper limitation called out in PR abcip-3.1 (ConnectionSize) and PR abcip-3.2
|
||
/// (instance-ID addressing). The planner's grouping is still observable from tests + future-proofs
|
||
/// the driver for when an upstream wrapper release exposes wire-level bundling.</para>
|
||
/// </remarks>
|
||
public enum ReadStrategy
|
||
{
|
||
/// <summary>Driver picks per-batch based on
|
||
/// <see cref="AbCipDeviceOptions.MultiPacketSparsityThreshold"/>. Default.</summary>
|
||
Auto = 0,
|
||
|
||
/// <summary>One read per parent UDT; members decoded from the buffer in-memory. Best when a
|
||
/// large fraction of the UDT's members are subscribed (dense reads).</summary>
|
||
WholeUdt = 1,
|
||
|
||
/// <summary>Bundle per-member reads into one CIP Multi-Service Packet. Best when only a few
|
||
/// members of a large UDT are subscribed (sparse reads). Unsupported on Micro800 / SLC500 /
|
||
/// PLC5; the driver warns + falls back to <see cref="WholeUdt"/> at device init.</summary>
|
||
MultiPacket = 2,
|
||
}
|
||
|
||
/// <summary>
|
||
/// PR abcip-3.2 — how the AB CIP driver addresses tags on a given device. <see cref="Symbolic"/>
|
||
/// is the historical default + matches every previous driver build: each read carries the tag
|
||
/// name as ASCII bytes + the controller parses the path on every request. <see cref="Logical"/>
|
||
/// uses CIP logical-segment instance IDs (Symbol Object class 0x6B) — the controller looks the
|
||
/// tag up in its own symbol table once + the driver caches the resolved instance ID for
|
||
/// subsequent reads, eliminating the per-poll ASCII parse step. <see cref="Auto"/> lets the
|
||
/// driver pick (today: always Symbolic; a future PR fingerprints the controller and switches
|
||
/// to Logical when supported).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Logical addressing requires a one-time symbol-table walk at the first read on the device
|
||
/// (the driver issues an <c>@tags</c> read via <see cref="LibplctagTagEnumerator"/> and stores
|
||
/// the name → instance-id map on the per-device <c>DeviceState</c>). It is unsupported on
|
||
/// Micro800 / SLC500 / PLC5 — see <see cref="PlcFamilies.AbCipPlcFamilyProfile.SupportsLogicalAddressing"/>.
|
||
/// The libplctag .NET wrapper (1.5.x) does not expose a public knob for instance-ID
|
||
/// addressing, so the driver translates Logical → libplctag attribute via reflection on
|
||
/// <c>NativeTagWrapper.SetAttributeString</c> — same best-effort fallback pattern as
|
||
/// PR abcip-3.1's ConnectionSize plumbing.
|
||
/// </remarks>
|
||
public enum AddressingMode
|
||
{
|
||
/// <summary>Driver picks. Currently resolves to <see cref="Symbolic"/>; future PR may
|
||
/// auto-detect based on family + firmware + symbol-table size.</summary>
|
||
Auto = 0,
|
||
|
||
/// <summary>ASCII symbolic-path addressing — the libplctag default. Per-poll ASCII parse on
|
||
/// the controller; works on every CIP family.</summary>
|
||
Symbolic = 1,
|
||
|
||
/// <summary>CIP logical-segment / instance-ID addressing. Requires a one-time
|
||
/// symbol-table walk at first read; subsequent reads skip ASCII parsing on the
|
||
/// controller. Unsupported on Micro800 / SLC500 / PLC5.</summary>
|
||
Logical = 2,
|
||
}
|
||
|
||
/// <summary>
|
||
/// One AB-backed OPC UA variable. Mirrors the <c>ModbusTagDefinition</c> shape.
|
||
/// </summary>
|
||
/// <param name="Name">Tag name; becomes the OPC UA browse name and full reference.</param>
|
||
/// <param name="DeviceHostAddress">Which device (<see cref="AbCipDeviceOptions.HostAddress"/>) this tag lives on.</param>
|
||
/// <param name="TagPath">Logix symbolic path (controller or program scope).</param>
|
||
/// <param name="DataType">Logix atomic type, or <see cref="AbCipDataType.Structure"/> for UDT-typed tags.</param>
|
||
/// <param name="Writable">When <c>true</c> and the tag's ExternalAccess permits writes, IWritable routes writes here.</param>
|
||
/// <param name="WriteIdempotent">Per plan decisions #44–#45, #143 — safe to replay on write timeout. Default <c>false</c>.</param>
|
||
/// <param name="Members">For <see cref="AbCipDataType.Structure"/>-typed tags, the declared UDT
|
||
/// member layout. When supplied, discovery fans out the UDT into a folder + one Variable per
|
||
/// member (member TagPath = <c>{tag.TagPath}.{member.Name}</c>). When <c>null</c> on a Structure
|
||
/// tag, the driver treats it as a black-box and relies on downstream configuration to address
|
||
/// members individually via dotted <see cref="AbCipTagPath"/> syntax. Ignored for atomic types.</param>
|
||
/// <param name="SafetyTag">GuardLogix safety-partition tag hint. When <c>true</c>, the driver
|
||
/// forces <c>SecurityClassification.ViewOnly</c> on discovery regardless of
|
||
/// <paramref name="Writable"/> — safety tags can only be written from the safety task of a
|
||
/// GuardLogix controller; non-safety writes violate the safety-partition isolation and are
|
||
/// rejected by the PLC anyway. Surfaces the intent explicitly instead of relying on the
|
||
/// write attempt failing at runtime.</param>
|
||
/// <param name="StringLength">Capacity of the DATA character array on a Logix STRING / STRINGnn
|
||
/// UDT — 82 for the stock <c>STRING</c>, 20/40/80/etc for user-defined <c>STRING_20</c>,
|
||
/// <c>STRING_40</c>, <c>STRING_80</c> variants. Threads through libplctag's
|
||
/// <c>str_max_capacity</c> attribute so the wrapper allocates the correct backing buffer
|
||
/// and <c>GetString</c> / <c>SetString</c> truncate at the right boundary. <c>null</c>
|
||
/// keeps libplctag's default 82-byte STRING behaviour for back-compat. Ignored for
|
||
/// non-<see cref="AbCipDataType.String"/> types.</param>
|
||
/// <param name="Description">Tag description carried from the L5K/L5X export (or set explicitly
|
||
/// in pre-declared config). Surfaces as the OPC UA <c>Description</c> attribute on the
|
||
/// produced Variable node so SCADA / engineering clients see the comment from the source
|
||
/// project. <c>null</c> leaves Description unset, matching pre-2.3 behaviour.</param>
|
||
/// <param name="ScanRateMs">PR abcip-4.1 — optional per-tag publish rate (in milliseconds) that
|
||
/// overrides the subscription's default <c>publishingInterval</c> for this tag. Mirrors
|
||
/// Kepware's "scan classes" + Siemens / Mitsubishi per-tag scan groups; the driver buckets
|
||
/// tags by resolved interval at <see cref="AbCipDriver.SubscribeAsync"/> time + runs one
|
||
/// <see cref="Core.Abstractions.PollGroupEngine"/> loop per distinct interval so a fast HMI
|
||
/// tag is not delayed behind a slow batch tag's 10 s tick. <c>null</c> = use the subscription
|
||
/// default (legacy behaviour). The 100 ms floor enforced by the engine still applies — a
|
||
/// <c>ScanRateMs < 100</c> is clamped up. UDT member tags inherit the parent tag's
|
||
/// <c>ScanRateMs</c> at member-fan-out time. See
|
||
/// <c>docs/drivers/AbCip-Operability.md</c> §"Per-tag scan rate".</param>
|
||
/// <param name="WriteDeadband">PR abcip-4.2 — optional numeric write deadband. When set and both
|
||
/// the previous successfully-written value and the new write are numeric, the driver suppresses
|
||
/// the next write if <c>|new - last| < WriteDeadband</c>. Suppressed writes still return
|
||
/// <c>Good</c> so the OPC UA write semantics observed by clients are unchanged — the driver
|
||
/// simply skips the wire round-trip. Mirrors Kepware's "Deadband (write)" knob and is the
|
||
/// write-side companion to the read-side deadband already shipped at the OPC UA monitored-item
|
||
/// layer. NaN / Infinity values bypass suppression (let the wire decide). See
|
||
/// <c>docs/drivers/AbCip-Operability.md</c> §"Write deadband / write-on-change".</param>
|
||
/// <param name="WriteOnChange">PR abcip-4.2 — optional write-on-change gate. When <c>true</c> and
|
||
/// the new write equals the previous successfully-written value, the driver suppresses the
|
||
/// write (returns <c>Good</c> without hitting the wire). Combines with <see cref="WriteDeadband"/>
|
||
/// for numeric tags — the deadband test takes priority for numerics, equality is the fallback
|
||
/// for non-numeric types (BOOL setpoints, STRING constants, etc.). Default <c>false</c> —
|
||
/// legacy behaviour where every write goes to the wire.</param>
|
||
public sealed record AbCipTagDefinition(
|
||
string Name,
|
||
string DeviceHostAddress,
|
||
string TagPath,
|
||
AbCipDataType DataType,
|
||
bool Writable = true,
|
||
bool WriteIdempotent = false,
|
||
IReadOnlyList<AbCipStructureMember>? Members = null,
|
||
bool SafetyTag = false,
|
||
int? StringLength = null,
|
||
string? Description = null,
|
||
int? ScanRateMs = null,
|
||
double? WriteDeadband = null,
|
||
bool WriteOnChange = false);
|
||
|
||
/// <summary>
|
||
/// One declared member of a UDT tag. Name is the member identifier on the PLC (e.g. <c>Speed</c>,
|
||
/// <c>Status</c>), DataType is the atomic Logix type, Writable/WriteIdempotent mirror
|
||
/// <see cref="AbCipTagDefinition"/>. Declaration-driven — the real CIP Template Object reader
|
||
/// (class 0x6C) that would auto-discover member layouts lands as a follow-up PR.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para><see cref="Description"/> carries the per-member comment from L5K/L5X UDT definitions so
|
||
/// the OPC UA Variable nodes produced for individual members surface their descriptions too,
|
||
/// not just the top-level tag.</para>
|
||
/// <para>PR abcip-2.6 — <see cref="AoiQualifier"/> tags AOI parameters as Input / Output /
|
||
/// InOut / Local. Plain UDT members default to <see cref="AoiQualifier.Local"/>. Discovery
|
||
/// groups Input / Output / InOut members under sub-folders so an AOI-typed tag fans out as
|
||
/// <c>Tag/Inputs/...</c>, <c>Tag/Outputs/...</c>, <c>Tag/InOut/...</c> while Local stays at the
|
||
/// UDT root — matching how AOIs visually present in Studio 5000.</para>
|
||
/// </remarks>
|
||
public sealed record AbCipStructureMember(
|
||
string Name,
|
||
AbCipDataType DataType,
|
||
bool Writable = true,
|
||
bool WriteIdempotent = false,
|
||
int? StringLength = null,
|
||
string? Description = null,
|
||
AoiQualifier AoiQualifier = AoiQualifier.Local);
|
||
|
||
/// <summary>
|
||
/// PR abcip-2.6 — directional qualifier for AOI parameters. Surfaces the Studio 5000
|
||
/// <c>Usage</c> attribute (<c>Input</c> / <c>Output</c> / <c>InOut</c>) so discovery can group
|
||
/// AOI members into sub-folders and downstream consumers can reason about parameter direction.
|
||
/// Plain UDT members (non-AOI types) default to <see cref="Local"/>, which keeps them at the
|
||
/// UDT root + indicates they are internal storage rather than a directional parameter.
|
||
/// </summary>
|
||
public enum AoiQualifier
|
||
{
|
||
/// <summary>UDT member or AOI local tag — non-directional, browsed at the parent's root.</summary>
|
||
Local,
|
||
|
||
/// <summary>AOI input parameter — written by the caller, read by the AOI body.</summary>
|
||
Input,
|
||
|
||
/// <summary>AOI output parameter — written by the AOI body, read by the caller.</summary>
|
||
Output,
|
||
|
||
/// <summary>AOI bidirectional parameter — passed by reference, both sides may read/write.</summary>
|
||
InOut,
|
||
}
|
||
|
||
/// <summary>
|
||
/// One L5K-import entry. Either <see cref="FilePath"/> or <see cref="InlineText"/> must be
|
||
/// set (FilePath wins when both supplied — useful for tests that pre-load fixtures into
|
||
/// options without touching disk).
|
||
/// </summary>
|
||
/// <param name="DeviceHostAddress">Target device <c>HostAddress</c> tags from this file are bound to.</param>
|
||
/// <param name="FilePath">On-disk path to a <c>*.L5K</c> export. Loaded eagerly at InitializeAsync.</param>
|
||
/// <param name="InlineText">Pre-loaded L5K body — used by tests + Admin UI uploads.</param>
|
||
/// <param name="NamePrefix">Optional prefix prepended to imported tag names to avoid collisions
|
||
/// when ingesting multiple files into one driver instance.</param>
|
||
public sealed record AbCipL5kImportOptions(
|
||
string DeviceHostAddress,
|
||
string? FilePath = null,
|
||
string? InlineText = null,
|
||
string NamePrefix = "");
|
||
|
||
/// <summary>
|
||
/// One L5X-import entry. Mirrors <see cref="AbCipL5kImportOptions"/> field-for-field — the
|
||
/// two are kept as distinct types so configuration JSON makes the source format explicit
|
||
/// (an L5X file under an <c>L5kImports</c> entry would parse-fail confusingly otherwise).
|
||
/// </summary>
|
||
/// <param name="DeviceHostAddress">Target device <c>HostAddress</c> tags from this file are bound to.</param>
|
||
/// <param name="FilePath">On-disk path to a <c>*.L5X</c> XML export. Loaded eagerly at InitializeAsync.</param>
|
||
/// <param name="InlineText">Pre-loaded L5X body — used by tests + Admin UI uploads.</param>
|
||
/// <param name="NamePrefix">Optional prefix prepended to imported tag names to avoid collisions
|
||
/// when ingesting multiple files into one driver instance.</param>
|
||
public sealed record AbCipL5xImportOptions(
|
||
string DeviceHostAddress,
|
||
string? FilePath = null,
|
||
string? InlineText = null,
|
||
string NamePrefix = "");
|
||
|
||
/// <summary>
|
||
/// One Kepware-format CSV import entry. Field shape mirrors <see cref="AbCipL5kImportOptions"/>
|
||
/// so configuration JSON stays consistent across the three import sources.
|
||
/// </summary>
|
||
/// <param name="DeviceHostAddress">Target device <c>HostAddress</c> tags from this file are bound to.</param>
|
||
/// <param name="FilePath">On-disk path to a Kepware-format <c>*.csv</c>. Loaded eagerly at InitializeAsync.</param>
|
||
/// <param name="InlineText">Pre-loaded CSV body — used by tests + Admin UI uploads.</param>
|
||
/// <param name="NamePrefix">Optional prefix prepended to imported tag names to avoid collisions.</param>
|
||
public sealed record AbCipCsvImportOptions(
|
||
string DeviceHostAddress,
|
||
string? FilePath = null,
|
||
string? InlineText = null,
|
||
string NamePrefix = "");
|
||
|
||
/// <summary>Which AB PLC family the device is — selects the profile applied to connection params.</summary>
|
||
public enum AbCipPlcFamily
|
||
{
|
||
ControlLogix,
|
||
CompactLogix,
|
||
Micro800,
|
||
GuardLogix,
|
||
}
|
||
|
||
/// <summary>
|
||
/// Background connectivity-probe settings. Enabled by default; the probe reads a cheap tag
|
||
/// on the PLC at the configured interval to drive <see cref="Core.Abstractions.IHostConnectivityProbe"/>
|
||
/// state transitions + Admin UI health status.
|
||
/// </summary>
|
||
public sealed class AbCipProbeOptions
|
||
{
|
||
public bool Enabled { get; init; } = true;
|
||
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
|
||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||
|
||
/// <summary>
|
||
/// Tag path used for the probe. If null, the driver attempts to read a default
|
||
/// system tag (PR 8 wires this up — the choice is family-dependent, e.g.
|
||
/// <c>@raw_cpu_type</c> on ControlLogix or a user-configured probe tag on Micro800).
|
||
/// </summary>
|
||
public string? ProbeTagPath { get; init; }
|
||
}
|