From b857df8a123586e3107adeec9b6f0f847e770cd7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 15 Aug 2026 12:06:53 -0400 Subject: [PATCH] feat(core): add IBatchSubscribable batch data-change contract --- .../ISubscribable.cs | 26 ++++++++++++++++++ .../DataChangeBatchContractTests.cs | 27 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/DataChangeBatchContractTests.cs diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs index 9787bf2d..8e86a671 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs @@ -51,3 +51,29 @@ public sealed record DataChangeEventArgs( ISubscriptionHandle SubscriptionHandle, string FullReference, DataValueSnapshot Snapshot); + +/// One changed attribute within a . +/// Driver-side full reference of the changed attribute. +/// New value + quality + timestamps. +public sealed record DataChangeItem(string FullReference, DataValueSnapshot Snapshot); + +/// Event payload for — every attribute +/// that changed within ONE production burst (a poll cycle, an inbound message, a stream chunk). +/// The handle returned by the original call. +/// The changed attributes, one entry per changed tag. Never empty. +public sealed record DataChangeBatchEventArgs( + ISubscriptionHandle SubscriptionHandle, + IReadOnlyList Items); + +/// +/// Optional driver capability: data changes delivered one event per production burst instead of +/// one event per tag. A driver implementing this raises once per +/// burst AND continues raising the per-tag for each item +/// (legacy consumers: Galaxy cold reads, driver CLIs). A consumer subscribes to EXACTLY ONE of the +/// two events — subscribing both double-delivers. DriverInstanceActor prefers the batch event. +/// +public interface IBatchSubscribable : ISubscribable +{ + /// Batched data-change notification — one raise per production burst. + event EventHandler? OnDataChangeBatch; +} diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/DataChangeBatchContractTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/DataChangeBatchContractTests.cs new file mode 100644 index 00000000..738600c8 --- /dev/null +++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/DataChangeBatchContractTests.cs @@ -0,0 +1,27 @@ +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Core.Abstractions; + +namespace ZB.MOM.WW.OtOpcUa.Core.Tests; + +/// Contract tests for the batched data-change payload types. +[Trait("Category", "Unit")] +public sealed class DataChangeBatchContractTests +{ + private sealed record FakeHandle(string DiagnosticId) : ISubscriptionHandle; + + /// Verifies the batch payload carries the subscription handle and its per-tag items. + [Fact] + public void Batch_args_carry_handle_and_items() + { + var handle = new FakeHandle("h1"); + var snap = new DataValueSnapshot(42, 0u, DateTime.UtcNow, DateTime.UtcNow); + + var args = new DataChangeBatchEventArgs(handle, new[] { new DataChangeItem("plc/tag1", snap) }); + + args.SubscriptionHandle.ShouldBe(handle); + args.Items.Count.ShouldBe(1); + args.Items[0].FullReference.ShouldBe("plc/tag1"); + args.Items[0].Snapshot.ShouldBe(snap); + } +}