fix(driver-focas): resolve Low code-review findings (Driver.FOCAS-007,008,009,010,011)

- Driver.FOCAS-007: optional ILogger<FocasDriver> + alarm-projection
  logger; log Debug around every formerly-empty catch (probe / shutdown
  / fixed-tree / recycle / alarms-read / projection).
- Driver.FOCAS-008: cache the parsed FocasAddress per tag at
  InitializeAsync; Read/WriteAsync look it up instead of re-parsing on
  every call.
- Driver.FOCAS-009: ProbeLoopAsync now wraps client.ProbeAsync in a
  linked CTS honouring Probe.Timeout so a hung CNC socket can't block
  past the configured limit.
- Driver.FOCAS-010: FocasOperationModeExtensions.ToText delegates to
  FocasOpMode.ToText — single canonical op-mode label surface.
- Driver.FOCAS-011: FocasAlarmType constants are typed short to match
  the cnc_rdalmmsg2 wire field and the projection switch arms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-23 07:45:38 -04:00
parent f7e3e9885e
commit 6575c6e5f6
8 changed files with 522 additions and 64 deletions

View File

@@ -58,23 +58,13 @@ public static class FocasOperationModeExtensions
{
/// <summary>
/// Canonical operator-facing label for an operation mode (e.g. <c>"AUTO"</c>,
/// <c>"EDIT"</c>). Unknown codes fall back to the raw numeric value as a string
/// so the UI still shows something interpretable.
/// <c>"EDIT"</c>). Delegates to <see cref="FocasOpMode.ToText"/> so the wire layer
/// and the fixed-tree projection render identical labels — historically these two
/// surfaces diverged ("TJOG" vs "T-JOG", "TEACH_IN_HANDLE" vs "TEACH-IN-HANDLE",
/// and different unknown-code fallbacks). Resolved by Driver.FOCAS-010.
/// </summary>
public static string ToText(this FocasOperationMode mode) => mode switch
{
FocasOperationMode.Mdi => "MDI",
FocasOperationMode.Auto => "AUTO",
FocasOperationMode.TJog => "T-JOG",
FocasOperationMode.Edit => "EDIT",
FocasOperationMode.Handle => "HANDLE",
FocasOperationMode.Jog => "JOG",
FocasOperationMode.TeachInHandle => "TEACH-IN-HANDLE",
FocasOperationMode.Reference => "REFERENCE",
FocasOperationMode.Remote => "REMOTE",
FocasOperationMode.Test => "TEST",
_ => ((short)mode).ToString(),
};
public static string ToText(this FocasOperationMode mode) =>
FocasOpMode.ToText((short)mode);
}
/// <summary>

View File

@@ -1,3 +1,5 @@
using Microsoft.Extensions.Logging;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire;
/// <summary>
@@ -14,9 +16,25 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire;
/// </remarks>
public sealed class WireFocasClient : IFocasClient
{
private readonly FocasWireClient _wire = new();
private readonly FocasWireClient _wire;
private FocasHostAddress? _address;
/// <summary>
/// Default constructor — wire client without logger. Selected by the legacy
/// no-arg <see cref="WireFocasClientFactory.Create"/> path.
/// </summary>
public WireFocasClient() : this(logger: null) { }
/// <summary>
/// Construct with an optional logger. Threaded through to
/// <see cref="FocasWireClient"/> so the per-response Debug entries actually reach
/// the host's logging pipeline (Driver.FOCAS-007).
/// </summary>
public WireFocasClient(ILogger<FocasWireClient>? logger)
{
_wire = new FocasWireClient(logger);
}
public bool IsConnected => _wire.IsConnected;
public async Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken)
@@ -339,5 +357,20 @@ public sealed class WireFocasClient : IFocasClient
/// <summary>Factory producing <see cref="WireFocasClient"/> instances — one per configured device.</summary>
public sealed class WireFocasClientFactory : IFocasClientFactory
{
public IFocasClient Create() => new WireFocasClient();
private readonly ILogger<FocasWireClient>? _logger;
public WireFocasClientFactory() : this(logger: null) { }
/// <summary>
/// Construct the factory with a logger that every created <see cref="WireFocasClient"/>
/// forwards to its <see cref="FocasWireClient"/>. Resolves Driver.FOCAS-007 — the wire
/// client already emits Debug entries per FOCAS response, but the previous no-arg
/// factory path discarded them.
/// </summary>
public WireFocasClientFactory(ILogger<FocasWireClient>? logger)
{
_logger = logger;
}
public IFocasClient Create() => new WireFocasClient(_logger);
}