From 3fcbc70cbac1ff38d270982cf95f560b6557cf4f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 16 Jun 2026 19:38:49 -0400 Subject: [PATCH] feat(focas): add cnc_getfigure per-axis position-figure client binding --- .../ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs | 13 +++++++++++-- .../Wire/WireFocasClient.cs | 12 ++++++++++++ .../FakeFocasClient.cs | 7 +++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs index fedca8d4..ce5486c1 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs @@ -153,6 +153,15 @@ public interface IFocasClient : IDisposable /// /// The cancellation token. Task> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken); + + /// + /// Read the per-axis position decimal-place figure via cnc_getfigure. The returned + /// list is parallel to (index = axis). An EMPTY list means the + /// CNC/backend does not report figures — the driver then falls back to the configured + /// PositionDecimalPlaces. Values are clamped non-negative. + /// + /// The cancellation token. + Task> GetPositionFiguresAsync(CancellationToken cancellationToken); } /// One servo-meter entry — one axis's current load percentage. @@ -217,8 +226,8 @@ public sealed record FocasSpindleName(string Name, string Suffix1, string Suffix /// /// Fast-poll bundle for one axis. Position values are scaled integers; the caller /// divides by 10^DecimalPlaces to get the decimal value. DecimalPlaces is -/// currently left to the caller to supply (via device config or a future -/// cnc_getfigure path once that export lands). +/// supplied by the caller — either via device config (PositionDecimalPlaces) or +/// the per-axis (cnc_getfigure) path. /// /// /// Program + operation-mode snapshot. Name is the currently-executing diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs index 58eb4682..003a146e 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs @@ -284,6 +284,18 @@ public sealed class WireFocasClient : IFocasClient public Task> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken) => ReadSpindleMetricAsync((sel, ct) => _wire.ReadSpindleMaxRpmAsync(sel, ct), cancellationToken); + /// Gets the per-axis position decimal-place figures via cnc_getfigure. + /// Cancellation token for the operation. + /// + /// An empty list — the managed FOCAS/2 Ethernet wire client () + /// does not currently expose the cnc_getfigure command, so this backend reports no + /// per-axis figures. Per the contract an + /// empty list signals the driver to fall back to the configured PositionDecimalPlaces. + /// Never throws — figure reads degrade to the config knob rather than faulting. + /// + public Task> GetPositionFiguresAsync(CancellationToken cancellationToken) => + Task.FromResult>(Array.Empty()); + private static async Task> ReadSpindleMetricAsync( Func>>> call, CancellationToken cancellationToken) diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs index 3cd1ed28..a488df99 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs @@ -153,6 +153,13 @@ internal class FakeFocasClient : IFocasClient public virtual Task> GetSpindleMaxRpmsAsync(CancellationToken ct) => Task.FromResult>([.. SpindleMaxRpms]); + /// Gets or sets the per-axis position decimal-place figures returned by . + public IReadOnlyList PositionFigures { get; set; } = []; + /// Gets the per-axis position decimal-place figures asynchronously. + /// The cancellation token. + public virtual Task> GetPositionFiguresAsync(CancellationToken ct) => + Task.FromResult(PositionFigures); + /// Disposes the client. public virtual void Dispose() {