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()
{