diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs
index 2d6c6d06..78108f1d 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs
@@ -24,7 +24,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
/// (called in ); the library's own finalizer
/// handles GC-collected tags.
///
-public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable,
+public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable, IBatchSubscribable,
IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource, ISkippedTagReporter, IDisposable, IAsyncDisposable
{
/// Authored tags this driver could not build into its poll table (Gitea #541).
@@ -114,6 +114,11 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
/// Occurs when a subscribed tag's value changes.
public event EventHandler? OnDataChange;
+ /// Occurs once per poll cycle carrying every tag that changed in that cycle. The driver raises
+ /// BOTH events for every burst — this one once, and once per changed tag — so
+ /// a consumer must subscribe to EXACTLY ONE of the two or it double-delivers.
+ public event EventHandler? OnDataChangeBatch;
+
/// Occurs when a device's host connectivity status changes.
public event EventHandler? OnHostStatusChanged;
@@ -151,7 +156,9 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
onChange: (handle, tagRef, snapshot) =>
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot)),
onError: HandlePollError,
- backoffCap: PollBackoffCap);
+ backoffCap: PollBackoffCap,
+ onBatch: (handle, items) =>
+ OnDataChangeBatch?.Invoke(this, new DataChangeBatchEventArgs(handle, items)));
_alarmProjection = new AbCipAlarmProjection(this, _options.AlarmPollInterval, _logger);
}
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs
index 8c9aabfc..8ec0a047 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs
@@ -18,7 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
/// + the default makes misconfigured servers
/// fail fast.
///
-public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable,
+public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable, IBatchSubscribable,
IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource, ISkippedTagReporter, IDisposable, IAsyncDisposable
{
/// Authored tags this driver could not build into its poll table (Gitea #541). FOCAS has three
@@ -80,6 +80,10 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
/// Occurs when data changes on a subscribed tag.
public event EventHandler? OnDataChange;
+ /// Occurs once per poll cycle carrying every tag that changed in that cycle. The driver raises
+ /// BOTH events for every burst — this one once, and once per changed tag — so
+ /// a consumer must subscribe to EXACTLY ONE of the two or it double-delivers.
+ public event EventHandler? OnDataChangeBatch;
/// Occurs when a device host connection status changes.
public event EventHandler? OnHostStatusChanged;
/// Occurs when an alarm event is raised.
@@ -112,7 +116,9 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
onChange: (handle, tagRef, snapshot) =>
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot)),
onError: HandlePollError,
- backoffCap: PollBackoffCap);
+ backoffCap: PollBackoffCap,
+ onBatch: (handle, items) =>
+ OnDataChangeBatch?.Invoke(this, new DataChangeBatchEventArgs(handle, items)));
}
/// True when the supplied DriverConfig JSON carries a real body. The bootstrapper always passes