Files
lmxopcua/src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/ISubscribable.cs
2026-04-25 15:25:20 -04:00

159 lines
7.8 KiB
C#

namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
/// <summary>
/// Driver capability for data-change subscriptions — covers both native subscriptions
/// (Galaxy MXAccess advisory, OPC UA monitored items, TwinCAT ADS notifications) and
/// driver-internal polled subscriptions (Modbus, AB CIP, S7, FOCAS). The driver owns
/// its polling loop where applicable; the Core just sees <see cref="OnDataChange"/>
/// callbacks regardless of mechanism.
/// </summary>
public interface ISubscribable
{
/// <summary>
/// Subscribe to data changes for a batch of attributes.
/// The driver MAY fire <see cref="OnDataChange"/> immediately with the current value
/// (initial-data callback per OPC UA convention) and again on every change.
/// </summary>
/// <returns>An opaque subscription handle the caller passes to <see cref="UnsubscribeAsync"/>.</returns>
Task<ISubscriptionHandle> SubscribeAsync(
IReadOnlyList<string> fullReferences,
TimeSpan publishingInterval,
CancellationToken cancellationToken);
/// <summary>
/// Subscribe to data changes with per-tag advanced tuning (sampling interval, queue
/// size, monitoring mode, deadband filter). Drivers that don't have a native concept
/// of these knobs (e.g. polled drivers like Modbus) MAY ignore the per-tag knobs and
/// delegate to the simple
/// <see cref="SubscribeAsync(IReadOnlyList{string}, TimeSpan, CancellationToken)"/>
/// overload — the default implementation does exactly that, so existing implementers
/// compile unchanged.
/// </summary>
/// <param name="tags">Per-tag subscription specs. <see cref="MonitoredTagSpec.TagName"/> is the driver-side full reference.</param>
/// <param name="publishingInterval">Subscription publishing interval, applied to the whole batch.</param>
/// <param name="cancellationToken">Cancellation.</param>
/// <returns>Opaque subscription handle for <see cref="UnsubscribeAsync"/>.</returns>
Task<ISubscriptionHandle> SubscribeAsync(
IReadOnlyList<MonitoredTagSpec> tags,
TimeSpan publishingInterval,
CancellationToken cancellationToken)
=> SubscribeAsync(
tags.Select(t => t.TagName).ToList(),
publishingInterval,
cancellationToken);
/// <summary>Cancel a subscription returned by either <c>SubscribeAsync</c> overload.</summary>
Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken);
/// <summary>
/// Server-pushed data-change notification. Fires whenever a subscribed attribute changes,
/// and (per OPC UA convention) on subscription establishment for current values.
/// </summary>
event EventHandler<DataChangeEventArgs>? OnDataChange;
}
/// <summary>Opaque subscription identity returned by <see cref="ISubscribable.SubscribeAsync(IReadOnlyList{string}, TimeSpan, CancellationToken)"/>.</summary>
public interface ISubscriptionHandle
{
/// <summary>Driver-internal subscription identifier (for diagnostics + post-mortem).</summary>
string DiagnosticId { get; }
}
/// <summary>Event payload for <see cref="ISubscribable.OnDataChange"/>.</summary>
/// <param name="SubscriptionHandle">The handle returned by the original <see cref="ISubscribable.SubscribeAsync(IReadOnlyList{string}, TimeSpan, CancellationToken)"/> call.</param>
/// <param name="FullReference">Driver-side full reference of the changed attribute.</param>
/// <param name="Snapshot">New value + quality + timestamps.</param>
public sealed record DataChangeEventArgs(
ISubscriptionHandle SubscriptionHandle,
string FullReference,
DataValueSnapshot Snapshot);
/// <summary>
/// Per-tag subscription tuning. Maps onto OPC UA <c>MonitoredItem</c> properties for the
/// OpcUaClient driver; non-OPC-UA drivers either map a subset (e.g. ADS picks up
/// <see cref="SamplingIntervalMs"/>) or ignore the knobs entirely and fall back to the
/// simple <see cref="ISubscribable.SubscribeAsync(IReadOnlyList{string}, TimeSpan, CancellationToken)"/>.
/// </summary>
/// <param name="TagName">Driver-side full reference (e.g. <c>ns=2;s=Foo</c> for OPC UA).</param>
/// <param name="SamplingIntervalMs">
/// Server-side sampling rate in milliseconds. <c>null</c> = use the publishing interval.
/// Sub-publish-interval values let a server sample faster than it publishes (queue +
/// coalesce), useful for events that change between publish ticks.
/// </param>
/// <param name="QueueSize">Server-side notification queue depth. <c>null</c> = driver default (1).</param>
/// <param name="DiscardOldest">
/// When the server-side queue overflows: <c>true</c> drops oldest, <c>false</c> drops newest.
/// <c>null</c> = driver default (true — preserve recency).
/// </param>
/// <param name="MonitoringMode">
/// Per-item monitoring mode. <c>Reporting</c> = sample + publish, <c>Sampling</c> = sample
/// but suppress publishing (useful with triggering), <c>Disabled</c> = neither.
/// </param>
/// <param name="DataChangeFilter">
/// Optional data-change filter (deadband + trigger semantics). <c>null</c> = no filter
/// (every change publishes regardless of magnitude).
/// </param>
public sealed record MonitoredTagSpec(
string TagName,
double? SamplingIntervalMs = null,
uint? QueueSize = null,
bool? DiscardOldest = null,
SubscriptionMonitoringMode? MonitoringMode = null,
DataChangeFilterSpec? DataChangeFilter = null);
/// <summary>
/// OPC UA <c>DataChangeFilter</c> spec. Mirrors the OPC UA Part 4 §7.17.2 structure but
/// lives in Core.Abstractions so non-OpcUaClient drivers (e.g. Modbus, S7) can accept it
/// as metadata even if they ignore the deadband mechanics.
/// </summary>
/// <param name="Trigger">When to fire: status only / status+value / status+value+timestamp.</param>
/// <param name="DeadbandType">Deadband mode: none / absolute (engineering units) / percent of EURange.</param>
/// <param name="DeadbandValue">
/// Magnitude of the deadband. For <see cref="OtOpcUa.Core.Abstractions.DeadbandType.Absolute"/>
/// this is in the variable's engineering units; for <see cref="OtOpcUa.Core.Abstractions.DeadbandType.Percent"/>
/// it's a 0..100 percentage of EURange (server returns BadFilterNotAllowed if EURange isn't set).
/// </param>
public sealed record DataChangeFilterSpec(
DataChangeTrigger Trigger,
DeadbandType DeadbandType,
double DeadbandValue);
/// <summary>
/// OPC UA <c>DataChangeTrigger</c> values. Wraps the SDK enum so Core.Abstractions doesn't
/// leak an OPC-UA-stack reference into every driver project.
/// </summary>
public enum DataChangeTrigger
{
/// <summary>Fire only when StatusCode changes.</summary>
Status = 0,
/// <summary>Fire when StatusCode or Value changes (the OPC UA default).</summary>
StatusValue = 1,
/// <summary>Fire when StatusCode, Value, or SourceTimestamp changes.</summary>
StatusValueTimestamp = 2,
}
/// <summary>OPC UA deadband-filter modes.</summary>
public enum DeadbandType
{
/// <summary>No deadband — every value change publishes.</summary>
None = 0,
/// <summary>Deadband expressed in the variable's engineering units.</summary>
Absolute = 1,
/// <summary>Deadband expressed as 0..100 percent of the variable's EURange.</summary>
Percent = 2,
}
/// <summary>
/// Per-item subscription monitoring mode. Wraps the OPC UA SDK's <c>MonitoringMode</c>
/// so Core.Abstractions stays SDK-free.
/// </summary>
public enum SubscriptionMonitoringMode
{
/// <summary>Item is created but neither sampling nor publishing.</summary>
Disabled = 0,
/// <summary>Item samples and queues but does not publish (useful with triggering).</summary>
Sampling = 1,
/// <summary>Item samples and publishes — the OPC UA default.</summary>
Reporting = 2,
}