You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I refactored my lib for the opc client but with the update I'm not receving the notifications from the monitored items.
I currently use asp.net core 8 and the service (OpcUaClient2) is intantiated as a singleton.
The currious thing is that while I'm steping through the subscribing code, the breakpoint on the notifications method is hit, but after that nothing.
If I debug without stepping through, the breakpoint is never hit. Notification on another thread?
The class for the UaClient is from UA-.NETStandard/Applications/ConsoleReferenceClient/UAClient.cs and to intantiate it a variation of Program.cs from the same route. Tested with both configuration from code and xml file and againt Kepware server. Value of the item is changing, verified with UaExpert and with the old version my lib.
Surrely I'm missing something and I dont know what it is.
publicclassUAClient:IUAClient,IDisposable{privatereadonlyobject_lock=new();privatereadonlyILogger_logger;privatereadonlyAction<IList,IList>_validateResponse;privatereadonlyApplicationConfiguration_configuration;privatebool_disposed=false;privateSessionReconnectHandler_reconnectHandler;privatereadonlyReverseConnectManager?_reverseConnectManager;privateISession_session;/// <summary>/// Initializes a new instance of the UAClient class./// </summary>publicUAClient(ApplicationConfigurationconfiguration,ILoggerlogger,Action<IList,IList>validateResponse){_validateResponse=validateResponse;_logger=logger;_configuration=configuration;
_configuration.CertificateValidator.CertificateValidation +=CertificateValidation;_reverseConnectManager=null;}/// <summary>/// Initializes a new instance of the UAClient class for reverse connections./// </summary>publicUAClient(ApplicationConfigurationconfiguration,ReverseConnectManagerreverseConnectManager,ILoggerlogger,Action<IList,IList>validateResponse){_validateResponse=validateResponse;_logger=logger;_configuration=configuration;
_configuration.CertificateValidator.CertificateValidation +=CertificateValidation;_reverseConnectManager=reverseConnectManager;}/// <summary>/// Auto accept untrusted certificates./// </summary>publicboolAutoAccept{get;set;}=false;/// <summary>/// The session keepalive interval to be used in ms./// </summary>publicintKeepAliveInterval{get;set;}=5000;/// <summary>/// The file to use for log output./// </summary>publicstringLogFile{get;set;}/// <summary>/// The reconnect period to be used in ms./// </summary>publicintReconnectPeriod{get;set;}=1000;/// <summary>/// The reconnect period exponential backoff to be used in ms./// </summary>publicintReconnectPeriodExponentialBackoff{get;set;}=15000;/// <summary>/// Gets the client session./// </summary>publicISessionSession=> _session;/// <summary>/// The session lifetime./// </summary>publicuintSessionLifeTime{get;set;}=60*1000;/// <summary>/// The user identity to use to connect to the server./// </summary>publicIUserIdentityUserIdentity{get;set;}=new UserIdentity();/// <summary>/// Action used/// </summary>Action<IList,IList> ValidateResponse => _validateResponse;/// <summary>/// Creates a session with the UA server/// </summary>publicasyncTask<bool>ConnectAsync(stringserverUrl,booluseSecurity=true,CancellationTokenct=default){if(_disposed)thrownew ObjectDisposedException(nameof(UAClient));if(serverUrl==null)thrownew ArgumentNullException(nameof(serverUrl));try{if(_session!=null&& _session.Connected ==true){
_logger.LogInformation("Session already connected!");}else{ITransportWaitingConnectionconnection=null;EndpointDescriptionendpointDescription=null;if(_reverseConnectManager!=null){
_logger.LogInformation("Waiting for reverse connection to.... {0}", serverUrl);do{usingvarcts=new CancellationTokenSource(30_000);usingvarlinkedCTS= CancellationTokenSource.CreateLinkedTokenSource(ct, cts.Token);connection=await _reverseConnectManager.WaitForConnection(new Uri(serverUrl),null, linkedCTS.Token).ConfigureAwait(false);if(connection==null)thrownew ServiceResultException(StatusCodes.BadTimeout,"Waiting for a reverse connection timed out.");if(endpointDescription==null){
_logger.LogInformation("Discover reverse connection endpoints....");endpointDescription= CoreClientUtils.SelectEndpoint(_configuration, connection, useSecurity);connection=null;}}while(connection==null);}else{
_logger.LogInformation("Connecting to... {0}", serverUrl);endpointDescription= CoreClientUtils.SelectEndpoint(_configuration, serverUrl, useSecurity);}// Get the endpoint by connecting to server's discovery endpoint.// Try to find the first endopint with security.EndpointConfigurationendpointConfiguration= EndpointConfiguration.Create(_configuration);ConfiguredEndpointendpoint=new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);varsessionFactory= TraceableSessionFactory.Instance;// Create the sessionvarsession=await sessionFactory.CreateAsync(
_configuration,
connection,
endpoint,connection==null,false,
_configuration.ApplicationName,
SessionLifeTime,
UserIdentity,null,
ct
).ConfigureAwait(false);// Assign the created sessionif(session!=null&& session.Connected){_session=session;// override keep alive interval
_session.KeepAliveInterval =KeepAliveInterval;// support transfer
_session.DeleteSubscriptionsOnClose =false;
_session.TransferSubscriptionsOnReconnect =true;// set up keep alive callback.
_session.KeepAlive +=Session_KeepAlive;// prepare a reconnect handler_reconnectHandler=new SessionReconnectHandler(true, ReconnectPeriodExponentialBackoff);// Session created successfully.
_logger.LogInformation("New Session Created with SessionName = {0}", _session.SessionName);returntrue;}}returntrue;}catch(Exceptionex){// Log Error
_logger.LogError("Create Session Error : {0}", ex.Message);returnfalse;}}/// <summary>/// Disconnects the session./// </summary>/// <param name="leaveChannelOpen">Leaves the channel open.</param>publicvoidDisconnect(boolleaveChannelOpen=false){try{if(_session!=null){
_logger.LogInformation("Disconnecting...");lock(_lock){
_session.KeepAlive -=Session_KeepAlive;
_reconnectHandler?.Dispose();_reconnectHandler=null;}
_session.Close(!leaveChannelOpen);if(leaveChannelOpen){// detach the channel, so it doesn't get closed when the session is disposed.
_session.DetachChannel();}
_session.Dispose();_session=null;// Log Session Disconnected event
_logger.LogInformation("Session Disconnected.");}else{
_logger.LogInformation("Session not created!");}}catch(Exceptionex){// Log Error
_logger.LogError($"Disconnect Error : {ex.Message}");}}/// <summary>/// Dispose objects./// </summary>publicvoidDispose(){_disposed=true;
Utils.SilentDispose(_session);
_configuration.CertificateValidator.CertificateValidation -=CertificateValidation;
GC.SuppressFinalize(this);}/// <summary>/// Handles the certificate validation event./// This event is triggered every time an untrusted certificate is received from the server./// </summary>protectedvirtualvoidCertificateValidation(CertificateValidatorsender,CertificateValidationEventArgse){boolcertificateAccepted=false;// ****// Implement a custom logic to decide if the certificate should be// accepted or not and set certificateAccepted flag accordingly.// The certificate can be retrieved from the e.Certificate field// ***ServiceResulterror= e.Error;
_logger.LogWarning(error.ToString(), error);if(error.StatusCode == StatusCodes.BadCertificateUntrusted &&AutoAccept){certificateAccepted=true;}if(certificateAccepted){
_logger.LogWarning("Untrusted Certificate accepted. Subject = {0}", e.Certificate.Subject);
e.Accept =true;}else{
_logger.LogError("Untrusted Certificate rejected. Subject = {0}", e.Certificate.Subject);}}/// <summary>/// Called when the reconnect attempt was successful./// </summary>privatevoidClient_ReconnectComplete(objectsender,EventArgse){// ignore callbacks from discarded objects.if(!ReferenceEquals(sender, _reconnectHandler)){return;}lock(_lock){// if session recovered, Session property is nullif(_reconnectHandler.Session !=null){// ensure only a new instance is disposed// after reactivate, the same session instance may be returnedif(!ReferenceEquals(_session, _reconnectHandler.Session)){
_logger.LogInformation("--- RECONNECTED TO NEW SESSION --- {0}", _reconnectHandler.Session.SessionId);varsession= _session;_session= _reconnectHandler.Session;
Utils.SilentDispose(session);}else{
_logger.LogInformation("--- REACTIVATED SESSION --- {0}", _reconnectHandler.Session.SessionId);}}else{
_logger.LogInformation("--- RECONNECT KeepAlive recovered ---");}}}/// <summary>/// Handles a keep alive event from a session and triggers a reconnect if necessary./// </summary>privatevoidSession_KeepAlive(ISessionsession,KeepAliveEventArgse){try{// check for events from discarded sessions.if(!_session.Equals(session)){return;}// start reconnect sequence on communication error.if(ServiceResult.IsBad(e.Status)){if(ReconnectPeriod <= 0){
Utils.LogWarning("KeepAlive status {0}, but reconnect is disabled.", e.Status);return;}varstate= _reconnectHandler.BeginReconnect(_session, _reverseConnectManager, ReconnectPeriod, Client_ReconnectComplete);if(state== SessionReconnectHandler.ReconnectState.Triggered){
Utils.LogInfo("KeepAlive status {0}, reconnect status {1}, reconnect period {2}ms.", e.Status, state, ReconnectPeriod);}else{
Utils.LogInfo("KeepAlive status {0}, reconnect status {1}.", e.Status, state);}// cancel sending a new keep alive request, because reconnect is triggered.
e.CancelKeepAlive =true;return;}}catch(Exceptionexception){
Utils.LogError(exception,"Error in OnKeepAlive.");}}}
internalclassOpcUaClient2:IOpcUaClient2{privatereadonlyOpcUaClientOptionsoptions;privatereadonlyILoggerlogger;privateUAClientuaClient;privateboolconnected;publicOpcUaClient2(OpcUaClientOptionsoptions,ILoggerlogger){this.options =options;this.logger =logger;try{
Initialize().Wait();}catch(Exceptionex){throw;}}publicDataChangedDelegateDataChangedHandler{get;set;}privateasync Task Initialize(){// Createa instanceApplicationInstanceapplication=await CreateApplicationInstance().ConfigureAwait(false);// Check the application certificate.try{boolhaveAppCertificate=await application.CheckApplicationInstanceCertificate(false, minimumKeySize:0, lifeTimeInMonths:120).ConfigureAwait(false);}catch(Exceptionex){await application.DeleteApplicationInstanceCertificate().ConfigureAwait(false);thrownew Exception("Application instance certificate invalid!. Restart the application!", ex);}usingvaruaClient=new UAClient(application.ApplicationConfiguration, logger, ClientBase.ValidateResponse){AutoAccept= options.AutoAcceptServerCertificate,UserIdentity=new UserIdentity(options.Username, options.Password),SessionLifeTime=60_000};// Connect to serverconnected=false;while(connected==false){connected=await uaClient.ConnectAsync(options.ServerUrl, options.UseSecurity).ConfigureAwait(false);if(connected==true)break;else{
logger.LogError("Application not connected! Retrying connection in 5s...");await Task.Delay(5000);}}// Enable subscription transfer
uaClient.ReconnectPeriod =1000;
uaClient.ReconnectPeriodExponentialBackoff =10000;//uaClient.Session.MinPublishRequestCount = 3;
uaClient.Session.TransferSubscriptionsOnReconnect =true;// create avaible subscriptionsforeach(var subs in options.Subscriptions){Subscriptionsubscription=new Subscription(uaClient.Session.DefaultSubscription){DisplayName= options.ApplicationName +" - "+ subs.DisplayName,PublishingEnabled=true,PublishingInterval= subs.PublishingInterval,//LifetimeCount = 10,SequentialPublishing=true,RepublishAfterTransfer=true,//DisableMonitoredItemCache = true,MaxNotificationsPerPublish=1000,MinLifetimeInterval=(uint)uaClient.Session.SessionTimeout *3,};
uaClient.Session.AddSubscription(subscription);// Create the subscription on Server sideawait subscription.CreateAsync().ConfigureAwait(false);foreach(var nId in subs.NodeIds){varitem=new MonitoredItem(subscription.DefaultItem);
item.StartNodeId =new NodeId(nId.NodeId);
item.AttributeId = Attributes.Value;
item.DisplayName = nId.DisplayName;
item.SamplingInterval = nId.SamplingInterval;
item.QueueSize =1;
item.DiscardOldest =true;
item.Notification +=Item_Notification;
subscription.AddItem(item);}await subscription.ApplyChangesAsync().ConfigureAwait(false);
logger.LogInformation($"New Subscription '{subscription.DisplayName}' created with SubscriptionId = {subscription.Id}.");}}privatevoidItem_Notification(MonitoredItemmonitoredItem,MonitoredItemNotificationEventArgse){// breakpoint hereif(e.NotificationValue is MonitoredItemNotification notification){
DataChangedHandler?.Invoke(new OpcUaClientValue
{MonitoredItem=monitoredItem,Notification=notification});}}privateasyncTask<ApplicationInstance>CreateApplicationInstance(){CertificatePasswordProviderPasswordProvider=new(options.CertificatePassword);ApplicationInstanceapplication=new(){ApplicationName= options.ApplicationName,ApplicationType= ApplicationType.Client,CertificatePasswordProvider=PasswordProvider};
application.ApplicationConfiguration =await CreateConfiguration().ConfigureAwait(false);returnapplication;}privateasyncTask<ApplicationConfiguration>CreateConfiguration(){ApplicationConfigurationconfiguration=new();
configuration.ApplicationName = options.ApplicationName;
configuration.ApplicationUri =$"urn:{Utils.GetHostName()}:{options.ApplicationName}";
configuration.ProductUri =$"uri:opcfoundation.org:{options.ApplicationName}";
configuration.ApplicationType = ApplicationType.Client;
configuration.SecurityConfiguration =new SecurityConfiguration
{ApplicationCertificate=new CertificateIdentifier
{// https://stackoverflow.com/a/75947695StoreType= CertificateStoreType.Directory,StorePath=@"%LocalApplicationData%/OPC Foundation/pki/own",SubjectName=$"CN={options.ApplicationName}, C=ES, S=Almeria, O=Cosentino, DC=localhost"},TrustedIssuerCertificates=new CertificateTrustList
{StoreType= CertificateStoreType.Directory,StorePath=@"%LocalApplicationData%/OPC Foundation/pki/issuer",},TrustedPeerCertificates=new CertificateTrustList
{StoreType= CertificateStoreType.Directory,StorePath=@"%LocalApplicationData%/OPC Foundation/pki/trusted",},RejectedCertificateStore=new CertificateTrustList
{StoreType= CertificateStoreType.Directory,StorePath=@"%LocalApplicationData%/OPC Foundation/pki/rejected",},AutoAcceptUntrustedCertificates=false,RejectSHA1SignedCertificates=true,RejectUnknownRevocationStatus=true,MinimumCertificateKeySize=2048,AddAppCertToTrustedStore=false,SendCertificateChain=true,TrustedUserCertificates=new CertificateTrustList
{StoreType= CertificateStoreType.Directory,StorePath=@"%LocalApplicationData%/OPC Foundation/pki/trustedUser"}};
configuration.TransportConfigurations =[];
configuration.TransportQuotas =new TransportQuotas
{OperationTimeout=120000,MaxStringLength=4194304,MaxByteStringLength=4194304,MaxArrayLength=65535,MaxMessageSize=4194304,MaxBufferSize=65535,ChannelLifetime=300000,SecurityTokenLifetime=3600000};
configuration.ClientConfiguration =new ClientConfiguration
{DefaultSessionTimeout=60_000,WellKnownDiscoveryUrls=["opc.tcp://{0}:4840","http://{0}:52601/UADiscovery","http://{0}/UADiscovery/Default.svc"],MinSubscriptionLifetime=1000,OperationLimits=new OperationLimits
{MaxNodesPerRead=2500,MaxNodesPerHistoryReadData=1000,MaxNodesPerHistoryReadEvents=1000,MaxNodesPerWrite=2500,MaxNodesPerHistoryUpdateData=1000,MaxNodesPerHistoryUpdateEvents=1000,MaxNodesPerMethodCall=2500,MaxNodesPerBrowse=2500,MaxNodesPerRegisterNodes=2500,MaxNodesPerTranslateBrowsePathsToNodeIds=2500,MaxNodesPerNodeManagement=2500,MaxMonitoredItemsPerCall=2500,}};
configuration.TraceConfiguration =new TraceConfiguration(){OutputFilePath=$@"%LocalApplicationData%/OPC Foundation/Logs/{options.ApplicationName}.log.txt",DeleteOnLoad=true,// <!-- Show Only Errors -->// <!-- <TraceMasks>1</TraceMasks> -->// <!-- Show Only Security and Errors -->// <!-- <TraceMasks>513</TraceMasks> -->// <!-- Show Only Security, Errors and Trace -->// <!-- <TraceMasks>515</TraceMasks> -->// <!-- Show Only Security, COM Calls, Errors and Trace -->// <!-- <TraceMasks>771</TraceMasks> -->// <!-- Show Only Security, Service Calls, Errors and Trace -->// <!-- <TraceMasks>523</TraceMasks> -->// <!-- Show Only Security, ServiceResultExceptions, Errors and Trace -->// <!-- <TraceMasks>519</TraceMasks> -->};await configuration.Validate(ApplicationType.Client).ConfigureAwait(false);returnconfiguration;}publicasyncTask<ReadResponse?>ReadAsync(ReadValueIdCollectionnodesToRead,CancellationTokencancellationToken=default){if(!connected)returnnull;ReadResponseresult=await uaClient.Session.ReadAsync(null,0, TimestampsToReturn.Both, nodesToRead, cancellationToken).ConfigureAwait(false);returnresult;}publicasyncTask<WriteResponse?>WriteAsync(WriteValueCollectionnodesToWrite,CancellationTokencancellationToken=default){if(!connected)returnnull;WriteResponseresult=await uaClient.Session.WriteAsync(null, nodesToWrite, cancellationToken).ConfigureAwait(false);returnresult;}}
Type of issue
Current Behavior
I refactored my lib for the opc client but with the update I'm not receving the notifications from the monitored items.
I currently use asp.net core 8 and the service (OpcUaClient2) is intantiated as a singleton.
The currious thing is that while I'm steping through the subscribing code, the breakpoint on the notifications method is hit, but after that nothing.
If I debug without stepping through, the breakpoint is never hit. Notification on another thread?
The class for the UaClient is from UA-.NETStandard/Applications/ConsoleReferenceClient/UAClient.cs and to intantiate it a variation of Program.cs from the same route. Tested with both configuration from code and xml file and againt Kepware server. Value of the item is changing, verified with UaExpert and with the old version my lib.
Surrely I'm missing something and I dont know what it is.
Expected Behavior
To receive notifications
Steps To Reproduce
No response
Environment
Anything else?
No response
The text was updated successfully, but these errors were encountered: