using System.ComponentModel.DataAnnotations; 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. Pre-declared tags always emit during /// discovery; opt in to controller-side discovery via /// . /// public IReadOnlyList Tags { 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 IAlarmSource; 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); /// /// Opt-in for the declaration-only whole-UDT read fast path. When false (the /// default) a batch of UDT members is always read per-member, because the byte offsets /// computed by assume the controller lays members /// out in declaration order — and the Studio 5000 compiler does NOT guarantee that /// (it reorders for largest-first packing, BOOL host bytes, nested-struct padding). /// Decoding at declaration-order offsets against a reordered controller layout yields /// silently-plausible wrong numbers. Set true only when the operator has /// hand-verified that every configured UDT's member declaration order matches the /// controller's compiled layout; in that case whole-UDT grouping collapses N member /// reads into one. The richer CIP Template Object path remains the long-term fix. /// public bool EnableDeclarationOnlyUdtGrouping { get; init; } /// /// Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a /// 60s server-side maximum; this default is what the form pre-fills for new instances. /// [Display(Name = "Probe timeout (seconds)", Description = "Connection test timeout. Default 5s.", GroupName = "Diagnostics")] [Range(1, 60)] public int ProbeTimeoutSeconds { get; init; } = 5; } /// /// 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 the family /// AllowPacking default, ConnectionSize default, unconnected-only hint, and /// other quirks; per-device overrides via and /// take precedence when set. /// Optional display label for Admin UI. Falls back to . /// Driver.AbCip-013 — per-device override for CIP request-packing /// (firmware 20+). null (the default) inherits the family profile's /// SupportsRequestPacking; set explicitly to opt a single device in or out without /// touching every other device on the same family. /// Driver.AbCip-013 — per-device override for the Forward Open /// ConnectionSize (Large Forward Open packet size in bytes). null inherits the family /// profile's DefaultConnectionSize. Honoured by the driver layer; the underlying /// libplctag 1.5.2 wrapper has no direct ConnectionSize property, so the value is /// plumbed through for forward-compat with future wrapper /// versions or a custom tag-attribute path; current builds use the family profile default at /// the wire layer regardless. public sealed record AbCipDeviceOptions( string HostAddress, AbCipPlcFamily PlcFamily = AbCipPlcFamily.ControlLogix, string? DeviceName = null, bool? AllowPacking = null, int? ConnectionSize = 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. public sealed record AbCipTagDefinition( string Name, string DeviceHostAddress, string TagPath, AbCipDataType DataType, bool Writable = true, bool WriteIdempotent = false, IReadOnlyList? Members = null, bool SafetyTag = false); /// /// 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. /// public sealed record AbCipStructureMember( string Name, AbCipDataType DataType, bool Writable = true, bool WriteIdempotent = false); /// 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 IHostConnectivityProbe /// state transitions + Admin UI health status. /// public sealed class AbCipProbeOptions { /// Gets a value indicating whether the probe is enabled. public bool Enabled { get; init; } = true; /// Gets the interval at which the probe reads the probe tag. public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5); /// Gets the timeout for each probe read operation. public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); /// /// Tag path used for the probe. When is true but this is /// null/blank, the driver logs a warning and runs no probe loops (Driver.AbCip-011); /// GetHostStatuses() will then report every device as Unknown. A family-default /// system-tag fallback (e.g. @raw_cpu_type on ControlLogix) is a deferred follow-up; /// today an operator opting into the probe must supply a tag path explicitly. /// public string? ProbeTagPath { get; init; } }