using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests; /// /// Review 07 H2: the deadband baseline is keyed on (value, status). A Good-after-Bad recovery carrying /// the IDENTICAL value must publish — the value-only dedup left the node Bad until the value drifted. /// [Trait("Category", "Unit")] public sealed class ModbusDeadbandRecoveryTests { private const uint Good = 0u; private const uint Uncertain = 0x40000000u; private const uint BadComm = 0x80050000u; private static async Task NewDriverAsync() { var tag = new ModbusTagDefinition("Temp", ModbusRegion.HoldingRegisters, 0, ModbusDataType.Int16, Deadband: 5.0); var opts = new ModbusDriverOptions { Host = "f", RawTags = ModbusRawTags.Entries([tag]), Probe = new ModbusProbeOptions { Enabled = false }, }; var drv = new ModbusDriver(opts, "m1", _ => new ModbusDriverTests.FakeTransport()); await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); return drv; } private static DataValueSnapshot Snap(object? v, uint status) => new(v, status, DateTime.UtcNow, DateTime.UtcNow); [Fact] public async Task Good_after_Bad_with_the_same_value_publishes() { await using var drv = await NewDriverAsync(); drv.ShouldPublish("Temp", Snap((short)10, Good)).ShouldBeTrue(); // baseline drv.ShouldPublish("Temp", Snap(null, BadComm)).ShouldBeTrue(); // comms drop drv.ShouldPublish("Temp", Snap((short)10, Good)).ShouldBeTrue("the recovery publish must not be swallowed"); // Positive control: the deadband is still active after the recovery re-seeded the baseline. drv.ShouldPublish("Temp", Snap((short)11, Good)).ShouldBeFalse(); drv.ShouldPublish("Temp", Snap((short)16, Good)).ShouldBeTrue(); } [Fact] public async Task A_status_change_with_the_same_value_publishes() { await using var drv = await NewDriverAsync(); drv.ShouldPublish("Temp", Snap((short)10, Good)).ShouldBeTrue(); drv.ShouldPublish("Temp", Snap((short)10, Uncertain)).ShouldBeTrue("a quality change must publish"); drv.ShouldPublish("Temp", Snap((short)12, Uncertain)).ShouldBeFalse("within deadband at the same quality"); drv.ShouldPublish("Temp", Snap((short)10, Good)).ShouldBeTrue("back to Good must publish"); } }