Auto: opcuaclient-1 — per-subscription tuning

Closes #273
This commit is contained in:
Joseph Doherty
2026-04-25 15:09:08 -04:00
parent 8314c273e7
commit 7209364c35
3 changed files with 105 additions and 13 deletions

View File

@@ -858,22 +858,24 @@ public sealed class OpcUaClientDriver(OpcUaClientDriverOptions options, string d
var id = Interlocked.Increment(ref _nextSubscriptionId);
var handle = new OpcUaSubscriptionHandle(id);
// Floor the publishing interval at 50ms — OPC UA servers routinely negotiate
// minimum-supported intervals up anyway, but sending sub-50ms wastes negotiation
// bandwidth on every subscription create.
var intervalMs = publishingInterval < TimeSpan.FromMilliseconds(50)
? 50
// Floor the publishing interval — OPC UA servers routinely negotiate
// minimum-supported intervals up anyway, but sending sub-floor values wastes
// negotiation bandwidth on every subscription create. Floor is configurable via
// OpcUaSubscriptionDefaults.MinPublishingIntervalMs (default 50ms).
var subDefaults = _options.Subscriptions;
var intervalMs = publishingInterval < TimeSpan.FromMilliseconds(subDefaults.MinPublishingIntervalMs)
? subDefaults.MinPublishingIntervalMs
: (int)publishingInterval.TotalMilliseconds;
var subscription = new Subscription(telemetry: null!, new SubscriptionOptions
{
DisplayName = $"opcua-sub-{id}",
PublishingInterval = intervalMs,
KeepAliveCount = 10,
LifetimeCount = 1000,
MaxNotificationsPerPublish = 0,
KeepAliveCount = (uint)subDefaults.KeepAliveCount,
LifetimeCount = subDefaults.LifetimeCount,
MaxNotificationsPerPublish = subDefaults.MaxNotificationsPerPublish,
PublishingEnabled = true,
Priority = 0,
Priority = subDefaults.Priority,
TimestampsToReturn = TimestampsToReturn.Both,
});
@@ -975,15 +977,16 @@ public sealed class OpcUaClientDriver(OpcUaClientDriverOptions options, string d
// match in O(1) without re-parsing on every event.
var sourceFilter = new HashSet<string>(sourceNodeIds, StringComparer.Ordinal);
var alarmDefaults = _options.Subscriptions;
var subscription = new Subscription(telemetry: null!, new SubscriptionOptions
{
DisplayName = $"opcua-alarm-sub-{id}",
PublishingInterval = 500, // 500ms — alarms don't need fast polling; the server pushes
KeepAliveCount = 10,
LifetimeCount = 1000,
MaxNotificationsPerPublish = 0,
KeepAliveCount = (uint)alarmDefaults.KeepAliveCount,
LifetimeCount = alarmDefaults.LifetimeCount,
MaxNotificationsPerPublish = alarmDefaults.MaxNotificationsPerPublish,
PublishingEnabled = true,
Priority = 0,
Priority = alarmDefaults.AlarmsPriority,
TimestampsToReturn = TimestampsToReturn.Both,
});

View File

@@ -134,8 +134,58 @@ public sealed class OpcUaClientDriverOptions
/// browse forever.
/// </summary>
public int MaxBrowseDepth { get; init; } = 10;
/// <summary>
/// Per-subscription tuning knobs applied when the driver creates data + alarm
/// subscriptions on the upstream session. Defaults preserve the previous hard-coded
/// values so existing deployments see no behaviour change.
/// </summary>
public OpcUaSubscriptionDefaults Subscriptions { get; init; } = new();
}
/// <summary>
/// Tuning surface for OPC UA subscriptions created by <see cref="OpcUaClientDriver"/>.
/// Lifted from the per-call hard-coded literals so operators can tune publish cadence,
/// keep-alive ratio, and alarm-vs-data prioritisation without recompiling the driver.
/// Defaults match the original hard-coded values (KeepAlive=10, Lifetime=1000,
/// MaxNotifications=0 unlimited, Priority=0, MinPublishingInterval=50ms).
/// </summary>
/// <param name="KeepAliveCount">
/// Number of consecutive empty publish cycles before the server sends a keep-alive
/// response. Default 10 — high enough to suppress idle traffic, low enough that the
/// client notices a stalled subscription within ~5x the publish interval.
/// </param>
/// <param name="LifetimeCount">
/// Number of consecutive missed publish responses before the server tears down the
/// subscription. Must be ≥3×<see cref="KeepAliveCount"/> per OPC UA spec; default 1000
/// gives ~100 keep-alives of slack which is conservative on flaky networks.
/// </param>
/// <param name="MaxNotificationsPerPublish">
/// Cap on notifications returned per publish response. <c>0</c> = unlimited (the OPC UA
/// spec sentinel). Lower this to bound publish-message size on bursty servers.
/// </param>
/// <param name="Priority">
/// Subscription priority for data subscriptions (0..255). Higher = scheduled ahead of
/// lower. Default 0 matches the SDK's default for ordinary tag subscriptions.
/// </param>
/// <param name="MinPublishingIntervalMs">
/// Floor (ms) applied to <c>publishingInterval</c> requests. Sub-floor values are
/// clamped up so wire-side negotiations don't waste round-trips on intervals the server
/// will only round up anyway. Default 50ms.
/// </param>
/// <param name="AlarmsPriority">
/// Subscription priority for the alarm subscription (0..255). Higher than
/// <see cref="Priority"/> by default (1 vs 0) so alarm publishes aren't starved during
/// data-tag bursts.
/// </param>
public sealed record OpcUaSubscriptionDefaults(
int KeepAliveCount = 10,
uint LifetimeCount = 1000,
uint MaxNotificationsPerPublish = 0,
byte Priority = 0,
int MinPublishingIntervalMs = 50,
byte AlarmsPriority = 1);
/// <summary>OPC UA message security mode.</summary>
public enum OpcUaSecurityMode
{