using System.ComponentModel.DataAnnotations; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// /// FOCAS driver configuration. One instance supports N CNC devices. Each device gets its own /// (DriverInstanceId, HostAddress) per-host pipeline key at the Phase 6.1 resilience layer. /// public sealed class FocasDriverOptions { /// Gets the list of configured CNC devices. public IReadOnlyList Devices { get; init; } = []; /// /// Authored raw tags this driver serves. The deploy artifact hands each authored raw /// Tag as a (RawPath identity + driver TagConfig blob + /// WriteIdempotent flag + ); the driver maps each through /// into its RawPath → definition table, /// routing each tag to its device by . FOCAS has no online /// tag-discovery for user tags — the driver serves exactly these (plus the fixed-node tree). /// public IReadOnlyList RawTags { get; init; } = []; /// Gets the probe options. public FocasProbeOptions Probe { get; init; } = new(); /// Gets the timeout duration for operations. public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); /// Gets the alarm projection options. public FocasAlarmProjectionOptions AlarmProjection { get; init; } = new(); /// Gets the handle recycle options. public FocasHandleRecycleOptions HandleRecycle { get; init; } = new(); /// Gets the fixed tree options. public FocasFixedTreeOptions FixedTree { get; init; } = new(); /// Gets the PMC range-coalescing options. public FocasPmcCoalesceOptions PmcCoalesce { get; init; } = new(); /// /// 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 10s.", GroupName = "Diagnostics")] [Range(1, 60)] public int ProbeTimeoutSeconds { get; init; } = 10; } /// /// Merges adjacent / near PMC reads in one poll batch into fewer pmc_rdpmcrng calls. /// On by default: a batch with a single PMC tag per area is unaffected, and a coalesced read the CNC /// rejects falls back to per-tag reads, so enabling it cannot make a readable tag unreadable. /// public sealed class FocasPmcCoalesceOptions { /// Gets a value indicating whether PMC coalescing is on. public bool Enabled { get; init; } = true; /// /// Largest range, in bytes, one coalesced read may cover. Bounds the response size and the /// blast radius of a rejected range. Default 128. /// [Range(1, 1024)] public int MaxSpanBytes { get; init; } = 128; /// /// Largest run of bytes nobody asked for that may sit between two merged tags. Reading a few /// spare bytes is cheaper than a second round-trip, but every spare byte must be a valid PMC /// address on the CNC. Default 8; 0 merges only strictly adjacent tags. /// [Range(0, 1024)] public int MaxGapBytes { get; init; } = 8; } /// /// Fixed-node tree exposed by FOCAS per docs/v2/driver-specs.md §7 — /// Identity/, Axes/{name}/, etc. populated from /// cnc_sysinfo / cnc_rdaxisname / cnc_rddynamic2. Disabled by /// default so existing configs that only use user-authored tags don't grow new /// nodes on upgrade. /// public sealed class FocasFixedTreeOptions { /// Gets or sets a value indicating whether the fixed-node tree is enabled for every configured device. public bool Enabled { get; init; } = false; /// /// Poll cadence for cnc_rddynamic2. Each tick calls the API once per /// configured axis + publishes OnDataChange for the axis subtree. Real CNCs /// serve ~100ms loops comfortably; the default is conservative. /// public TimeSpan PollInterval { get; init; } = TimeSpan.FromMilliseconds(250); /// /// Poll cadence for program + operation-mode info. Slower than the axis /// poll because program / mode transitions happen on operator timescales. /// Zero / negative disables the program poll entirely. /// public TimeSpan ProgramPollInterval { get; init; } = TimeSpan.FromSeconds(1); /// /// Poll cadence for timers (power-on / operating / cutting / cycle). /// These change at human timescales — default is 30s. Zero / negative /// disables the timer poll entirely. /// public TimeSpan TimerPollInterval { get; init; } = TimeSpan.FromSeconds(30); } /// /// Proactive session-recycle cadence. Fanuc CNCs have a finite FWLIB handle pool /// (~5–10 concurrent connections) and certain series have documented handle-leak bugs /// that manifest after long uptime. When is true the /// driver closes + reopens each device's session on the cadence, /// forcing FWLIB to release its handle slot back to the pool. Reads / writes during /// recycle wait for the reconnect rather than failing — worst case an operator sees a /// brief read latency spike once per cadence. /// /// /// Disabled by default because a healthy CNC + driver doesn't need it. Enable when /// field experience shows handle exhaustion against a specific series / firmware. /// Typical tuning: 30 min for sites running multiple OtOpcUa instances against the /// same CNC (they share the pool); 6 h for a single-client deployment. /// public sealed class FocasHandleRecycleOptions { /// Gets or sets a value indicating whether handle recycling is enabled. public bool Enabled { get; init; } = false; /// Gets or sets the interval for handle recycle operations. public TimeSpan Interval { get; init; } = TimeSpan.FromHours(1); } /// /// Controls the CNC active-alarm polling projection that surfaces FOCAS alarms via /// IAlarmSource as Part 9 conditions at ALARM: raw tags. /// /// /// Authoring an ALARM: tag IS the opt-in. The projection polls exactly the devices that own a /// subscribed alarm tag, so a driver with none issues no cnc_rdalmmsg2 traffic whatever /// says. Previously gated the poll and /// SubscribeAlarmsAsync THREW while it was false — so an operator who authored an alarm tag and left /// the default got a feed that stayed gated with a Warning on every reconnect. It is retained so existing /// configs still bind, and no longer has any effect. /// public sealed class FocasAlarmProjectionOptions { /// Retained for config compatibility; has no effect (see the class remarks). public bool Enabled { get; init; } = false; /// Poll cadence. One cnc_rdalmmsg2 call per device per tick. public TimeSpan PollInterval { get; init; } = TimeSpan.FromSeconds(2); } /// /// One CNC the driver talks to. enables per-series /// address validation at FocasDriver.InitializeAsync; leave as /// to skip validation (legacy behaviour). /// /// /// FOCAS TCP endpoint in focas://{ip}[:{port}] form, e.g. /// focas://10.20.30.40:8193. The port defaults to 8193 when omitted. /// Parsed by FocasHostAddress.TryParse at FocasDriver.InitializeAsync. /// /// /// Optional human-readable label shown in the OPC UA address-space folder for this /// device. Defaults to when null. /// /// /// CNC controller series. Used by to gate which /// FOCAS addresses are valid. Leave as (the /// default) to skip range validation and preserve legacy behaviour. /// /// /// Fallback only. A real CNC's cnc_rddynamic2 position block carries its own /// scale (raw / base^exponent), which the driver always applies and which wins over /// this setting. Only a block that carries no scale (a 4-byte payload, e.g. an older /// simulator) is divided by 10^PositionDecimalPlaces. Default 0 (no scaling). /// Negative values are clamped to 0. /// public sealed record FocasDeviceOptions( string HostAddress, string? DeviceName = null, FocasCncSeries Series = FocasCncSeries.Unknown, int PositionDecimalPlaces = 0) { /// /// Axis-position decimal places, clamped to a non-negative value so the /// 10^PositionDecimalPlaces divide at the publish seam can never misbehave. /// public int PositionDecimalPlaces { get; init; } = PositionDecimalPlaces < 0 ? 0 : PositionDecimalPlaces; } /// /// One FOCAS-backed OPC UA variable. is the canonical FOCAS /// address string that parses via FocasAddress.TryParse — /// X0.0 / R100 / PARAM:1815/0 / MACRO:500. /// /// /// Whether the tag is declared writable in config. Reflected at the /// DiscoverAsync address-space advertisement seam and gates the driver's /// write path — FOCAS supports PMC/data writes (see FocasPmcBitRmwTests / /// FocasReadWriteTests). Defaults to true. /// /// /// Whether repeated writes of the same value are safe. Threaded through to /// DriverAttributeInfo.WriteIdempotent by DiscoverAsync so OPC UA /// clients can optimise write coalescing for idempotent tags. Defaults to false. /// /// /// The tag's cross-driver alarm object, when it carries one — which makes the host /// materialise it as a Part 9 condition at its RawPath. Only an ALARM: address can drive one. /// public sealed record FocasTagDefinition( string Name, string DeviceHostAddress, string Address, FocasDataType DataType, bool Writable = true, bool WriteIdempotent = false, FocasTagAlarm? Alarm = null); /// /// What the FOCAS driver reads from a tag's cross-driver alarm object. The host owns the /// rest of it (alarmType, historizeToAveva) through TagConfigIntent. /// /// /// The authored alarm.severity (an integer), or when the object has none. An /// authored severity is sent as the condition's exact Part 9 severity; without one the severity comes from /// the displayed CNC alarm's type (FocasAlarmConditionMapper). /// public sealed record FocasTagAlarm(int? Severity); /// /// Controls periodic connectivity probing. One cnc_rdcncstat call per /// configured device per tick; transitions fire OnHostStatusChanged. /// Enabled by default so the driver surfaces a fast host-state transition when /// a CNC goes offline between data reads. /// public sealed class FocasProbeOptions { /// Gets or sets a value indicating whether probing is enabled. public bool Enabled { get; init; } = true; /// Gets or sets the probe interval. public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5); /// Gets or sets the probe timeout. public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); }