From 47aea46fe3df3b0111d20b04b7354e26031f507b Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Wed, 16 Oct 2024 17:52:50 +0200 Subject: [PATCH] Updates --- docs/opc-publisher/directmethods.md | 4 +- ...ure.IIoT.OpcUa.Publisher.Module.Cli.csproj | 11 +- .../MachineTools.init} | 0 .../cli/Initfiles/Machinery.init | 20 +++ .../cli/Initfiles/Objects.init | 21 +++ .../cli/Initfiles/Variables.init | 26 +++ .../cli/Program.cs | 145 +++++++++++++--- .../src/Stack/Services/OpcUaMonitoredItem.cs | 4 +- .../src/Stack/Services/OpcUaSubscription.cs | 161 +++++++++++------- 9 files changed, 302 insertions(+), 90 deletions(-) rename src/Azure.IIoT.OpcUa.Publisher.Module/cli/{publishednodes.init => Initfiles/MachineTools.init} (100%) create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Machinery.init create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Objects.init create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Variables.init diff --git a/docs/opc-publisher/directmethods.md b/docs/opc-publisher/directmethods.md index b1fd7e30e4..0f27fa6393 100644 --- a/docs/opc-publisher/directmethods.md +++ b/docs/opc-publisher/directmethods.md @@ -86,7 +86,7 @@ The certificate Key can also be read from the IoT Hub device twin as `__certifi ## GetApiKey_V2 -This API call allows a caller to programmatically obtain the current API key. The API key is passed in the `Authorization` header of the HTTP request sent to the HTTP endpoint. The format of the header value is `ApiKey `, e.g., `ApiKey 856F93F75DD847DE87E22953E7DFE478`. +This API call allows a caller to programmatically obtain the current API key. The API key is passed in the `Authorization` header of the HTTP request sent to the HTTP endpoint. The format of the header value is `ApiKey `, e.g., `ApiKey 85.........F78`. _Request_: follows strictly the request [payload schema](./definitions.md#publishednodesentrymodel), the `OpcNodes` attribute being mandatory. @@ -107,7 +107,7 @@ This API call allows a caller to programmatically obtain the current API key. Th > _Response_: > > ```json - > "856F93F75DD847DE87E22953E7DFE478" + > "85.........F78" > ``` The API Key can also be read from the IoT Hub device twin as the `__apikey__` reported property. diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Azure.IIoT.OpcUa.Publisher.Module.Cli.csproj b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Azure.IIoT.OpcUa.Publisher.Module.Cli.csproj index f02032b0ea..9141d11d39 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Azure.IIoT.OpcUa.Publisher.Module.Cli.csproj +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Azure.IIoT.OpcUa.Publisher.Module.Cli.csproj @@ -22,11 +22,14 @@ Always - + + Always + + + + + Always - - - \ No newline at end of file diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/publishednodes.init b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/MachineTools.init similarity index 100% rename from src/Azure.IIoT.OpcUa.Publisher.Module/cli/publishednodes.init rename to src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/MachineTools.init diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Machinery.init b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Machinery.init new file mode 100644 index 0000000000..47a6b71564 --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Machinery.init @@ -0,0 +1,20 @@ +### +// @delay 5 +ExpandAndCreateOrUpdateDataSetWriterEntries + +{ + "entry": { + "EndpointUrl": "opc.tcp://opcua.umati.app:4840", + "UseSecurity": false, + "DataSetWriterGroup": "Machinery Objects", + "OpcNodes": [ + { "Id": "nsu=http://opcfoundation.org/UA/Machinery/;i=1001" } + ] + } +} + +// @retries 3 +### +Shutdown +// @on-error +### diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Objects.init b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Objects.init new file mode 100644 index 0000000000..466fe6da59 --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Objects.init @@ -0,0 +1,21 @@ +### +// @delay 5 +// @retries 3 +ExpandAndCreateOrUpdateDataSetWriterEntries_V2 + +{ + "entry": { + "EndpointUrl": "{{EndpointUrl}}", + "UseSecurity": false, + "DataSetWriterGroup": "Objects", + "OpcNodes": [ + { "Id": "i=85" } + ] + } +} + +### +# @on-error +Shutdown_V2 +true +### diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Variables.init b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Variables.init new file mode 100644 index 0000000000..b66e94b162 --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Initfiles/Variables.init @@ -0,0 +1,26 @@ +### +// @delay 5 +// @retries 3 +ExpandAndCreateOrUpdateDataSetWriterEntries_V2 + +{ + "entry": { + "EndpointUrl": "{{EndpointUrl}}", + "UseSecurity": false, + "DataSetWriterGroup": "Variables", + "OpcNodes": [ + { "Id": "i=85" } + ] + }, + "request": { + "createSingleWriter": true, + "maxDepth": 10, + "discardErrors": true + } +} + +### +# @on-error +Shutdown_V2 +true +### diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Program.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Program.cs index 7bf493d184..a73d956a3f 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Program.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/cli/Program.cs @@ -53,6 +53,8 @@ public static void Main(string[] args) var instances = 1; string? connectionString = null; string? publishProfile = null; + string? publishInitProfile = null; + string? publishInitFilePath = null; string? publishedNodesFilePath = null; var useNullTransport = false; string? dumpMessages = null; @@ -155,6 +157,16 @@ public static void Main(string[] args) break; } throw new ArgumentException("Missing argument for --publish-profile"); + case "-I": + case "--init-profile": + i++; + if (i < args.Length) + { + publishInitProfile = args[i]; + withServer = true; + break; + } + throw new ArgumentException("Missing argument for --init-profile"); case "--pnjson": i++; if (i < args.Length) @@ -163,6 +175,14 @@ public static void Main(string[] args) break; } throw new ArgumentException("Missing argument for --pnjson"); + case "--init": + i++; + if (i < args.Length) + { + publishInitFilePath = args[i]; + break; + } + throw new ArgumentException("Missing argument for --init"); case "--": break; default: @@ -235,19 +255,36 @@ public static void Main(string[] args) { if (dumpMessages != null) { - hostingTask = DumpMessagesAsync(dumpMessages, publishProfile, loggerFactory, - TimeSpan.FromMinutes(2), scaleunits, dumpMessagesOutput, args, cts.Token); + hostingTask = DumpMessagesAsync(dumpMessages, publishProfile, publishInitProfile, + loggerFactory, TimeSpan.FromMinutes(2), scaleunits, dumpMessagesOutput, args, + cts.Token); } else if (!withServer) { + if (publishInitFilePath != null && !File.Exists(publishInitFilePath)) + { + publishInitFilePath = $"./Initfiles/{publishInitFilePath}.init"; + if (File.Exists(publishInitFilePath)) + { + const string copyTo = "profile.init"; + File.Copy(publishInitFilePath, copyTo, true); + File.SetLastWriteTimeUtc(copyTo, DateTime.UtcNow); + publishInitFilePath = copyTo; + } + else + { + publishInitFilePath = null; + } + } hostingTask = HostAsync(connectionString, loggerFactory, deviceId, moduleId, args, reverseConnectPort, !checkTrust, - publishedNodesFilePath, cts.Token); + publishInitFilePath, publishedNodesFilePath, cts.Token); } else { - hostingTask = WithServerAsync(connectionString, loggerFactory, deviceId, moduleId, args, - publishProfile, scaleunits, !checkTrust, reverseConnectPort, cts.Token); + hostingTask = WithServerAsync(connectionString, loggerFactory, deviceId, + moduleId, args, publishProfile, publishInitProfile, scaleunits, + !checkTrust, reverseConnectPort, cts.Token); } while (!cts.Token.IsCancellationRequested) @@ -295,11 +332,13 @@ public static void Main(string[] args) /// /// /// + /// /// /// private static async Task HostAsync(string? connectionString, ILoggerFactory loggerFactory, string deviceId, string moduleId, string[] args, int? reverseConnectPort, - bool acceptAll, string? publishedNodesFilePath = null, CancellationToken ct = default) + bool acceptAll, string? publishInitFile, string? publishedNodesFilePath = null, + CancellationToken ct = default) { var logger = loggerFactory.CreateLogger(); logger.LogInformation("Create or retrieve connection string for {DeviceId} {ModuleId}...", @@ -332,7 +371,7 @@ private static async Task HostAsync(string? connectionString, ILoggerFactory log using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); var running = RunAsync(logger, deviceId, moduleId, args, acceptAll, cs, - reverseConnectPort, publishedNodesFilePath, cts.Token); + reverseConnectPort, publishedNodesFilePath, publishInitFile, cts.Token); Console.WriteLine("Publisher running (Press P to restart)..."); await _restartPublisher.WaitAsync(ct).ConfigureAwait(false); @@ -346,12 +385,17 @@ private static async Task HostAsync(string? connectionString, ILoggerFactory log static async Task RunAsync(ILogger logger, string deviceId, string moduleId, string[] args, bool acceptAll, ConnectionString? cs, int? reverseConnectPort, string? publishedNodesFilePath, - CancellationToken ct) + string? publishInitFile, CancellationToken ct) { logger.LogInformation("Starting publisher module {DeviceId} {ModuleId}...", deviceId, moduleId); var arguments = args.ToList(); + if (publishInitFile != null) + { + arguments.Add($"--pi={publishInitFile}"); + } + if (publishedNodesFilePath != null) { arguments.Add($"--pf={publishedNodesFilePath}"); @@ -394,25 +438,37 @@ static async Task RunAsync(ILogger logger, string deviceId, string moduleId, str /// /// /// + /// /// /// /// /// private static async Task WithServerAsync(string? connectionString, ILoggerFactory loggerFactory, - string deviceId, string moduleId, string[] args, string? publishProfile, uint scaleunits, bool acceptAll, - int? reverseConnectPort, CancellationToken ct) + string deviceId, string moduleId, string[] args, string? publishProfile, string? publishInitProfile, + uint scaleunits, bool acceptAll, int? reverseConnectPort, CancellationToken ct) { try { // Start test server using (var server = new ServerWrapper(scaleunits, loggerFactory, reverseConnectPort)) { + var endpointUrl = $"opc.tcp://localhost:{server.Port}/UA/SampleServer"; + + var publishInitFile = await LoadInitFile(server, publishInitProfile, + endpointUrl, ct).ConfigureAwait(false); + + if (publishInitFile != null && publishProfile == null) + { + publishProfile = "Empty"; + } + var publishedNodesFilePath = await LoadPnJson(server, publishProfile, - $"opc.tcp://localhost:{server.Port}/UA/SampleServer", ct).ConfigureAwait(false); + endpointUrl, ct).ConfigureAwait(false); // Start publisher module await HostAsync(connectionString, loggerFactory, deviceId, moduleId, - args, reverseConnectPort, acceptAll, publishedNodesFilePath, ct).ConfigureAwait(false); + args, reverseConnectPort, acceptAll, publishInitFile, publishedNodesFilePath, + ct).ConfigureAwait(false); } } catch (OperationCanceledException) { } @@ -423,6 +479,7 @@ await HostAsync(connectionString, loggerFactory, deviceId, moduleId, /// /// /// + /// /// /// /// @@ -431,8 +488,8 @@ await HostAsync(connectionString, loggerFactory, deviceId, moduleId, /// /// private static async Task DumpMessagesAsync(string messageMode, string? publishProfile, - ILoggerFactory loggerFactory, TimeSpan duration, uint scaleunits, string? dumpMessagesOutput, - string[] args, CancellationToken ct) + string? publishInitProfile, ILoggerFactory loggerFactory, TimeSpan duration, + uint scaleunits, string? dumpMessagesOutput, string[] args, CancellationToken ct) { try { @@ -463,14 +520,26 @@ private static async Task DumpMessagesAsync(string messageMode, string? publishP continue; } Directory.CreateDirectory(outputFolder); - await DumpPublishingProfiles(outputFolder, messageProfile, publishProfile).ConfigureAwait(false); + await DumpPublishingProfiles(outputFolder, messageProfile, publishProfile, + publishInitProfile).ConfigureAwait(false); } } catch (OperationCanceledException) { } // Dump message profile for all publishing profiles - async Task DumpPublishingProfiles(string rootFolder, MessagingProfile messageProfile, string? profile) + async Task DumpPublishingProfiles(string rootFolder, MessagingProfile messageProfile, + string? profile, string? publishInitProfile) { + if (publishInitProfile != null) + { + var outputFolder = Path.Combine(rootFolder, publishInitProfile); + + await DumpMessagesForDuration(outputFolder, "Empty", messageProfile, + publishInitProfile, args).ConfigureAwait(false); + + return; + } + foreach (var publishProfile in Directory.EnumerateFiles("./Profiles", "*.json")) { var publishProfileName = Path.GetFileNameWithoutExtension(publishProfile); @@ -492,13 +561,13 @@ async Task DumpPublishingProfiles(string rootFolder, MessagingProfile messagePro continue; } Directory.CreateDirectory(outputFolder); - await DumpMessagesForDuration(outputFolder, publishProfile, - messageProfile, args).ConfigureAwait(false); + await DumpMessagesForDuration(outputFolder, publishProfile, messageProfile, + null, args).ConfigureAwait(false); } } async Task DumpMessagesForDuration(string outputFolder, string publishProfile, - MessagingProfile messageProfile, string[] args) + MessagingProfile messageProfile, string? publishInitProfile, string[] args) { using var runtime = new CancellationTokenSource(duration); try @@ -508,26 +577,31 @@ async Task DumpMessagesForDuration(string outputFolder, string publishProfile, var name = Path.GetFileNameWithoutExtension(publishProfile); Console.Title = $"Dumping {messageProfile} for {name}..."; await RunAsync(loggerFactory, publishProfile, messageProfile, - outputFolder, scaleunits, args, linkedToken.Token).ConfigureAwait(false); + outputFolder, scaleunits, args, publishInitProfile, linkedToken.Token).ConfigureAwait(false); } catch (OperationCanceledException) when (runtime.IsCancellationRequested) { } } static async Task RunAsync(ILoggerFactory loggerFactory, string publishProfile, MessagingProfile messageProfile, string outputFolder, uint scaleunits, string[] args, - CancellationToken ct) + string? publishInitProfile, CancellationToken ct) { // Start test server using (var server = new ServerWrapper(scaleunits, loggerFactory, null)) { var name = Path.GetFileNameWithoutExtension(publishProfile); - var publishedNodesFilePath = await LoadPnJson(server, name, - $"opc.tcp://localhost:{server.Port}/UA/SampleServer", ct).ConfigureAwait(false); + var endpointUrl = $"opc.tcp://localhost:{server.Port}/UA/SampleServer"; + + var publishedNodesFilePath = await LoadPnJson(server, name, endpointUrl, + ct).ConfigureAwait(false); if (publishedNodesFilePath == null) { return; } + var publishInitFile = await LoadInitFile(server, name, endpointUrl, + ct).ConfigureAwait(false); + // // Check whether the profile overrides the messaging mode, then set it to the desired // one regardless of whether it will work or not @@ -554,6 +628,10 @@ static async Task RunAsync(ILoggerFactory loggerFactory, string publishProfile, $"-o={outputFolder}", "--aa" }; + if (publishInitFile != null) + { + arguments.Add($"--pi={publishInitFile}"); + } args.ForEach(a => arguments.Add(a)); await Publisher.Module.Program.RunAsync(arguments.ToArray(), ct).ConfigureAwait(false); } @@ -593,6 +671,27 @@ await File.WriteAllTextAsync(publishedNodesFilePath, return null; } + private static async Task LoadInitFile(ServerWrapper server, string? initProfile, + string endpointUrl, CancellationToken ct) + { + const string initFile = "profile.init"; + if (!string.IsNullOrEmpty(initProfile)) + { + var publishedNodesFile = $"./Initfiles/{initProfile}.init"; + if (!File.Exists(publishedNodesFile)) + { + throw new ArgumentException($"Init profile {initProfile} does not exist"); + } + await File.WriteAllTextAsync(initFile, + (await File.ReadAllTextAsync(publishedNodesFile, ct).ConfigureAwait(false)) + .Replace("{{EndpointUrl}}", endpointUrl, + StringComparison.Ordinal), ct).ConfigureAwait(false); + + return initFile; + } + return null; + } + /// /// Add or get module identity /// diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaMonitoredItem.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaMonitoredItem.cs index 348c0399bc..2acad4ba34 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaMonitoredItem.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaMonitoredItem.cs @@ -445,7 +445,7 @@ public void LogRevisedSamplingRateAndQueueSize() } Debug.Assert(Subscription != null); if (SamplingInterval != Status.SamplingInterval && - QueueSize != Status.QueueSize) + QueueSize != Status.QueueSize && Status.QueueSize != 0) { _logger.LogInformation("Server revised SamplingInterval from {SamplingInterval} " + "to {CurrentSamplingInterval} and QueueSize from {QueueSize} " + @@ -460,7 +460,7 @@ public void LogRevisedSamplingRateAndQueueSize() SamplingInterval, Status.SamplingInterval, Subscription.Id, StartNodeId, DisplayName); } - else if (QueueSize != Status.QueueSize) + else if (QueueSize != Status.QueueSize && Status.QueueSize != 0) { _logger.LogInformation("Server revised QueueSize from {QueueSize} " + "to {CurrentQueueSize} for #{SubscriptionId}|{Item}('{Name}').", diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaSubscription.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaSubscription.cs index 2df74e3b40..99a3e44e93 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaSubscription.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaSubscription.cs @@ -345,12 +345,10 @@ public override int GetHashCode() protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (!disposing || _disposed) { return; } - _disposed = true; try { ResetMonitoredItemWatchdogTimer(false); @@ -392,6 +390,7 @@ protected override void Dispose(bool disposing) } finally { + _disposed = true; _keepAliveWatcher.Dispose(); _monitoredItemWatcher.Dispose(); _timer.Dispose(); @@ -1462,53 +1461,10 @@ private async ValueTask SynchronizeMonitoredItemsAsync( var conditionsEnabled = set .Count(r => r is OpcUaMonitoredItem.Condition h && h.TimerEnabled); - if (_badMonitoredItems != badMonitoredItems || - _errorsDuringSync != errorsDuringSync || - _goodMonitoredItems != goodMonitoredItems || - _reportingItems != reportingItems || - _disabledItems != disabledItems || - _samplingItems != samplingItems || - _notAppliedItems != notAppliedItems || - _heartbeatItems != heartbeatItems || - _conditionItems != conditionItems) - { - _logger.LogInformation(@"{Subscription} - Now monitoring {Count} nodes: -# Good/Bad: {Good}/{Bad} -# Errors: {Errors} -# Reporting: {Reporting} -# Sampling: {Sampling} -# Heartbeat/ing: {Heartbeat}/{EnabledHeartbeats} -# Condition/ing: {Conditions}/{EnabledConditions} -# Disabled: {Disabled} -# Not applied: {NotApplied} -# Removed: {Disposed}", - this, set.Count, - goodMonitoredItems, badMonitoredItems, - errorsDuringSync, - reportingItems, - samplingItems, - heartbeatItems, heartbeatsEnabled, - conditionItems, conditionsEnabled, - disabledItems, - notAppliedItems, - dispose.Count); - } - else - { - _logger.LogDebug( - "{Subscription} Applied changes to monitored items, but nothing changed.", - this); - } - - _badMonitoredItems = badMonitoredItems; - _errorsDuringSync = errorsDuringSync; - _goodMonitoredItems = goodMonitoredItems; - _reportingItems = reportingItems; - _disabledItems = disabledItems; - _samplingItems = samplingItems; - _notAppliedItems = notAppliedItems; - _heartbeatItems = heartbeatItems; - _conditionItems = conditionItems; + ReportMonitoredItemChanges(set.Count, goodMonitoredItems, badMonitoredItems, + errorsDuringSync, notAppliedItems, reportingItems, disabledItems, heartbeatItems, + heartbeatsEnabled, conditionItems, conditionsEnabled, samplingItems, + dispose.Count); // Set up subscription management trigger if (badMonitoredItems != 0 || errorsDuringSync != 0) @@ -1705,6 +1661,97 @@ private void LogRevisedValues(bool created) CurrentLifetimeCount, LifetimeCount); } + /// + /// Report monitored item changes + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private void ReportMonitoredItemChanges(int count, + int goodMonitoredItems, int badMonitoredItems, + int errorsDuringSync, int notAppliedItems, + int reportingItems, int disabledItems, + int heartbeatItems, int heartbeatsEnabled, + int conditionItems, int conditionsEnabled, + int samplingItems, int disposed) + { + if (_badMonitoredItems != badMonitoredItems || + _errorsDuringSync != errorsDuringSync || + _goodMonitoredItems != goodMonitoredItems || + _reportingItems != reportingItems || + _disabledItems != disabledItems || + _samplingItems != samplingItems || + _notAppliedItems != notAppliedItems || + _heartbeatItems != heartbeatItems || + _conditionItems != conditionItems) + { + if (samplingItems == 0 && heartbeatItems == 0 && conditionItems == 0 && + notAppliedItems == 0) + { + if (errorsDuringSync == 0 && disabledItems == 0) + { + _logger.LogInformation( +@"{Subscription} - Removed {Removed} - now monitoring {Count} nodes: +# Good/Bad/Reporting: {Good}/{Bad}/{Reporting}", + this, disposed, count, + goodMonitoredItems, badMonitoredItems, reportingItems); + } + else + { + _logger.LogWarning( +@"{Subscription} - Removed {Removed} - now monitoring {Count} nodes: +# Good/Bad/Reporting: {Good}/{Bad}/{Reporting} +# Disabled/Errors: {Disabled}/{Errors}", + this, disposed, count, + goodMonitoredItems, badMonitoredItems, reportingItems, + disabledItems, errorsDuringSync); + } + } + else + { + _logger.LogInformation( +@"{Subscription} - Removed {Removed} - now monitoring {Count} nodes: +# Good/Bad/Reporting: {Good}/{Bad}/{Reporting} +# Disabled/Errors: {Disabled}/{Errors} (Not applied: {NotApplied}) +# Sampling: {Sampling} +# Heartbeat/ing: {Heartbeat}/{EnabledHeartbeats} +# Condition/ing: {Conditions}/{EnabledConditions}", + this, disposed, count, + goodMonitoredItems, badMonitoredItems, reportingItems, + disabledItems, errorsDuringSync, notAppliedItems, + samplingItems, + heartbeatItems, heartbeatsEnabled, + conditionItems, conditionsEnabled); + } + } + else + { + _logger.LogDebug( + "{ Subscription} Applied changes to monitored items, but nothing changed.", + this); + } + + _badMonitoredItems = badMonitoredItems; + _errorsDuringSync = errorsDuringSync; + _goodMonitoredItems = goodMonitoredItems; + _reportingItems = reportingItems; + _disabledItems = disabledItems; + _samplingItems = samplingItems; + _notAppliedItems = notAppliedItems; + _heartbeatItems = heartbeatItems; + _conditionItems = conditionItems; + } + /// /// Calculate delay /// @@ -1834,7 +1881,7 @@ private void OnSubscriptionEventNotificationList(Subscription subscription, EventNotificationList notification, IList? stringTable) { Debug.Assert(ReferenceEquals(subscription, this)); - Debug.Assert(!_disposed); + ObjectDisposedException.ThrowIf(_disposed, this); if (notification?.Events == null) { @@ -1960,7 +2007,7 @@ private void OnSubscriptionKeepAliveNotification(Subscription subscription, NotificationData notification) { Debug.Assert(ReferenceEquals(subscription, this)); - Debug.Assert(!_disposed); + ObjectDisposedException.ThrowIf(_disposed, this); ResetKeepAliveTimer(); @@ -2036,7 +2083,7 @@ public void OnSubscriptionCylicReadNotification(Subscription subscription, List values, uint sequenceNumber, DateTime publishTime) { Debug.Assert(ReferenceEquals(subscription, this)); - Debug.Assert(!_disposed); + ObjectDisposedException.ThrowIf(_disposed, this); var session = Session; if (session is not IOpcUaSession sessionContext) { @@ -2108,7 +2155,7 @@ private void OnSubscriptionDataChangeNotification(Subscription subscription, DataChangeNotification notification, IList? stringTable) { Debug.Assert(ReferenceEquals(subscription, this)); - Debug.Assert(!_disposed); + ObjectDisposedException.ThrowIf(_disposed, this); var firstDataChangeReceived = _firstDataChangeReceived; _firstDataChangeReceived = true; @@ -2485,13 +2532,11 @@ private void OnKeepAliveMissing(object? state) /// private void OnPublishStatusChange(Subscription subscription, PublishStateChangedEventArgs e) { + ObjectDisposedException.ThrowIf(_disposed, this); if (_disposed) { - // Debug.Fail("Should not be called after dispose"); - // This currently happens because the stack caches the callbacks! return; } - if (e.Status.HasFlag(PublishStateChangedMask.Stopped) && !_publishingStopped) { _logger.LogInformation("Subscription {Subscription} STOPPED!", this); @@ -2541,13 +2586,11 @@ private void OnPublishStatusChange(Subscription subscription, PublishStateChange /// private void OnStateChange(Subscription subscription, SubscriptionStateChangedEventArgs e) { + ObjectDisposedException.ThrowIf(_disposed, this); if (_disposed) { - // Debug.Fail("Should not be called after dispose"); - // This currently happens because the stack caches the callbacks! return; } - if (e.Status.HasFlag(SubscriptionChangeMask.Created)) { _logger.LogDebug("Subscription {Subscription} created.", this);