namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
///
/// AB CIP / EtherNet-IP driver configuration, bound from the driver's DriverConfig
/// JSON at DriverHost.RegisterAsync. One instance supports N devices (PLCs) behind
/// the same driver; per-device routing is keyed on
/// via IPerCallHostResolver.
///
///
/// Per v2 plan decisions #11 (libplctag), #41 (AbCip vs AbLegacy split), #143–144 (per-call
/// host resolver + resilience keys), #144 (bulkhead keyed on (DriverInstanceId, HostName)).
///
public sealed class AbCipDriverOptions
{
///
/// PLCs this driver instance talks to. Each device contributes its own
/// string as the hostName key used by resilience pipelines and the Admin UI.
///
public IReadOnlyList Devices { get; init; } = [];
/// Pre-declared tag map across all devices — AB discovery lands in PR 5.
public IReadOnlyList Tags { get; init; } = [];
///
/// L5K (Studio 5000 controller export) imports merged into at
/// InitializeAsync. Each entry points at one L5K file + the device whose tags it
/// describes; the parser extracts TAG + DATATYPE blocks and produces
/// records (alias tags + ExternalAccess=None tags
/// skipped — see ). Pre-declared entries
/// win on Name conflicts so operators can override import results without
/// editing the L5K source.
///
public IReadOnlyList L5kImports { get; init; } = [];
///
/// L5X (Studio 5000 XML controller export) imports merged into at
/// InitializeAsync. Same shape and merge semantics as —
/// the entries differ only in source format. Pre-declared entries win
/// on Name conflicts; entries already produced by also win
/// so an L5X re-export of the same controller doesn't double-emit. See
/// for the format-specific mechanics.
///
public IReadOnlyList L5xImports { get; init; } = [];
///
/// Kepware-format CSV imports merged into at InitializeAsync.
/// Same merge semantics as / —
/// pre-declared entries win on Name 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
/// for the column layout + parse rules.
///
public IReadOnlyList CsvImports { get; init; } = [];
/// Per-device probe settings. Falls back to defaults when omitted.
public AbCipProbeOptions Probe { get; init; } = new();
///
/// 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.
///
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
///
/// When true, DiscoverAsync walks each device's Logix symbol table via
/// the @tags pseudo-tag + surfaces controller-resident globals under a
/// Discovered/ sub-folder. Pre-declared tags always emit regardless. Default
/// false to keep the strict-config path for deployments where only declared tags
/// should appear in the address space.
///
public bool EnableControllerBrowse { get; init; }
///
/// Task #177 — when true, declared ALMD tags are surfaced as alarm conditions
/// via ; the driver polls each subscribed
/// alarm's InFaulted + Severity members + fires OnAlarmEvent on
/// state transitions. Default false — 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.
///
public bool EnableAlarmProjection { get; init; }
///
/// 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.
///
public TimeSpan AlarmPollInterval { get; init; } = TimeSpan.FromSeconds(1);
}
///
/// One PLC endpoint. must parse via
/// ; misconfigured devices fail driver
/// initialization rather than silently connecting to nothing.
///
/// Canonical ab://gateway[:port]/cip-path string.
/// Which per-family profile to apply. Determines ConnectionSize,
/// request-packing support, unconnected-only hint, and other quirks.
/// Optional display label for Admin UI. Falls back to .
public sealed record AbCipDeviceOptions(
string HostAddress,
AbCipPlcFamily PlcFamily = AbCipPlcFamily.ControlLogix,
string? DeviceName = null);
///
/// One AB-backed OPC UA variable. Mirrors the ModbusTagDefinition shape.
///
/// Tag name; becomes the OPC UA browse name and full reference.
/// Which device () this tag lives on.
/// Logix symbolic path (controller or program scope).
/// Logix atomic type, or for UDT-typed tags.
/// When true and the tag's ExternalAccess permits writes, IWritable routes writes here.
/// Per plan decisions #44–#45, #143 — safe to replay on write timeout. Default false.
/// For -typed tags, the declared UDT
/// member layout. When supplied, discovery fans out the UDT into a folder + one Variable per
/// member (member TagPath = {tag.TagPath}.{member.Name}). When null on a Structure
/// tag, the driver treats it as a black-box and relies on downstream configuration to address
/// members individually via dotted syntax. Ignored for atomic types.
/// GuardLogix safety-partition tag hint. When true, the driver
/// forces SecurityClassification.ViewOnly on discovery regardless of
/// — 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.
/// Capacity of the DATA character array on a Logix STRING / STRINGnn
/// UDT — 82 for the stock STRING, 20/40/80/etc for user-defined STRING_20,
/// STRING_40, STRING_80 variants. Threads through libplctag's
/// str_max_capacity attribute so the wrapper allocates the correct backing buffer
/// and GetString / SetString truncate at the right boundary. null
/// keeps libplctag's default 82-byte STRING behaviour for back-compat. Ignored for
/// non- types.
/// Tag description carried from the L5K/L5X export (or set explicitly
/// in pre-declared config). Surfaces as the OPC UA Description attribute on the
/// produced Variable node so SCADA / engineering clients see the comment from the source
/// project. null leaves Description unset, matching pre-2.3 behaviour.
public sealed record AbCipTagDefinition(
string Name,
string DeviceHostAddress,
string TagPath,
AbCipDataType DataType,
bool Writable = true,
bool WriteIdempotent = false,
IReadOnlyList? Members = null,
bool SafetyTag = false,
int? StringLength = null,
string? Description = null);
///
/// One declared member of a UDT tag. Name is the member identifier on the PLC (e.g. Speed,
/// Status), DataType is the atomic Logix type, Writable/WriteIdempotent mirror
/// . Declaration-driven — the real CIP Template Object reader
/// (class 0x6C) that would auto-discover member layouts lands as a follow-up PR.
///
///
/// 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.
/// PR abcip-2.6 — tags AOI parameters as Input / Output /
/// InOut / Local. Plain UDT members default to . Discovery
/// groups Input / Output / InOut members under sub-folders so an AOI-typed tag fans out as
/// Tag/Inputs/..., Tag/Outputs/..., Tag/InOut/... while Local stays at the
/// UDT root — matching how AOIs visually present in Studio 5000.
///
public sealed record AbCipStructureMember(
string Name,
AbCipDataType DataType,
bool Writable = true,
bool WriteIdempotent = false,
int? StringLength = null,
string? Description = null,
AoiQualifier AoiQualifier = AoiQualifier.Local);
///
/// PR abcip-2.6 — directional qualifier for AOI parameters. Surfaces the Studio 5000
/// Usage attribute (Input / Output / InOut) 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 , which keeps them at the
/// UDT root + indicates they are internal storage rather than a directional parameter.
///
public enum AoiQualifier
{
/// UDT member or AOI local tag — non-directional, browsed at the parent's root.
Local,
/// AOI input parameter — written by the caller, read by the AOI body.
Input,
/// AOI output parameter — written by the AOI body, read by the caller.
Output,
/// AOI bidirectional parameter — passed by reference, both sides may read/write.
InOut,
}
///
/// One L5K-import entry. Either or must be
/// set (FilePath wins when both supplied — useful for tests that pre-load fixtures into
/// options without touching disk).
///
/// Target device HostAddress tags from this file are bound to.
/// On-disk path to a *.L5K export. Loaded eagerly at InitializeAsync.
/// Pre-loaded L5K body — used by tests + Admin UI uploads.
/// Optional prefix prepended to imported tag names to avoid collisions
/// when ingesting multiple files into one driver instance.
public sealed record AbCipL5kImportOptions(
string DeviceHostAddress,
string? FilePath = null,
string? InlineText = null,
string NamePrefix = "");
///
/// One L5X-import entry. Mirrors field-for-field — the
/// two are kept as distinct types so configuration JSON makes the source format explicit
/// (an L5X file under an L5kImports entry would parse-fail confusingly otherwise).
///
/// Target device HostAddress tags from this file are bound to.
/// On-disk path to a *.L5X XML export. Loaded eagerly at InitializeAsync.
/// Pre-loaded L5X body — used by tests + Admin UI uploads.
/// Optional prefix prepended to imported tag names to avoid collisions
/// when ingesting multiple files into one driver instance.
public sealed record AbCipL5xImportOptions(
string DeviceHostAddress,
string? FilePath = null,
string? InlineText = null,
string NamePrefix = "");
///
/// One Kepware-format CSV import entry. Field shape mirrors
/// so configuration JSON stays consistent across the three import sources.
///
/// Target device HostAddress tags from this file are bound to.
/// On-disk path to a Kepware-format *.csv. Loaded eagerly at InitializeAsync.
/// Pre-loaded CSV body — used by tests + Admin UI uploads.
/// Optional prefix prepended to imported tag names to avoid collisions.
public sealed record AbCipCsvImportOptions(
string DeviceHostAddress,
string? FilePath = null,
string? InlineText = null,
string NamePrefix = "");
/// Which AB PLC family the device is — selects the profile applied to connection params.
public enum AbCipPlcFamily
{
ControlLogix,
CompactLogix,
Micro800,
GuardLogix,
}
///
/// Background connectivity-probe settings. Enabled by default; the probe reads a cheap tag
/// on the PLC at the configured interval to drive
/// state transitions + Admin UI health status.
///
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);
///
/// 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.
/// @raw_cpu_type on ControlLogix or a user-configured probe tag on Micro800).
///
public string? ProbeTagPath { get; init; }
}