namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// /// FOCAS driver configuration. One instance supports N CNC devices. Per plan decision #144 /// each device gets its own (DriverInstanceId, HostAddress) bulkhead key at the /// Phase 6.1 resilience layer. /// public sealed class FocasDriverOptions { public IReadOnlyList Devices { get; init; } = []; public IReadOnlyList Tags { get; init; } = []; public FocasProbeOptions Probe { get; init; } = new(); public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); /// /// Fixed-tree behaviour knobs (issue #262, plan PR F1-f). Carries the /// ApplyFigureScaling toggle that gates the cnc_getfigure /// decimal-place division applied to position values before publishing. /// public FocasFixedTreeOptions FixedTree { get; init; } = new(); /// /// Alarm projection knobs (issue #267, plan PR F3-a). Default mode is /// — the projection only surfaces /// currently-active alarms. Operators who want the on-CNC ring-buffer history /// replayed as historic OPC UA events (so dashboards see the real CNC timestamp, /// not the moment the projection polled) flip this to /// . /// public FocasAlarmProjectionOptions AlarmProjection { get; init; } = new(); /// /// Driver-level write opt-in (issue #268, plan PR F4-a). Defaults to /// Enabled = false — the driver short-circuits every IWritable.WriteAsync /// call to until the deployment explicitly /// flips this on. Combined with the per-tag /// gate (also default-off), every CNC write requires two opt-ins. /// public FocasWritesOptions Writes { get; init; } = new(); } /// /// Driver-level write controls (issue #268, plan PR F4-a). Per the F4-a decision record /// writes ship behind a flag with a safe default: an operator who pulls the FOCAS driver /// into production without touching Writes.Enabled gets read-only behaviour, and /// even with the flag flipped on each individual tag must still set /// = true. /// public sealed record FocasWritesOptions { /// /// Driver-level master switch. Default false — every write returns /// with the status text /// "writes disabled at driver level". /// public bool Enabled { get; init; } = false; /// /// Issue #269, plan PR F4-b — granular kill-switch for cnc_wrparam /// parameter writes (defense in depth on top of and the /// per-tag ). Default false: an /// operator who flips on without explicitly opting into /// parameter writes still gets /// for every PARAM: tag. A misdirected parameter write can put the CNC /// in a bad state, so the third opt-in keeps the blast radius bounded. /// Server-layer ACL: PARAM: tags additionally surface a /// classification /// so the OPC UA gate requires WriteConfigure group membership; this /// flag is the driver-level kill switch the operator team can flip without a /// redeploy. /// public bool AllowParameter { get; init; } = false; /// /// Issue #269, plan PR F4-b — granular kill-switch for cnc_wrmacro macro /// variable writes (defense in depth on top of and the /// per-tag ). Default false: /// macro writes are gated separately from parameter writes because they're a /// normal HMI-driven recipe / setpoint surface where parameter writes are /// mostly emergency commissioning territory. /// Server-layer ACL: MACRO: tags surface /// so the OPC UA /// gate requires WriteOperate group membership. /// public bool AllowMacro { get; init; } = false; /// /// Issue #270, plan PR F4-c — granular kill-switch for pmc_wrpmcrng PMC /// range writes (and the bit-level read-modify-write that wraps it). Default /// false: PMC is ladder working memory — a mistargeted bit can move /// motion, latch a feedhold, or flip a safety interlock. Even with /// on and a tag's /// flag flipped on, PMC writes stay locked until this third opt-in fires. /// Server-layer ACL: PMC tags surface /// so the OPC UA /// gate requires WriteOperate group membership; this flag is the driver- /// level kill switch the operator team can flip without a redeploy. /// public bool AllowPmc { get; init; } = false; } /// /// Mode for the FOCAS alarm projection (issue #267, plan PR F3-a). Default /// matches today's behaviour — only currently-active /// alarms surface as OPC UA events. additionally /// polls cnc_rdalmhistry on connect + on a configurable cadence and emits the /// ring-buffer entries as historic events, deduped by (OccurrenceTime, AlarmNumber, /// AlarmType) so a polled entry never re-fires. /// public enum FocasAlarmProjectionMode { /// Surface only currently-active CNC alarms. No history poll. Default. ActiveOnly = 0, /// /// Surface active alarms plus the on-CNC ring-buffer history. The projection /// polls cnc_rdalmhistry on connect and on /// ticks afterward. /// Each new entry (keyed by (OccurrenceTime, AlarmNumber, AlarmType)) /// fires an with /// SourceTimestampUtc set from the CNC's reported timestamp, not Now. /// ActivePlusHistory = 1, } /// /// FOCAS alarm-projection knobs (issue #267, plan PR F3-a). Carries the mode switch + /// the cadence / depth tuning for the cnc_rdalmhistry poll loop. Defaults match /// "operator dashboard with five-minute refresh" — the single most common deployment /// shape per the F3-a deployment doc. /// public sealed record FocasAlarmProjectionOptions { /// Default poll interval — 5 minutes. Matches dashboard-class cadences. public static readonly TimeSpan DefaultHistoryPollInterval = TimeSpan.FromMinutes(5); /// /// Default ring-buffer depth requested per poll — 100. Most FANUC controllers /// keep ~100 entries by default; pulling the full depth on every poll keeps the /// dedup set authoritative across reconnects without burning extra wire bandwidth on /// entries the dedup key would discard anyway. /// public const int DefaultHistoryDepth = 100; /// /// Hard ceiling on . The projection clamps user-requested /// depths above this value down — typical CNC ring buffers cap well below this and /// letting an operator type 10000 by accident shouldn't take down the wire /// session with a giant cnc_rdalmhistry request. /// public const int MaxHistoryDepth = 250; /// Active-only (default) vs Active-plus-history. See . public FocasAlarmProjectionMode Mode { get; init; } = FocasAlarmProjectionMode.ActiveOnly; /// /// Cadence at which the projection re-polls cnc_rdalmhistry when /// is . /// Default = 5 minutes. Only applies after /// the on-connect poll fires. /// public TimeSpan HistoryPollInterval { get; init; } = DefaultHistoryPollInterval; /// /// Number of most-recent ring-buffer entries to request per poll. Clamped to /// [1..] at projection startup so misconfigured /// values can't hammer the CNC. Default = 100. /// public int HistoryDepth { get; init; } = DefaultHistoryDepth; } /// /// Per-driver fixed-tree options. New installs default /// to true so position values surface in user units (mm / inch). Existing /// deployments that already published raw scaled integers can flip this to false /// for migration parity — the operator-facing concern is that switching the flag /// mid-deployment changes the values clients see, so the migration path is /// documentation-only (issue #262). /// public sealed record FocasFixedTreeOptions { /// /// When true (default), position values from cnc_absolute / /// cnc_machine / cnc_relative / cnc_distance / /// cnc_actf are divided by 10^decimalPlaces per axis using the /// cnc_getfigure snapshot cached at probe time. When false, the /// raw integer values are published unchanged — used for migrations from /// older drivers that didn't apply the scaling. /// public bool ApplyFigureScaling { get; init; } = true; } /// /// One CNC the driver talks to. enables per-series /// address validation at ; leave as /// to skip validation (legacy behaviour). /// declares the four MTB-specific override /// cnc_rdparam numbers surfaced under Override/; pass null to /// suppress the entire Override/ subfolder for that device (issue #259). /// (issue #271, plan PR F4-d) is the CNC connection-level /// password emitted via cnc_wrunlockparam on connect when the controller /// gates parameter writes / certain reads behind a password switch (16i + some /// 30i firmwares with parameter-protect on). /// /// /// No-log invariant: is a secret. The driver MUST NOT /// log it. FocasDeviceOptions.ToString() would include the field by default /// because it's a positional record member, so the record's auto-generated /// ToString is overridden via below to redact /// the password. Any new logging surface that touches /// must continue to redact. See docs/v2/focas-deployment.md § "FOCAS password /// handling" for the no-log invariant and rotation runbook. /// public sealed record FocasDeviceOptions( string HostAddress, string? DeviceName = null, FocasCncSeries Series = FocasCncSeries.Unknown, FocasOverrideParameters? OverrideParameters = null, string? Password = null) { /// /// Issue #271 (plan PR F4-d) — record auto-generated ToString would print /// verbatim. Override the printer so the secret is replaced /// with "***" when the field is non-null. The no-log invariant relies on /// this — every Serilog destructure that flows a /// value through {Device} gets redaction for free. /// private bool PrintMembers(System.Text.StringBuilder builder) { builder.Append("HostAddress = ").Append(HostAddress); builder.Append(", DeviceName = ").Append(DeviceName); builder.Append(", Series = ").Append(Series); builder.Append(", OverrideParameters = ").Append(OverrideParameters); builder.Append(", Password = ").Append(Password is null ? "" : "***"); return true; } } /// /// One FOCAS-backed OPC UA variable. is the canonical FOCAS /// address string that parses via — /// X0.0 / R100 / PARAM:1815/0 / MACRO:500 / /// DIAG:1031 / DIAG:280/2. /// /// /// defaults to false per issue #268 / plan PR F4-a — a /// newly-onboarded tag is read-only until the deployment explicitly opts it in, matching /// the driver-level safer-by-default posture. /// is plumbed through the /// retry path at the /// server layer (see ); a /// true value lets the Polly pipeline retry on transient failures while /// false (the default) disables retry per decisions #44/#45. /// public sealed record FocasTagDefinition( string Name, string DeviceHostAddress, string Address, FocasDataType DataType, bool Writable = false, bool WriteIdempotent = false); public sealed class FocasProbeOptions { public bool Enabled { get; init; } = true; public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5); public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); }