/// <summary> /// Creates the subscriptions on the server. /// </summary> /// <param name="token">A cancellation token. </param> /// <returns>A task.</returns> private async Task AutoCreateSubscriptionsAsync(CancellationToken token = default(CancellationToken)) { var tcs = new TaskCompletionSource <bool>(); NotifyCollectionChangedEventHandler handler = async(o, e) => { if (e.Action == NotifyCollectionChangedAction.Add) { foreach (var subscription in e.NewItems.OfType <Subscription>()) { var target = subscription.Target; if (target == null) { continue; } try { // create the subscription. var subscriptionRequest = new CreateSubscriptionRequest { RequestedPublishingInterval = subscription.PublishingInterval, RequestedMaxKeepAliveCount = subscription.KeepAliveCount, RequestedLifetimeCount = Math.Max(subscription.LifetimeCount, 3 * subscription.KeepAliveCount), PublishingEnabled = subscription.PublishingEnabled }; var subscriptionResponse = await this.CreateSubscriptionAsync(subscriptionRequest).ConfigureAwait(false); var id = subscription.SubscriptionId = subscriptionResponse.SubscriptionId; // add the items. if (subscription.MonitoredItems.Count > 0) { var items = subscription.MonitoredItems.ToList(); var requests = items.Select(m => new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { NodeId = m.NodeId, AttributeId = m.AttributeId, IndexRange = m.IndexRange }, MonitoringMode = m.MonitoringMode, RequestedParameters = new MonitoringParameters { ClientHandle = m.ClientId, DiscardOldest = m.DiscardOldest, QueueSize = m.QueueSize, SamplingInterval = m.SamplingInterval, Filter = m.Filter } }).ToArray(); var itemsRequest = new CreateMonitoredItemsRequest { SubscriptionId = id, ItemsToCreate = requests, }; var itemsResponse = await this.CreateMonitoredItemsAsync(itemsRequest).ConfigureAwait(false); for (int i = 0; i < itemsResponse.Results.Length; i++) { var item = items[i]; var result = itemsResponse.Results[i]; item.OnCreateResult(target, result); if (StatusCode.IsBad(result.StatusCode)) { this.Logger?.LogError($"Error creating MonitoredItem for {item.NodeId}. {StatusCodes.GetDefaultMessage(result.StatusCode)}"); } } } } catch (ServiceResultException ex) { this.Logger?.LogError($"Error creating subscription. {ex.Message}"); this.innerChannel.Fault(ex); } } } else if (e.Action == NotifyCollectionChangedAction.Remove) { try { // delete the subscriptions. var request = new DeleteSubscriptionsRequest { SubscriptionIds = e.OldItems.OfType <Subscription>().Select(s => s.SubscriptionId).ToArray() }; await this.DeleteSubscriptionsAsync(request).ConfigureAwait(false); } catch (ServiceResultException ex) { this.Logger?.LogError($"Error deleting subscriptions. {ex.Message}"); } } }; using (token.Register(state => ((TaskCompletionSource <bool>)state).TrySetResult(true), tcs, false)) { try { handler.Invoke(this.subscriptions, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, this.subscriptions.ToList())); this.subscriptions.CollectionChanged += handler; await tcs.Task; } finally { this.subscriptions.CollectionChanged -= handler; foreach (var subscription in this.subscriptions) { subscription.SubscriptionId = 0; } } } }
/// <summary> /// The state machine manages the state of the subscription. /// </summary> /// <param name="token">A cancellation token.</param> /// <returns>A task.</returns> private async Task StateMachineAsync(CancellationToken token = default(CancellationToken)) { while (!token.IsCancellationRequested) { await this.whenSubscribed.Task; this.progress.Report(CommunicationState.Opening); try { // get a channel. this.innerChannel = await this.application.GetChannelAsync(this.endpointUrl, token); try { // create the subscription. var subscriptionRequest = new CreateSubscriptionRequest { RequestedPublishingInterval = this.publishingInterval, RequestedMaxKeepAliveCount = this.keepAliveCount, RequestedLifetimeCount = Math.Max(this.lifetimeCount, 3 * this.keepAliveCount), PublishingEnabled = true }; var subscriptionResponse = await this.innerChannel.CreateSubscriptionAsync(subscriptionRequest).ConfigureAwait(true); // link up the dataflow blocks var id = this.subscriptionId = subscriptionResponse.SubscriptionId; var linkToken = this.innerChannel.LinkTo(this.actionBlock, pr => pr.SubscriptionId == id); try { // create the monitored items. var items = this.monitoredItems.ToList(); if (items.Count > 0) { var requests = items.Select(m => new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { NodeId = m.NodeId, AttributeId = m.AttributeId, IndexRange = m.IndexRange }, MonitoringMode = m.MonitoringMode, RequestedParameters = new MonitoringParameters { ClientHandle = m.ClientId, DiscardOldest = m.DiscardOldest, QueueSize = m.QueueSize, SamplingInterval = m.SamplingInterval, Filter = m.Filter } }).ToArray(); var itemsRequest = new CreateMonitoredItemsRequest { SubscriptionId = id, ItemsToCreate = requests, }; var itemsResponse = await this.innerChannel.CreateMonitoredItemsAsync(itemsRequest); for (int i = 0; i < itemsResponse.Results.Length; i++) { var item = items[i]; var result = itemsResponse.Results[i]; item.OnCreateResult(result); if (StatusCode.IsBad(result.StatusCode)) { this.logger?.LogError($"Error creating MonitoredItem for {item.NodeId}. {StatusCodes.GetDefaultMessage(result.StatusCode)}"); } } } this.progress.Report(CommunicationState.Opened); // wait here until channel is closing, unsubscribed or token cancelled. try { await Task.WhenAny( this.WhenChannelClosingAsync(this.innerChannel, token), this.whenUnsubscribed.Task); } catch { } finally { this.progress.Report(CommunicationState.Closing); } } catch (Exception ex) { this.logger?.LogError($"Error creating MonitoredItems. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); } finally { linkToken.Dispose(); } if (this.innerChannel.State == CommunicationState.Opened) { try { // delete the subscription. var deleteRequest = new DeleteSubscriptionsRequest { SubscriptionIds = new uint[] { id } }; await this.innerChannel.DeleteSubscriptionsAsync(deleteRequest); } catch (Exception ex) { this.logger?.LogError($"Error deleting subscription. {ex.Message}"); await Task.Delay(2000); } } this.progress.Report(CommunicationState.Closed); } catch (Exception ex) { this.logger?.LogError($"Error creating subscription. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); await Task.Delay(2000); } } catch (Exception ex) { this.logger?.LogTrace($"Error getting channel. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); await Task.Delay(2000); } } }
/// <summary> /// The state machine manages the state of the subscription. /// </summary> /// <param name="token">A cancellation token.</param> /// <returns>A task.</returns> private async Task StateMachineAsync(CancellationToken token = default) { while (!token.IsCancellationRequested) { await this.whenSubscribed.Task; this.progress.Report(CommunicationState.Opening); try { if (this.endpointUrl is null) { throw new InvalidOperationException("The endpointUrl field must not be null. Please, use the Subscription attribute properly."); } // get a channel. this.innerChannel = await this.application.GetChannelAsync(this.endpointUrl, token); try { // create the subscription. var subscriptionRequest = new CreateSubscriptionRequest { RequestedPublishingInterval = this.publishingInterval, RequestedMaxKeepAliveCount = this.keepAliveCount, RequestedLifetimeCount = Math.Max(this.lifetimeCount, 3 * this.keepAliveCount), PublishingEnabled = true }; var subscriptionResponse = await this.innerChannel.CreateSubscriptionAsync(subscriptionRequest).ConfigureAwait(false); // link up the dataflow blocks var id = this.subscriptionId = subscriptionResponse.SubscriptionId; var linkToken = this.innerChannel.LinkTo(this.actionBlock, pr => pr.SubscriptionId == id); try { // create the monitored items. var items = this.monitoredItems.ToList(); if (items.Count > 0) { var requests = items.Select(m => new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { NodeId = ExpandedNodeId.ToNodeId(m.NodeId, this.InnerChannel.NamespaceUris), AttributeId = m.AttributeId, IndexRange = m.IndexRange }, MonitoringMode = m.MonitoringMode, RequestedParameters = new MonitoringParameters { ClientHandle = m.ClientId, DiscardOldest = m.DiscardOldest, QueueSize = m.QueueSize, SamplingInterval = m.SamplingInterval, Filter = m.Filter } }).ToArray(); //split requests array to MaxMonitoredItemsPerCall chunks int maxmonitoreditemspercall = 100; MonitoredItemCreateRequest[] requests_chunk; int chunk_size; for (int i_chunk = 0; i_chunk < requests.Length; i_chunk += maxmonitoreditemspercall) { chunk_size = Math.Min(maxmonitoreditemspercall, requests.Length - i_chunk); requests_chunk = new MonitoredItemCreateRequest[chunk_size]; Array.Copy(requests, i_chunk, requests_chunk, 0, chunk_size); var itemsRequest = new CreateMonitoredItemsRequest { SubscriptionId = id, ItemsToCreate = requests_chunk, }; var itemsResponse = await this.innerChannel.CreateMonitoredItemsAsync(itemsRequest); if (itemsResponse.Results is { } results) { for (int i = 0; i < results.Length; i++) { var item = items[i]; var result = results[i]; if (result is null) { this.logger?.LogError($"Error creating MonitoredItem for {item.NodeId}. The result is null."); continue; } item.OnCreateResult(result); if (StatusCode.IsBad(result.StatusCode)) { this.logger?.LogError($"Error creating MonitoredItem for {item.NodeId}. {StatusCodes.GetDefaultMessage(result.StatusCode)}"); } } } } } this.progress.Report(CommunicationState.Opened); // wait here until channel is closing, unsubscribed or token cancelled. try { await Task.WhenAny( this.WhenChannelClosingAsync(this.innerChannel, token), this.whenUnsubscribed.Task); } catch { } finally { this.progress.Report(CommunicationState.Closing); } } catch (Exception ex) { this.logger?.LogError($"Error creating MonitoredItems. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); } finally { linkToken.Dispose(); } if (this.innerChannel.State == CommunicationState.Opened) { try { // delete the subscription. var deleteRequest = new DeleteSubscriptionsRequest { SubscriptionIds = new uint[] { id } }; await this.innerChannel.DeleteSubscriptionsAsync(deleteRequest); } catch (Exception ex) { this.logger?.LogError($"Error deleting subscription. {ex.Message}"); await Task.Delay(2000); } } this.progress.Report(CommunicationState.Closed); } catch (Exception ex) { this.logger?.LogError($"Error creating subscription. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); await Task.Delay(2000); } } catch (Exception ex) { this.logger?.LogTrace($"Error getting channel. {ex.Message}"); this.progress.Report(CommunicationState.Faulted); await Task.Delay(2000); } } }