From 40224570006e1155702e238cbaca83b2ed4ed23b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 18 Jun 2026 12:44:35 -0400 Subject: [PATCH] test(focas): integration test for cnc_getfigure position scaling via focas-mock --- .../Series/WireBackendCoverageTests.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs index b45b89f5..52f1b22c 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs @@ -237,6 +237,73 @@ public sealed class WireBackendCoverageTests } } + /// + /// Verifies that per-axis position figures fetched via cnc_getfigure (wire command + /// 0x00D3) scale the published AbsolutePosition, and that the auto figure wins over + /// the configured PositionDecimalPlaces fallback knob. + /// Seed: X axis absolute = 12345, figure = 3 → published value = 12.345 (÷ 10^3). + /// Config knob = 1 → fallback would give 1234.5. 12.345 uniquely proves the wire path. + /// + [Fact] + public async Task Position_figures_scale_axis_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + axis_names = new[] { "X" }, + dynamic = new + { + alarm = 0, prgnum = 1, prgmnum = 1, seqnum = 1, + actf = 0, acts = 0, + axes = new + { + X = new { absolute = 12345, machine = 12345, relative = 0, distance = 0 }, + }, + }, + // Per-axis decimal-place figures for cnc_getfigure (command 0x00D3). + // The mock's _wire_position_figures() reads state["position_figures"][axisName]. + position_figures = new { X = 3 }, + }, ct); + + // PositionDecimalPlaces = 1 is intentionally different from the auto figure (3) + // so the assertion 12.345 uniquely proves the cnc_getfigure path won. + var driver = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost, PositionDecimalPlaces: 1)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + FixedTree = new FocasFixedTreeOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(100), + ProgramPollInterval = TimeSpan.Zero, + TimerPollInterval = TimeSpan.Zero, + }, + }, driverInstanceId: "wire-figures", clientFactory: new WireFocasClientFactory()); + + await using (driver) + { + await driver.InitializeAsync("{}", ct); + + await WaitFor(() => + driver.GetDeviceState(DeviceHost)?.LastFixedSnapshots + .ContainsKey($"{DeviceHost}/Axes/X/AbsolutePosition") == true, + TimeSpan.FromSeconds(5)); + + var state = driver.GetDeviceState(DeviceHost); + state.ShouldNotBeNull(); + var published = state.LastFixedSnapshots[$"{DeviceHost}/Axes/X/AbsolutePosition"]; + + // 12345 ÷ 10^3 = 12.345 → auto figure (3) won. + // 12345 ÷ 10^1 = 1234.5 → would mean config knob (1) was used instead. + // 12345 ÷ 10^0 = 12345.0 → would mean no scaling at all. + published.ShouldBe(12.345, tolerance: 0.0001); + } + } + private static async Task WaitFor(Func pred, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout;