/// <summary> /// Initializes a new instance of the <see cref="SubscriptionBase"/> class. /// </summary> /// <param name="application">The UaApplication.</param> public SubscriptionBase(UaApplication application) { this.application = application ?? throw new ArgumentNullException(nameof(application)); this.application.Completion.ContinueWith(t => this.stateMachineCts?.Cancel()); this.logger = this.application.LoggerFactory?.CreateLogger(this.GetType()); this.errors = new ErrorsContainer <string>(p => this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(p))); this.progress = new Progress <CommunicationState>(s => this.State = s); this.propertyChanged += this.OnPropertyChanged; this.whenSubscribed = new TaskCompletionSource <bool>(); this.whenUnsubscribed = new TaskCompletionSource <bool>(); this.whenUnsubscribed.TrySetResult(true); // register the action to be run on the ui thread, if there is one. if (SynchronizationContext.Current != null) { this.actionBlock = new ActionBlock <PublishResponse>(pr => this.OnPublishResponse(pr), new ExecutionDataflowBlockOptions { SingleProducerConstrained = true, TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() }); } else { this.actionBlock = new ActionBlock <PublishResponse>(pr => this.OnPublishResponse(pr), new ExecutionDataflowBlockOptions { SingleProducerConstrained = true }); } // read [Subscription] attribute. var typeInfo = this.GetType().GetTypeInfo(); var sa = typeInfo.GetCustomAttribute <SubscriptionAttribute>(); if (sa != null) { this.endpointUrl = sa.EndpointUrl; this.publishingInterval = sa.PublishingInterval; this.keepAliveCount = sa.KeepAliveCount; this.lifetimeCount = sa.LifetimeCount; } // read [MonitoredItem] attributes. foreach (var propertyInfo in typeInfo.DeclaredProperties) { var mia = propertyInfo.GetCustomAttribute <MonitoredItemAttribute>(); if (mia == null || string.IsNullOrEmpty(mia.NodeId)) { continue; } MonitoringFilter filter = null; if (mia.AttributeId == AttributeIds.Value && (mia.DataChangeTrigger != DataChangeTrigger.StatusValue || mia.DeadbandType != DeadbandType.None)) { filter = new DataChangeFilter() { Trigger = mia.DataChangeTrigger, DeadbandType = (uint)mia.DeadbandType, DeadbandValue = mia.DeadbandValue }; } var propType = propertyInfo.PropertyType; if (propType == typeof(DataValue)) { this.monitoredItems.Add(new DataValueMonitoredItem( target: this, property: propertyInfo, nodeId: ExpandedNodeId.Parse(mia.NodeId), indexRange: mia.IndexRange, attributeId: mia.AttributeId, samplingInterval: mia.SamplingInterval, filter: filter, queueSize: mia.QueueSize, discardOldest: mia.DiscardOldest)); continue; } if (propType == typeof(BaseEvent) || propType.GetTypeInfo().IsSubclassOf(typeof(BaseEvent))) { this.monitoredItems.Add(new EventMonitoredItem( target: this, property: propertyInfo, nodeId: ExpandedNodeId.Parse(mia.NodeId), indexRange: mia.IndexRange, attributeId: mia.AttributeId, samplingInterval: mia.SamplingInterval, filter: new EventFilter() { SelectClauses = EventHelper.GetSelectClauses(propType) }, queueSize: mia.QueueSize, discardOldest: mia.DiscardOldest)); continue; } if (propType == typeof(ObservableQueue <DataValue>)) { this.monitoredItems.Add(new DataValueQueueMonitoredItem( target: this, property: propertyInfo, nodeId: ExpandedNodeId.Parse(mia.NodeId), indexRange: mia.IndexRange, attributeId: mia.AttributeId, samplingInterval: mia.SamplingInterval, filter: filter, queueSize: mia.QueueSize, discardOldest: mia.DiscardOldest)); continue; } if (propType.IsConstructedGenericType && propType.GetGenericTypeDefinition() == typeof(ObservableQueue <>)) { var elemType = propType.GenericTypeArguments[0]; if (elemType == typeof(BaseEvent) || elemType.GetTypeInfo().IsSubclassOf(typeof(BaseEvent))) { this.monitoredItems.Add((MonitoredItemBase)Activator.CreateInstance( typeof(EventQueueMonitoredItem <>).MakeGenericType(elemType), this, propertyInfo, ExpandedNodeId.Parse(mia.NodeId), mia.AttributeId, mia.IndexRange, MonitoringMode.Reporting, mia.SamplingInterval, new EventFilter() { SelectClauses = EventHelper.GetSelectClauses(elemType) }, mia.QueueSize, mia.DiscardOldest)); continue; } } this.monitoredItems.Add(new ValueMonitoredItem( target: this, property: propertyInfo, nodeId: ExpandedNodeId.Parse(mia.NodeId), indexRange: mia.IndexRange, attributeId: mia.AttributeId, samplingInterval: mia.SamplingInterval, filter: filter, queueSize: mia.QueueSize, discardOldest: mia.DiscardOldest)); } this.stateMachineCts = new CancellationTokenSource(); this.stateMachineTask = Task.Run(() => this.StateMachineAsync(this.stateMachineCts.Token)); }
/// <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 { // 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(); 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); } } }
public Variant(ExpandedNodeId value) { this.Value = value; this.Type = VariantType.ExpandedNodeId; this.ArrayDimensions = null; }
public XmlEncodingIdAttribute(string s) { this.NodeId = ExpandedNodeId.Parse(s); }
public DataTypeIdAttribute(string s) { this.NodeId = ExpandedNodeId.Parse(s); }