fix(modbus): batch event honors deadband — raise from the driver-filtered buffer
This commit is contained in:
@@ -129,6 +129,74 @@ public sealed class ModbusBatchSubscriptionTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The batch event honours the driver's own per-tag deadband filter. The
|
||||
/// <c>PollGroupEngine</c> collects batch items unconditionally — it knows nothing about
|
||||
/// <c>ShouldPublish</c> — so a straight pass-through of the engine's list would carry
|
||||
/// changes the per-tag event suppressed, and a downstream consumer that PREFERS the batch
|
||||
/// event would see deadband silently stop working. A cycle whose only changes are all
|
||||
/// suppressed raises no batch at all.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Deadband_suppressed_changes_are_excluded_from_the_batch()
|
||||
{
|
||||
var (drv, fake) = NewDriver(
|
||||
new ModbusTagDefinition("Temp", ModbusRegion.HoldingRegisters, 0, ModbusDataType.Int16, Deadband: 5.0));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
fake.HoldingRegisters[0] = 100;
|
||||
|
||||
var perTag = new ConcurrentQueue<DataChangeEventArgs>();
|
||||
var batches = new ConcurrentQueue<DataChangeBatchEventArgs>();
|
||||
drv.OnDataChange += (_, e) => perTag.Enqueue(e);
|
||||
((IBatchSubscribable)drv).OnDataChangeBatch += (_, e) => batches.Enqueue(e);
|
||||
|
||||
var handle = await drv.SubscribeAsync(["Temp"], TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
try
|
||||
{
|
||||
// Cycle 1 — initial-data push. First sample seeds the deadband baseline and publishes
|
||||
// through both events.
|
||||
await WaitForCountAsync(batches, 1, TimeSpan.FromSeconds(2));
|
||||
batches.Count.ShouldBe(1);
|
||||
batches.Single().Items.Count.ShouldBe(1);
|
||||
batches.Single().Items[0].Snapshot.Value.ShouldBe((short)100);
|
||||
perTag.Count.ShouldBe(1);
|
||||
|
||||
// Under-threshold change (delta 2 < 5): the ENGINE sees a value change and would offer
|
||||
// it as a batch item, but the driver's deadband suppresses the publish — so neither
|
||||
// event fires and no batch is raised for that cycle at all.
|
||||
fake.HoldingRegisters[0] = 102;
|
||||
await Task.Delay(600);
|
||||
perTag.Count.ShouldBe(1, "under-threshold change is suppressed on the per-tag event");
|
||||
batches.Count.ShouldBe(1, "a cycle whose only change is deadband-suppressed raises no batch");
|
||||
|
||||
// Over-threshold change (delta 10 ≥ 5 against the 100 baseline): publishes on both.
|
||||
fake.HoldingRegisters[0] = 110;
|
||||
await WaitForCountAsync(batches, 2, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Unsubscribe drains the in-flight poll loop, so the queues are stable from here.
|
||||
await drv.UnsubscribeAsync(handle, CancellationToken.None);
|
||||
}
|
||||
|
||||
var allBatches = batches.ToArray();
|
||||
allBatches.Length.ShouldBe(2);
|
||||
allBatches[1].Items.Count.ShouldBe(1);
|
||||
allBatches[1].Items[0].Snapshot.Value.ShouldBe((short)110);
|
||||
|
||||
// Content equality: flattening the batches reproduces the per-tag event stream exactly.
|
||||
// The suppressed 102 appears in NEITHER.
|
||||
var flattened = allBatches.SelectMany(b => b.Items).ToArray();
|
||||
var perTagArray = perTag.ToArray();
|
||||
flattened.Length.ShouldBe(perTagArray.Length);
|
||||
for (var i = 0; i < flattened.Length; i++)
|
||||
{
|
||||
flattened[i].FullReference.ShouldBe(perTagArray[i].FullReference);
|
||||
flattened[i].Snapshot.ShouldBe(perTagArray[i].Snapshot);
|
||||
}
|
||||
flattened.ShouldNotContain(item => Equals(item.Snapshot.Value, (short)102));
|
||||
}
|
||||
|
||||
private static async Task WaitForCountAsync<T>(ConcurrentQueue<T> q, int target, TimeSpan timeout)
|
||||
{
|
||||
var deadline = DateTime.UtcNow + timeout;
|
||||
|
||||
Reference in New Issue
Block a user