refactor(driver-focas): extract FocasDriverOptions to .Contracts

Move FocasDriverOptions (and companion option types), FocasCncSeries,
and the FocasDataType enum to a new Driver.FOCAS.Contracts sibling
project. FocasDataTypeExtensions (which uses DriverDataType from
Core.Abstractions) stays in the runtime driver as FocasDataTypeExtensions.cs.

Convert two doc-comment references:
  <see cref="FocasDriver.InitializeAsync"/> → <c>FocasDriver.InitializeAsync</c>
  <see cref="FocasAddress.TryParse"/> → <c>FocasAddress.TryParse</c>
per the approved decision — no compilable usings were present in the
moved files.

The runtime Driver.FOCAS project gains a ProjectReference to .Contracts;
the .slnx is updated accordingly.
This commit is contained in:
Joseph Doherty
2026-05-28 09:13:10 -04:00
parent 9f62f2c242
commit d892ab9e12
7 changed files with 32 additions and 20 deletions
@@ -0,0 +1,128 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
/// <summary>
/// FOCAS driver configuration. One instance supports N CNC devices. Per plan decision #144
/// each device gets its own <c>(DriverInstanceId, HostAddress)</c> bulkhead key at the
/// Phase 6.1 resilience layer.
/// </summary>
public sealed class FocasDriverOptions
{
/// <summary>Gets the list of configured CNC devices.</summary>
public IReadOnlyList<FocasDeviceOptions> Devices { get; init; } = [];
/// <summary>Gets the list of FOCAS tag definitions.</summary>
public IReadOnlyList<FocasTagDefinition> Tags { get; init; } = [];
/// <summary>Gets the probe options.</summary>
public FocasProbeOptions Probe { get; init; } = new();
/// <summary>Gets the timeout duration for operations.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
/// <summary>Gets the alarm projection options.</summary>
public FocasAlarmProjectionOptions AlarmProjection { get; init; } = new();
/// <summary>Gets the handle recycle options.</summary>
public FocasHandleRecycleOptions HandleRecycle { get; init; } = new();
/// <summary>Gets the fixed tree options.</summary>
public FocasFixedTreeOptions FixedTree { get; init; } = new();
}
/// <summary>
/// Fixed-node tree exposed by FOCAS per <c>docs/v2/driver-specs.md §7</c> —
/// <c>Identity/</c>, <c>Axes/{name}/</c>, etc. populated from
/// <c>cnc_sysinfo</c> / <c>cnc_rdaxisname</c> / <c>cnc_rddynamic2</c>. Disabled by
/// default so existing configs that only use user-authored tags don't grow new
/// nodes on upgrade.
/// </summary>
public sealed class FocasFixedTreeOptions
{
/// <summary>Gets or sets a value indicating whether the fixed-node tree is enabled for every configured device.</summary>
public bool Enabled { get; init; } = false;
/// <summary>
/// Poll cadence for <c>cnc_rddynamic2</c>. 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.
/// </summary>
public TimeSpan PollInterval { get; init; } = TimeSpan.FromMilliseconds(250);
/// <summary>
/// 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.
/// </summary>
public TimeSpan ProgramPollInterval { get; init; } = TimeSpan.FromSeconds(1);
/// <summary>
/// Poll cadence for timers (power-on / operating / cutting / cycle).
/// These change at human timescales — default is 30s. Zero / negative
/// disables the timer poll entirely.
/// </summary>
public TimeSpan TimerPollInterval { get; init; } = TimeSpan.FromSeconds(30);
}
/// <summary>
/// Proactive session-recycle cadence. Fanuc CNCs have a finite FWLIB handle pool
/// (~510 concurrent connections) and certain series have documented handle-leak bugs
/// that manifest after long uptime. When <see cref="Enabled"/> is <c>true</c> the
/// driver closes + reopens each device's session on the <see cref="Interval"/> 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public sealed class FocasHandleRecycleOptions
{
/// <summary>Gets or sets a value indicating whether handle recycling is enabled.</summary>
public bool Enabled { get; init; } = false;
/// <summary>Gets or sets the interval for handle recycle operations.</summary>
public TimeSpan Interval { get; init; } = TimeSpan.FromHours(1);
}
/// <summary>
/// Controls the CNC active-alarm polling projection that surfaces FOCAS alarms via
/// <c>IAlarmSource</c>. Disabled by default — operators opt in by setting
/// <see cref="Enabled"/> in <c>appsettings.json</c>.
/// </summary>
public sealed class FocasAlarmProjectionOptions
{
/// <summary>Gets or sets a value indicating whether alarm projection is enabled.</summary>
public bool Enabled { get; init; } = false;
/// <summary>Poll cadence. One <c>cnc_rdalmmsg2</c> call per device per tick.</summary>
public TimeSpan PollInterval { get; init; } = TimeSpan.FromSeconds(2);
}
/// <summary>
/// One CNC the driver talks to. <paramref name="Series"/> enables per-series
/// address validation at <c>FocasDriver.InitializeAsync</c>; leave as
/// <see cref="FocasCncSeries.Unknown"/> to skip validation (legacy behaviour).
/// </summary>
public sealed record FocasDeviceOptions(
string HostAddress,
string? DeviceName = null,
FocasCncSeries Series = FocasCncSeries.Unknown);
/// <summary>
/// One FOCAS-backed OPC UA variable. <paramref name="Address"/> is the canonical FOCAS
/// address string that parses via <c>FocasAddress.TryParse</c> —
/// <c>X0.0</c> / <c>R100</c> / <c>PARAM:1815/0</c> / <c>MACRO:500</c>.
/// </summary>
public sealed record FocasTagDefinition(
string Name,
string DeviceHostAddress,
string Address,
FocasDataType DataType,
bool Writable = true,
bool WriteIdempotent = false);
public sealed class FocasProbeOptions
{
/// <summary>Gets or sets a value indicating whether probing is enabled.</summary>
public bool Enabled { get; init; } = true;
/// <summary>Gets or sets the probe interval.</summary>
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
/// <summary>Gets or sets the probe timeout.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
}