using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.Series; /// /// Live-wire coverage (focas-mock) for #257 (cnc_statinfo status flags) and #266 (PMC /// range coalescing). Skips cleanly when OTOPCUA_FOCAS_SIM_ENDPOINT (default /// localhost:8193) is unreachable. Read-only: no write path is exercised. /// [Collection(FocasSimCollection.Name)] public sealed class WireBackendStatusAndPmcTests { private readonly FocasSimFixture _fx; /// Initializes a new instance of the class. /// The FOCAS simulator fixture. public WireBackendStatusAndPmcTests(FocasSimFixture fx) => _fx = fx; private string DeviceHost => _fx.DeviceUri; /// The mock's cnc_statinfo flags surface through the Status/ subtree and an authored FIXED: tag. [Fact] public async Task Status_flags_populate_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 { sysinfo = new { addinfo = 0, max_axis = 8, cnc_type = "M ", mt_type = "M ", series = "30i ", version = "A1.0", axes = "1 ", }, axis_names = new[] { "X" }, statinfo = new { aut = 1, run = 3, motion = 1, mstb = 0, emergency = 0, alarm = 1, edit = 0 }, }, ct); var driver = new FocasDriver(new FocasDriverOptions { Devices = [new FocasDeviceOptions(DeviceHost)], RawTags = FocasRawTags.Entries( new FocasTagDefinition("RunFlag", DeviceHost, "FIXED:Status/Run", FocasDataType.Int32, Writable: false)), Probe = new FocasProbeOptions { Enabled = false }, FixedTree = new FocasFixedTreeOptions { Enabled = true, PollInterval = TimeSpan.FromMilliseconds(100), ProgramPollInterval = TimeSpan.FromMilliseconds(200), }, }, driverInstanceId: "focas-wire-status", clientFactory: new WireFocasClientFactory()); await using (driver) { await driver.InitializeAsync("{}", ct); await WaitFor(() => driver.GetDeviceState(DeviceHost) is { LastStatus: not null }, TimeSpan.FromSeconds(5)); var state = driver.GetDeviceState(DeviceHost); state.ShouldNotBeNull(); state.LastStatus.ShouldNotBeNull(); state.LastStatus.Run.ShouldBe(3); state.LastStatus.Alarm.ShouldBe(1); var snaps = await driver.ReadAsync(["RunFlag", $"{DeviceHost}/Status/Alarm", $"{DeviceHost}/Status/RunText"], ct); snaps.ShouldAllBe(s => s.StatusCode == FocasStatusMapper.Good); snaps[0].Value.ShouldBe(3); snaps[1].Value.ShouldBe(1); snaps[2].Value.ShouldBe("START"); // The flags follow the machine. await _fx.PatchStateAsync(new { statinfo = new { run = 1, emergency = 1 } }, ct); await WaitFor(() => driver.GetDeviceState(DeviceHost) is { LastStatus.Run: 1 }, TimeSpan.FromSeconds(5)); driver.GetDeviceState(DeviceHost)!.LastStatus!.Emergency.ShouldBe(1); } } private static async Task WaitFor(Func pred, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout; while (DateTime.UtcNow < deadline) { if (pred()) return; await Task.Delay(50); } } }