/// <summary> /// Initializes a new instance of the <see cref="UaApplication"/> class. /// </summary> /// <param name="localDescription">The <see cref="ApplicationDescription"/> of the local application.</param> /// <param name="certificateStore">The local certificate store.</param> /// <param name="identityProvider">An asynchronous function that provides the user identity. Provide an <see cref="AnonymousIdentity"/>, <see cref="UserNameIdentity"/>, <see cref="IssuedIdentity"/> or <see cref="X509Identity"/>.</param> /// <param name="mappedEndpoints">The mapped endpoints.</param> /// <param name="loggerFactory">The logger factory.</param> /// <param name="options">The application options.</param> /// <param name="additionalTypes">Any additional types to be registered with encoder.</param> public UaApplication( ApplicationDescription localDescription, ICertificateStore certificateStore, Func <EndpointDescription, Task <IUserIdentity> > identityProvider, IEnumerable <MappedEndpoint> mappedEndpoints, ILoggerFactory loggerFactory = null, UaApplicationOptions options = null, IEnumerable <Type> additionalTypes = null) { if (localDescription == null) { throw new ArgumentNullException(nameof(localDescription)); } this.LocalDescription = localDescription; this.CertificateStore = certificateStore; this.UserIdentityProvider = identityProvider; this.MappedEndpoints = mappedEndpoints; this.LoggerFactory = loggerFactory; this.Options = options ?? new UaApplicationOptions(); this.AdditionalTypes = additionalTypes; this.logger = loggerFactory?.CreateLogger <UaApplication>(); this.channelMap = new ConcurrentDictionary <string, Lazy <Task <UaTcpSessionChannel> > >(); lock (globalLock) { if (appInstance != null) { throw new InvalidOperationException("You can only create a single instance of this type."); } appInstance = this; } }
/// <summary> /// Closes the communication channels to the remote endpoint. /// </summary> /// <param name="disposing">If true, then dispose managed resources.</param> protected virtual void Dispose(bool disposing) { if (disposing & !this.disposed) { this.disposed = true; this.completionTask.TrySetResult(true); lock (globalLock) { appInstance = null; } foreach (var value in this.channelMap.Values) { var task = value.Value; if (task.Status == TaskStatus.RanToCompletion) { try { task.Result.CloseAsync().Wait(2000); } catch { } } } } }
/// <summary> /// Initializes a new instance of the <see cref="SubscriptionBase"/> class. /// </summary> public SubscriptionBase() { this.application = UaApplication.Current; 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: NodeId.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: NodeId.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: NodeId.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, NodeId.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: NodeId.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)); }