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; } }