97020d0527
First review at 7286d320. -001 re-triaged Won't-Fix: the 'FOCAS is read-only' premise was
WRONG (FOCAS supports PMC/data writes); flipping Writable default to false broke 6 consuming
write tests -> reverted to true, doc corrected. -003/-004 doc fixes resolved. -002
(WriteIdempotent not threaded, Driver.FOCAS) Open.
187 lines
9.1 KiB
C#
187 lines
9.1 KiB
C#
using System.ComponentModel.DataAnnotations;
|
||
|
||
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>
|
||
/// 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.
|
||
/// </summary>
|
||
[Display(Name = "Probe timeout (seconds)", Description = "Connection test timeout. Default 10s.", GroupName = "Diagnostics")]
|
||
[Range(1, 60)]
|
||
public int ProbeTimeoutSeconds { get; init; } = 10;
|
||
}
|
||
|
||
/// <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
|
||
/// (~5–10 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>
|
||
/// <param name="HostAddress">
|
||
/// FOCAS TCP endpoint in <c>focas://{ip}[:{port}]</c> form, e.g.
|
||
/// <c>focas://10.20.30.40:8193</c>. The port defaults to 8193 when omitted.
|
||
/// Parsed by <c>FocasHostAddress.TryParse</c> at <c>FocasDriver.InitializeAsync</c>.
|
||
/// </param>
|
||
/// <param name="DeviceName">
|
||
/// Optional human-readable label shown in the OPC UA address-space folder for this
|
||
/// device. Defaults to <paramref name="HostAddress"/> when <c>null</c>.
|
||
/// </param>
|
||
/// <param name="Series">
|
||
/// CNC controller series. Used by <see cref="FocasCapabilityMatrix"/> to gate which
|
||
/// FOCAS addresses are valid. Leave as <see cref="FocasCncSeries.Unknown"/> (the
|
||
/// default) to skip range validation and preserve legacy behaviour.
|
||
/// </param>
|
||
/// <param name="PositionDecimalPlaces">
|
||
/// Axis positions returned by <c>cnc_rddynamic2</c> are scaled integers. The driver
|
||
/// divides AbsolutePosition / MachinePosition / RelativePosition / DistanceToGo by
|
||
/// <c>10^PositionDecimalPlaces</c> at the publish seam so they surface in engineering
|
||
/// units on the Float64 axis nodes. Default <c>0</c> (no scaling) is byte-identical to
|
||
/// legacy behaviour. Auto-fetching this via <c>cnc_getfigure</c> is deferred (wire-gated),
|
||
/// so it is config-supplied. Negative values are clamped to 0 (no scaling).
|
||
/// </param>
|
||
public sealed record FocasDeviceOptions(
|
||
string HostAddress,
|
||
string? DeviceName = null,
|
||
FocasCncSeries Series = FocasCncSeries.Unknown,
|
||
int PositionDecimalPlaces = 0)
|
||
{
|
||
/// <summary>
|
||
/// Axis-position decimal places, clamped to a non-negative value so the
|
||
/// <c>10^PositionDecimalPlaces</c> divide at the publish seam can never misbehave.
|
||
/// </summary>
|
||
public int PositionDecimalPlaces { get; init; } =
|
||
PositionDecimalPlaces < 0 ? 0 : PositionDecimalPlaces;
|
||
}
|
||
|
||
/// <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>
|
||
/// <param name="Writable">
|
||
/// Whether the tag is declared writable in config. Reflected at the
|
||
/// <c>DiscoverAsync</c> address-space advertisement seam and gates the driver's
|
||
/// write path — FOCAS supports PMC/data writes (see <c>FocasPmcBitRmwTests</c> /
|
||
/// <c>FocasReadWriteTests</c>). Defaults to <c>true</c>.
|
||
/// </param>
|
||
/// <param name="WriteIdempotent">
|
||
/// Whether repeated writes of the same value are safe. Carried for parity; not yet
|
||
/// threaded through to <c>DriverAttributeInfo</c> in <c>DiscoverAsync</c> (see
|
||
/// Driver.FOCAS.Contracts-002). Defaults to <c>false</c>.
|
||
/// </param>
|
||
public sealed record FocasTagDefinition(
|
||
string Name,
|
||
string DeviceHostAddress,
|
||
string Address,
|
||
FocasDataType DataType,
|
||
bool Writable = true,
|
||
bool WriteIdempotent = false);
|
||
|
||
/// <summary>
|
||
/// Controls periodic connectivity probing. One <c>cnc_rdcncstat</c> call per
|
||
/// configured device per tick; transitions fire <c>OnHostStatusChanged</c>.
|
||
/// Enabled by default so the driver surfaces a fast host-state transition when
|
||
/// a CNC goes offline between data reads.
|
||
/// </summary>
|
||
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);
|
||
}
|