コード例 #1
0
 public async Task Shutdown()
 {
     _opcSessionSemaphore.Wait();
     try
     {
         // if the session is connected, close it.
         if (State == SessionState.Connected)
         {
             try
             {
                 Trace($"Removing {Session.DefaultSubscription.MonitoredItemCount} monitored items from default subscription from session to endpoint URI '{EndpointUri.AbsoluteUri}'.");
                 Session.DefaultSubscription.RemoveItems(Session.DefaultSubscription.MonitoredItems);
                 Trace($"Closing session to endpoint URI '{EndpointUri.AbsoluteUri}' closed successfully.");
                 Session.Close();
                 State = SessionState.Disconnected;
                 Trace($"Session to endpoint URI '{EndpointUri.AbsoluteUri}' closed successfully.");
             }
             catch (Exception e)
             {
                 Trace(e, $"Error while closing session to endpoint '{EndpointUri.AbsoluteUri}'.");
                 State = SessionState.Disconnected;
                 return;
             }
         }
     }
     finally
     {
         _opcSessionSemaphore.Release();
         if (OpcSessions.Count(s => s.State == SessionState.Connected) == 0)
         {
             _opcSessionSemaphore.Dispose();
         }
     }
 }
コード例 #2
0
        /// <summary>
        /// Ctor to initialize resources for the telemetry configuration.
        /// </summary>
        public PublisherNodeConfiguration()
        {
            OpcSessionsListSemaphore                = new SemaphoreSlim(1);
            PublisherNodeConfigurationSemaphore     = new SemaphoreSlim(1);
            PublisherNodeConfigurationFileSemaphore = new SemaphoreSlim(1);
            OpcSessions.Clear();
            _nodePublishingConfiguration = new List <NodePublishingConfigurationModel>();
            _configurationFileEntries    = new List <PublisherConfigurationFileEntryLegacyModel>();

            // read the configuration from the configuration file
            if (!ReadConfigAsync().Result)
            {
                string errorMessage = $"Error while reading the node configuration file '{PublisherNodeConfigurationFilename}'";
                Logger.Error(errorMessage);
                throw new Exception(errorMessage);
            }

            // create the configuration data structures
            if (!CreateOpcPublishingDataAsync().Result)
            {
                string errorMessage = $"Error while creating node configuration data structures.";
                Logger.Error(errorMessage);
                throw new Exception(errorMessage);
            }
        }
コード例 #3
0
        /// <summary>
        /// Kicks of the work horse of the publisher regularily for all sessions.
        /// </summary>
        public static async Task SessionConnectorAsync(CancellationToken ct)
        {
            while (true)
            {
                try
                {
                    // get tasks for all disconnected sessions and start them
                    IEnumerable <Task> singleSessionHandlerTaskList;
                    try
                    {
                        await OpcSessionsListSemaphore.WaitAsync();

                        singleSessionHandlerTaskList = OpcSessions.Select(s => s.ConnectAndMonitorAsync(ct));
                    }
                    finally
                    {
                        OpcSessionsListSemaphore.Release();
                    }
                    await Task.WhenAll(singleSessionHandlerTaskList);
                }
                catch (Exception e)
                {
                    Trace(e, $"Failed to connect and monitor a disconnected server. {(e.InnerException != null ? e.InnerException.Message : "")}");
                }
                try
                {
                    await Task.Delay(_publisherSessionConnectWaitSec * 1000, ct);
                }
                catch { }
                if (ct.IsCancellationRequested)
                {
                    return;
                }
            }
        }
コード例 #4
0
        /// <summary>
        /// Handler for the standard "keep alive" event sent by all OPC UA servers
        /// </summary>
        private static void StandardClient_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            // Ignore if we are shutting down.
            if (PublisherShutdownInProgress == true)
            {
                return;
            }

            if (e != null && session != null && session.ConfiguredEndpoint != null)
            {
                OpcSession opcSession = null;
                try
                {
                    OpcSessionsSemaphore.Wait();


                    var opcSessions = OpcSessions.Where(s => s.Session != null);
                    opcSession = opcSessions.Where(s => s.Session.ConfiguredEndpoint.EndpointUrl.Equals(session.ConfiguredEndpoint.EndpointUrl)).FirstOrDefault();

                    if (!ServiceResult.IsGood(e.Status))
                    {
                        Trace($"Session endpoint: {session.ConfiguredEndpoint.EndpointUrl} has Status: {e.Status}");
                        Trace($"Outstanding requests: {session.OutstandingRequestCount}, Defunct requests: {session.DefunctRequestCount}");
                        Trace($"Good publish requests: {session.GoodPublishRequestCount}, KeepAlive interval: {session.KeepAliveInterval}");
                        Trace($"SessionId: {session.SessionId}");

                        if (opcSession != null && opcSession.State == SessionState.Connected)
                        {
                            opcSession.MissedKeepAlives++;
                            Trace($"Missed KeepAlives: {opcSession.MissedKeepAlives}");
                            if (opcSession.MissedKeepAlives >= OpcKeepAliveDisconnectThreshold)
                            {
                                Trace($"Hit configured missed keep alive threshold of {Program.OpcKeepAliveDisconnectThreshold}. Disconnecting the session to endpoint {session.ConfiguredEndpoint.EndpointUrl}.");
                                session.KeepAlive -= StandardClient_KeepAlive;
                                opcSession.Disconnect();
                            }
                        }
                    }
                    else
                    {
                        if (opcSession != null && opcSession.MissedKeepAlives != 0)
                        {
                            // Reset missed keep alive count
                            Trace($"Session endpoint: {session.ConfiguredEndpoint.EndpointUrl} got a keep alive after {opcSession.MissedKeepAlives} {(opcSession.MissedKeepAlives == 1 ? "was" : "were")} missed.");
                            opcSession.MissedKeepAlives = 0;
                        }
                    }
                }
                finally
                {
                    OpcSessionsSemaphore.Release();
                }
            }
            else
            {
                Trace("Keep alive arguments seems to be wrong.");
            }
        }
コード例 #5
0
 /// <summary>
 /// Initialize resources for the node configuration.
 /// </summary>
 public static void Init()
 {
     OpcSessionsListSemaphore                = new SemaphoreSlim(1);
     PublisherNodeConfigurationSemaphore     = new SemaphoreSlim(1);
     PublisherNodeConfigurationFileSemaphore = new SemaphoreSlim(1);
     OpcSessions.Clear();
     _nodePublishingConfiguration = new List <NodePublishingConfigurationModel>();
     _configurationFileEntries    = new List <PublisherConfigurationFileEntryLegacyModel>();
 }
コード例 #6
0
 /// <summary>
 /// Frees resources for the node configuration.
 /// </summary>
 public static void Deinit()
 {
     OpcSessions.Clear();
     _nodePublishingConfiguration = null;
     OpcSessionsListSemaphore.Dispose();
     OpcSessionsListSemaphore = null;
     PublisherNodeConfigurationSemaphore.Dispose();
     PublisherNodeConfigurationSemaphore = null;
     PublisherNodeConfigurationFileSemaphore.Dispose();
     PublisherNodeConfigurationFileSemaphore = null;
 }
コード例 #7
0
        /// <summary>
        /// Shutdown all sessions.
        /// </summary>
        public async static Task SessionShutdownAsync()
        {
            if (HaveToWriteCsv)
            {
                csvWriter.Flush();
                csvWriter.Dispose();
            }

            try
            {
                while (OpcSessions.Count > 0)
                {
                    OpcSession opcSession = null;
                    try
                    {
                        await OpcSessionsListSemaphore.WaitAsync();

                        opcSession = OpcSessions.ElementAt(0);
                        OpcSessions.RemoveAt(0);
                    }
                    finally
                    {
                        OpcSessionsListSemaphore.Release();
                    }
                    await opcSession?.ShutdownAsync();
                }
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "Failed to shutdown all sessions.");
            }

            // wait and continue after a while
            uint maxTries = ShutdownRetryCount;

            while (true)
            {
                int sessionCount = OpcSessions.Count;
                if (sessionCount == 0)
                {
                    return;
                }
                if (maxTries-- == 0)
                {
                    Logger.Information($"There are still {sessionCount} sessions alive. Ignore and continue shutdown.");
                    return;
                }
                Logger.Information($"{ProgramName} is shutting down. Wait {SessionConnectWait} seconds, since there are stil {sessionCount} sessions alive...");
                await Task.Delay(SessionConnectWait * 1000);
            }
        }
コード例 #8
0
ファイル: OpcConfiguration.cs プロジェクト: infoil/opc-reader
        /// <summary>
        /// Create the data structures to manage actions.
        /// </summary>
        public static async Task <bool> CreateOpcActionDataAsync()
        {
            try
            {
                await OpcActionListSemaphore.WaitAsync();

                await OpcSessionsListSemaphore.WaitAsync();

                // create actions out of the configuration
                var uniqueSessionInfo = _actionConfiguration.Select(n => new Tuple <Uri, bool>(n.EndpointUrl, n.UseSecurity)).Distinct();
                foreach (var sessionInfo in uniqueSessionInfo)
                {
                    // create new session info.
                    OpcSession opcSession = new OpcSession(sessionInfo.Item1, sessionInfo.Item2, OpcSessionCreationTimeout);

                    // add all actions to the session
                    List <OpcAction> actionsOnEndpoint = new List <OpcAction>();
                    var endpointConfigs = _actionConfiguration.Where(c => c.EndpointUrl == sessionInfo.Item1 && c.UseSecurity == sessionInfo.Item2);
                    foreach (var config in endpointConfigs)
                    {
                        config?.Read.ForEach(a => opcSession.OpcActions.Add(new OpcReadAction(config.EndpointUrl, a)));
                        config?.Test.ForEach(a => opcSession.OpcActions.Add(new OpcTestAction(config.EndpointUrl, a)));
                        config?.HistoryRead.ForEach(a => opcSession.OpcActions.Add(new OpcHistoryReadAction(config.EndpointUrl, a)));
                    }

                    // report actions
                    Logger.Information($"Actions on '{opcSession.EndpointUrl.AbsoluteUri}' {(opcSession.UseSecurity ? "with" : "without")} security.");
                    foreach (var action in opcSession.OpcActions)
                    {
                        Logger.Information($"{action.Description}, recurring each: {action.Interval} sec");
                    }

                    // add session
                    OpcSessions.Add(opcSession);
                }
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "Error: creation of the internal OPC management structures failed. Exiting...");
                Environment.ExitCode = 1;
                return(false);
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
                OpcActionListSemaphore.Release();
            }
            return(true);
        }
コード例 #9
0
        /// <summary>
        /// Start all sessions.
        /// </summary>
        public async static Task SessionStartAsync()
        {
            try
            {
                await OpcSessionsListSemaphore.WaitAsync();

                OpcSessions.ForEach(s => s.ConnectAndMonitorSession.Set());
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "Failed to start all sessions.");
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
            }
        }
コード例 #10
0
        /// <summary>
        /// Shutdown all sessions.
        /// </summary>
        public async static Task SessionShutdownAsync()
        {
            try
            {
                while (OpcSessions.Count > 0)
                {
                    OpcSession opcSession = null;
                    try
                    {
                        await OpcSessionsListSemaphore.WaitAsync();

                        opcSession = OpcSessions.ElementAt(0);
                        OpcSessions.RemoveAt(0);
                    }
                    finally
                    {
                        OpcSessionsListSemaphore.Release();
                    }
                    await opcSession?.ShutdownAsync();
                }
            }
            catch (Exception e)
            {
                Trace(e, "Failed to shutdown all sessions.");
            }

            // Wait and continue after a while.
            uint maxTries = _publisherShutdownWaitPeriod;

            while (true)
            {
                int sessionCount = OpcSessions.Count;
                if (sessionCount == 0)
                {
                    return;
                }
                if (maxTries-- == 0)
                {
                    Trace($"There are still {sessionCount} sessions alive. Ignore and continue shutdown.");
                    return;
                }
                Trace($"Publisher is shutting down. Wait {_publisherSessionConnectWaitSec} seconds, since there are stil {sessionCount} sessions alive...");
                await Task.Delay(_publisherSessionConnectWaitSec * 1000);
            }
        }
コード例 #11
0
 /// <summary>
 /// write to session.
 /// </summary>
 public static void SessionWriteNode(string uri, string node, string value)
 {
     try
     {
         if (OpcSessions.Count > 0)
         {
             OpcSession opcSession = OpcSessions.Find(x => x.EndpointUri.ToString() == uri);
             if (opcSession != null)
             {
                 opcSession.WriteNode(node, value);
             }
         }
     }
     catch (Exception e)
     {
         Trace(e, $"Failed to write value to session. (message: {e.Message}");
     }
 }
コード例 #12
0
        /// <summary>
        /// Create the data structures to manage OPC sessions and actions.
        /// </summary>
        /// <returns></returns>
        public static async Task <bool> CreateOpcActionDataAsync()
        {
            try
            {
                await OpcActionListSemaphore.WaitAsync();

                await OpcSessionsListSemaphore.WaitAsync();

                // create actions out of the configuration
                var uniqueEndpointUrls = _actionConfiguration.Select(n => n.EndpointUrl).Distinct();
                foreach (var endpointUrl in uniqueEndpointUrls)
                {
                    // create new session info.
                    OpcSession opcSession = new OpcSession(endpointUrl, _actionConfiguration.Where(n => n.EndpointUrl == endpointUrl).First().UseSecurity, OpcSessionCreationTimeout);

                    // add all actions to the session
                    List <OpcAction> actionsOnEndpoint = new List <OpcAction>();
                    var endpointConfigs = _actionConfiguration.Where(c => c.EndpointUrl == endpointUrl);
                    foreach (var config in endpointConfigs)
                    {
                        config?.Read.ForEach(r => opcSession.OpcActions.Add(new OpcReadAction(r)));
                        config?.Write.ForEach(w => opcSession.OpcActions.Add(new OpcWriteAction(w)));
                    }

                    // add session.
                    OpcSessions.Add(opcSession);
                }
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "Creation of the internal OPC management structures failed. Exiting...");
                return(false);
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
                OpcActionListSemaphore.Release();
            }
            return(true);
        }
コード例 #13
0
 /// <summary>
 /// Implement IDisposable.
 /// </summary>
 protected virtual void Dispose(bool disposing)
 {
     if (disposing)
     {
         OpcSessionsListSemaphore.Wait();
         foreach (var opcSession in OpcSessions)
         {
             opcSession.Dispose();
         }
         OpcSessions?.Clear();
         OpcSessionsListSemaphore?.Dispose();
         OpcSessionsListSemaphore = null;
         PublisherNodeConfigurationSemaphore?.Dispose();
         PublisherNodeConfigurationSemaphore = null;
         PublisherNodeConfigurationFileSemaphore?.Dispose();
         PublisherNodeConfigurationFileSemaphore = null;
         _nodePublishingConfiguration?.Clear();
         _nodePublishingConfiguration = null;
         lock (_singletonLock)
         {
             _instance = null;
         }
     }
 }
コード例 #14
0
 /// <summary>
 /// Implement IDisposable.
 /// </summary>
 protected virtual void Dispose(bool disposing)
 {
     if (disposing)
     {
         //OpcSessionsListSemaphore.Wait(); avoid get semaphore here, otherwise there will be deadlock during OpcSession disposing
         foreach (var opcSession in OpcSessions)
         {
             opcSession.Dispose();
         }
         OpcSessions?.Clear();
         OpcSessionsListSemaphore?.Dispose();
         OpcSessionsListSemaphore = null;
         PublisherNodeConfigurationSemaphore?.Dispose();
         PublisherNodeConfigurationSemaphore = null;
         PublisherNodeConfigurationFileSemaphore?.Dispose();
         PublisherNodeConfigurationFileSemaphore = null;
         _nodePublishingConfiguration?.Clear();
         _nodePublishingConfiguration = null;
         lock (_singletonLock)
         {
             _instance = null;
         }
     }
 }
コード例 #15
0
        /// <summary>
        /// Method to remove the node from the subscription and stop publishing telemetry to IoTHub. Executes synchronously.
        /// </summary>
        private ServiceResult OnUnpublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            string logPrefix = "OnUnpublishNodeCall:";

            if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
            {
                Logger.Error($"{logPrefix} Invalid arguments!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            HttpStatusCode statusCode     = HttpStatusCode.InternalServerError;
            NodeId         nodeId         = null;
            ExpandedNodeId expandedNodeId = null;
            Uri            endpointUri    = null;
            bool           isNodeIdFormat = true;

            try
            {
                string id = inputArguments[0] as string;
                if (id.Contains("nsu=", StringComparison.InvariantCulture))
                {
                    expandedNodeId = ExpandedNodeId.Parse(id);
                    isNodeIdFormat = false;
                }
                else
                {
                    nodeId         = NodeId.Parse(id);
                    isNodeIdFormat = true;
                }
                endpointUri = new Uri(inputArguments[1] as string);
            }
            catch (UriFormatException)
            {
                Logger.Error($"{logPrefix} The endpointUrl is invalid '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }
            catch (Exception e)
            {
                Logger.Error(e, $"{logPrefix} The NodeId has an invalid format '{inputArguments[0] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in NodeId or ExpandedNodeId format as first argument!"));
            }

            // find the session and stop monitoring the node.
            try
            {
                OpcSessionsListSemaphore.Wait();
                if (ShutdownTokenSource.IsCancellationRequested)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                try
                {
                    opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUrl.Equals(endpointUri.OriginalString, StringComparison.OrdinalIgnoreCase));
                }
                catch
                {
                    opcSession = null;
                }

                if (opcSession == null)
                {
                    // do nothing if there is no session for this endpoint.
                    Logger.Error($"{logPrefix} Session for endpoint '{endpointUri.OriginalString}' not found.");
                    return(ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for endpoint of node to unpublished not found!"));
                }
                else
                {
                    if (isNodeIdFormat)
                    {
                        // stop monitoring the node, execute syncronously
                        Logger.Information($"{logPrefix} Request to stop monitoring item with NodeId '{nodeId.ToString()}')");
                        statusCode = opcSession.RequestMonitorItemRemovalAsync(nodeId, null, ShutdownTokenSource.Token).Result;
                    }
                    else
                    {
                        // stop monitoring the node, execute syncronously
                        Logger.Information($"{logPrefix} Request to stop monitoring item with ExpandedNodeId '{expandedNodeId.ToString()}')");
                        statusCode = opcSession.RequestMonitorItemRemovalAsync(null, expandedNodeId, ShutdownTokenSource.Token).Result;
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Error(e, $"{logPrefix} Exception while trying to configure publishing node '{nodeId.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error unpublishing node: {e.Message}"));
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
            }
            return(statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted ? ServiceResult.Good : ServiceResult.Create(StatusCodes.Bad, "Can not stop monitoring node!"));
        }
コード例 #16
0
        /// <summary>
        /// Method to start monitoring a node and publish the data to IoTHub. Executes synchronously.
        /// </summary>
        private ServiceResult OnPublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            string logPrefix = "OnPublishNodeCall:";

            if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
            {
                Logger.Error($"{logPrefix} Invalid Arguments when trying to publish a node.");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
            }

            HttpStatusCode statusCode     = HttpStatusCode.InternalServerError;
            NodeId         nodeId         = null;
            ExpandedNodeId expandedNodeId = null;
            Uri            endpointUri    = null;
            bool           isNodeIdFormat = true;

            try
            {
                string id = inputArguments[0] as string;
                if (id.Contains("nsu=", StringComparison.InvariantCulture))
                {
                    expandedNodeId = ExpandedNodeId.Parse(id);
                    isNodeIdFormat = false;
                }
                else
                {
                    nodeId         = NodeId.Parse(id);
                    isNodeIdFormat = true;
                }
                endpointUri = new Uri(inputArguments[1] as string);
            }
            catch (UriFormatException)
            {
                Logger.Error($"{logPrefix} The EndpointUrl has an invalid format '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }
            catch (Exception e)
            {
                Logger.Error(e, $"{logPrefix} The NodeId has an invalid format '{inputArguments[0] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in NodeId or ExpandedNodeId format as first argument!"));
            }

            // find/create a session to the endpoint URL and start monitoring the node.
            try
            {
                // lock the publishing configuration till we are done
                OpcSessionsListSemaphore.Wait();

                if (ShutdownTokenSource.IsCancellationRequested)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUrl.Equals(endpointUri.OriginalString, StringComparison.OrdinalIgnoreCase));

                // add a new session.
                if (opcSession == null)
                {
                    // create new session info.
                    opcSession = new OpcSession(endpointUri.OriginalString, true, OpcSessionCreationTimeout);
                    OpcSessions.Add(opcSession);
                    Logger.Information($"OnPublishNodeCall: No matching session found for endpoint '{endpointUri.OriginalString}'. Requested to create a new one.");
                }

                if (isNodeIdFormat)
                {
                    // add the node info to the subscription with the default publishing interval, execute syncronously
                    Logger.Debug($"{logPrefix} Request to monitor item with NodeId '{nodeId.ToString()}' (with default PublishingInterval and SamplingInterval)");
                    statusCode = opcSession.AddNodeForMonitoringAsync(nodeId, null, null, null, null, null, null, ShutdownTokenSource.Token).Result;
                }
                else
                {
                    // add the node info to the subscription with the default publishing interval, execute syncronously
                    Logger.Debug($"{logPrefix} Request to monitor item with ExpandedNodeId '{expandedNodeId.ToString()}' (with default PublishingInterval and SamplingInterval)");
                    statusCode = opcSession.AddNodeForMonitoringAsync(null, expandedNodeId, null, null, null, null, null, ShutdownTokenSource.Token).Result;
                }
            }
            catch (Exception e)
            {
                Logger.Error(e, $"{logPrefix} Exception while trying to configure publishing node '{(isNodeIdFormat ? nodeId.ToString() : expandedNodeId.ToString())}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
            }

            if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted)
            {
                return(ServiceResult.Good);
            }
            return(ServiceResult.Create(StatusCodes.Bad, "Can not start monitoring node! Reason unknown."));
        }
コード例 #17
0
        /// <summary>
        /// Asynchronous part of the main method of the app.
        /// </summary>
        public async static Task MainAsync(string[] args)
        {
            try
            {
                var shouldShowHelp = false;

                // shutdown token sources
                ShutdownTokenSource = new CancellationTokenSource();

                // command line options
                Mono.Options.OptionSet options = new Mono.Options.OptionSet {
                    { "cf|configfile=", $"the filename containing action configuration.\nDefault: '{OpcActionConfigurationFilename}'", (string p) => OpcActionConfigurationFilename = p },

                    { "tc|testconnectivity", $"tests connectivity with the server.\nDefault: {TestConnectivity}", b => TestConnectivity = b != null },
                    { "tu|testunsecureconnectivity", $"tests connectivity with the server using an unsecured endpoint.\nDefault: {TestUnsecureConnectivity}", b => TestUnsecureConnectivity = b != null },

                    { "de|defaultendpointurl=", $"endpoint OPC UA server used as default.\nDefault: {DefaultEndpointUrl}", (string s) => DefaultEndpointUrl = s },

                    { "sw|sessionconnectwait=", $"specify the wait time in seconds we try to connect to disconnected endpoints and starts monitoring unmonitored items\nMin: 10\nDefault: {SessionConnectWait}", (int i) => {
                          if (i > 10)
                          {
                              SessionConnectWait = i;
                          }
                          else
                          {
                              throw new OptionException("The sessionconnectwait must be greater than 10 sec", "sessionconnectwait");
                          }
                      } },
                    { "di|diagnosticsinterval=", $"shows diagnostic info at the specified interval in seconds (need log level info). 0 disables diagnostic output.\nDefault: {DiagnosticsInterval}", (uint u) => DiagnosticsInterval = u },

                    { "lf|logfile=", $"the filename of the logfile to use.\nDefault: don't write logfile.", (string l) => _logFileName = l },
                    { "lt|logflushtimespan=", $"the timespan in seconds when the logfile should be flushed.\nDefault: {_logFileFlushTimeSpanSec} sec", (int s) => {
                          if (s > 0)
                          {
                              _logFileFlushTimeSpanSec = TimeSpan.FromSeconds(s);
                          }
                          else
                          {
                              throw new Mono.Options.OptionException("The logflushtimespan must be a positive number.", "logflushtimespan");
                          }
                      } },
                    { "ll|loglevel=", $"the loglevel to use (allowed: fatal, error, warn, info, debug, verbose).\nDefault: info", (string l) => {
                          List <string> logLevels = new List <string> {
                              "fatal", "error", "warn", "info", "debug", "verbose"
                          };
                          if (logLevels.Contains(l.ToLowerInvariant()))
                          {
                              _logLevel = l.ToLowerInvariant();
                          }
                          else
                          {
                              throw new Mono.Options.OptionException("The loglevel must be one of: fatal, error, warn, info, debug, verbose", "loglevel");
                          }
                      } },

                    // opc configuration options
                    { "ol|opcmaxstringlen=", $"the max length of a string opc can transmit/receive.\nDefault: {OpcMaxStringLength}", (int i) => {
                          if (i > 0)
                          {
                              OpcMaxStringLength = i;
                          }
                          else
                          {
                              throw new OptionException("The max opc string length must be larger than 0.", "opcmaxstringlen");
                          }
                      } },
                    { "ot|operationtimeout=", $"the operation timeout of the OPC UA client in ms.\nDefault: {OpcOperationTimeout}", (int i) => {
                          if (i >= 0)
                          {
                              OpcOperationTimeout = i;
                          }
                          else
                          {
                              throw new OptionException("The operation timeout must be larger or equal 0.", "operationtimeout");
                          }
                      } },
                    { "ct|createsessiontimeout=", $"specify the timeout in seconds used when creating a session to an endpoint. On unsuccessful connection attemps a backoff up to {OpcSessionCreationBackoffMax} times the specified timeout value is used.\nMin: 1\nDefault: {OpcSessionCreationTimeout}", (uint u) => {
                          if (u > 1)
                          {
                              OpcSessionCreationTimeout = u;
                          }
                          else
                          {
                              throw new OptionException("The createsessiontimeout must be greater than 1 sec", "createsessiontimeout");
                          }
                      } },
                    { "ki|keepaliveinterval=", $"specify the interval in seconds se send keep alive messages to the OPC servers on the endpoints it is connected to.\nMin: 2\nDefault: {OpcKeepAliveInterval}", (int i) => {
                          if (i >= 2)
                          {
                              OpcKeepAliveInterval = i;
                          }
                          else
                          {
                              throw new OptionException("The keepaliveinterval must be greater or equal 2", "keepalivethreshold");
                          }
                      } },
                    { "kt|keepalivethreshold=", $"specify the number of keep alive packets a server can miss, before the session is disconneced\nMin: 1\nDefault: {OpcKeepAliveDisconnectThreshold}", (uint u) => {
                          if (u > 1)
                          {
                              OpcKeepAliveDisconnectThreshold = u;
                          }
                          else
                          {
                              throw new OptionException("The keepalivethreshold must be greater than 1", "keepalivethreshold");
                          }
                      } },

                    { "csvOutput=|csvoutput=", $"filename to store readed values in CSV format.\nDefault: './{_csvFileName}'", (string path) => _csvFileName = path },

                    { "aa|autoaccept", $"trusts all servers we establish a connection to.\nDefault: {AutoAcceptCerts}", b => AutoAcceptCerts = b != null },

                    { "to|trustowncert", $"our own certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", t => TrustMyself = t != null },

                    // cert store options
                    { "at|appcertstoretype=", $"the own application cert store type. \n(allowed values: Directory, X509Store)\nDefault: '{OpcOwnCertStoreType}'", (string s) => {
                          if (s.Equals(CertificateStoreType.X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
                          {
                              OpcOwnCertStoreType = s.Equals(CertificateStoreType.X509Store, StringComparison.OrdinalIgnoreCase) ? CertificateStoreType.X509Store : CertificateStoreType.Directory;
                              OpcOwnCertStorePath = s.Equals(CertificateStoreType.X509Store, StringComparison.OrdinalIgnoreCase) ? OpcOwnCertX509StorePathDefault : OpcOwnCertDirectoryStorePathDefault;
                          }
                          else
                          {
                              throw new OptionException();
                          }
                      } },
                    { "ap|appcertstorepath=", $"the path where the own application cert should be stored\nDefault (depends on store type):\n" +
                      $"X509Store: '{OpcOwnCertX509StorePathDefault}'\n" +
                      $"Directory: '{OpcOwnCertDirectoryStorePathDefault}'", (string s) => OpcOwnCertStorePath = s },

                    { "tp|trustedcertstorepath=", $"the path of the trusted cert store\nDefault '{OpcTrustedCertDirectoryStorePathDefault}'", (string s) => OpcTrustedCertStorePath = s },

                    { "rp|rejectedcertstorepath=", $"the path of the rejected cert store\nDefault '{OpcRejectedCertDirectoryStorePathDefault}'", (string s) => OpcRejectedCertStorePath = s },

                    { "ip|issuercertstorepath=", $"the path of the trusted issuer cert store\nDefault '{OpcIssuerCertDirectoryStorePathDefault}'", (string s) => OpcIssuerCertStorePath = s },

                    { "csr", $"show data to create a certificate signing request\nDefault '{ShowCreateSigningRequestInfo}'", c => ShowCreateSigningRequestInfo = c != null },

                    { "ab|applicationcertbase64=", $"update/set this applications certificate with the certificate passed in as bas64 string", (string s) =>
                      {
                          NewCertificateBase64String = s;
                      } },
                    { "af|applicationcertfile=", $"update/set this applications certificate with the certificate file specified", (string s) =>
                      {
                          if (File.Exists(s))
                          {
                              NewCertificateFileName = s;
                          }
                          else
                          {
                              throw new OptionException($"The file '{s}' does not exist.", "applicationcertfile");
                          }
                      } },

                    { "pb|privatekeybase64=", $"initial provisioning of the application certificate (with a PEM or PFX fomat) requires a private key passed in as base64 string", (string s) =>
                      {
                          PrivateKeyBase64String = s;
                      } },
                    { "pk|privatekeyfile=", $"initial provisioning of the application certificate (with a PEM or PFX fomat) requires a private key passed in as file", (string s) =>
                      {
                          if (File.Exists(s))
                          {
                              PrivateKeyFileName = s;
                          }
                          else
                          {
                              throw new OptionException($"The file '{s}' does not exist.", "privatekeyfile");
                          }
                      } },

                    { "cp|certpassword="******"the optional password for the PEM or PFX or the installed application certificate", (string s) =>
                      {
                          CertificatePassword = s;
                      } },

                    { "tb|addtrustedcertbase64=", $"adds the certificate to the applications trusted cert store passed in as base64 string (multiple strings supported)", (string s) =>
                      {
                          TrustedCertificateBase64Strings = ParseListOfStrings(s);
                      } },
                    { "tf|addtrustedcertfile=", $"adds the certificate file(s) to the applications trusted cert store passed in as base64 string (multiple filenames supported)", (string s) =>
                      {
                          TrustedCertificateFileNames = ParseListOfFileNames(s, "addtrustedcertfile");
                      } },

                    { "ib|addissuercertbase64=", $"adds the specified issuer certificate to the applications trusted issuer cert store passed in as base64 string (multiple strings supported)", (string s) =>
                      {
                          IssuerCertificateBase64Strings = ParseListOfStrings(s);
                      } },
                    { "if|addissuercertfile=", $"adds the specified issuer certificate file(s) to the applications trusted issuer cert store (multiple filenames supported)", (string s) =>
                      {
                          IssuerCertificateFileNames = ParseListOfFileNames(s, "addissuercertfile");
                      } },

                    { "rb|updatecrlbase64=", $"update the CRL passed in as base64 string to the corresponding cert store (trusted or trusted issuer)", (string s) =>
                      {
                          CrlBase64String = s;
                      } },
                    { "uc|updatecrlfile=", $"update the CRL passed in as file to the corresponding cert store (trusted or trusted issuer)", (string s) =>
                      {
                          if (File.Exists(s))
                          {
                              CrlFileName = s;
                          }
                          else
                          {
                              throw new OptionException($"The file '{s}' does not exist.", "updatecrlfile");
                          }
                      } },

                    { "rc|removecert=", $"remove cert(s) with the given thumbprint(s) (multiple thumbprints supported)", (string s) =>
                      {
                          ThumbprintsToRemove = ParseListOfStrings(s);
                      } },

                    // misc
                    { "h|help", "show this message and exit", h => shouldShowHelp = h != null },
                };

                List <string> extraArgs = new List <string>();
                try
                {
                    // parse the command line
                    extraArgs = options.Parse(args);
                }
                catch (OptionException e)
                {
                    // initialize logging
                    InitLogging();

                    // show message
                    Logger.Fatal(e, "Error in command line options");
                    Environment.ExitCode = 1;
                    Logger.Error($"Command line arguments: {String.Join(" ", args)}");

                    // show usage
                    Usage(options);
                    return;
                }

                // initialize logging
                InitLogging();

                // show usage if requested
                if (shouldShowHelp)
                {
                    Usage(options);
                    return;
                }

                // validate and parse extra arguments
                if (extraArgs.Count > 0)
                {
                    Logger.Error("Error in command line options");
                    Environment.ExitCode = 1;
                    Logger.Error($"Command line arguments: {String.Join(" ", args)}");
                    Usage(options);
                    return;
                }

                //show version
                Logger.Information($"{ProgramName} V{FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion} starting up...");
                Logger.Debug($"Informational version: V{(Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute).InformationalVersion}");

                // allow canceling the application
                var quitEvent = new ManualResetEvent(false);
                try
                {
                    Console.CancelKeyPress += (sender, eArgs) =>
                    {
                        quitEvent.Set();
                        eArgs.Cancel = true;
                        ShutdownTokenSource.Cancel();
                    };
                }
                catch
                {
                }

                // init OPC configuration and tracing
                OpcApplicationConfiguration applicationStackConfiguration = new OpcApplicationConfiguration();
                await applicationStackConfiguration.ConfigureAsync();

                // read OPC action configuration
                OpcConfiguration.Init();
                if (!await ReadOpcConfigurationAsync())
                {
                    return;
                }

                // add well known actions
                if (!await CreateOpcActionDataAsync())
                {
                    return;
                }

                // kick off OPC client activities
                await SessionStartAsync();

                // initialize diagnostics
                Diagnostics.Init();

                await OpcSessionsListSemaphore.WaitAsync();

                var pendingOperations = OpcSessions.Sum(s => s.OpcActions.Count);
                OpcSessionsListSemaphore.Release();
                while (pendingOperations > 0 && ShutdownTokenSource.IsCancellationRequested != true)
                {
                    Logger.Information($"{pendingOperations} pending operation(s).");
                    Logger.Information("");
                    Logger.Information($"{ProgramName} is running. Press CTRL-C to quit.");
                    Thread.Sleep(1000);
                    await OpcSessionsListSemaphore.WaitAsync();

                    pendingOperations = OpcSessions.Sum(s => s.OpcActions.Count);
                    OpcSessionsListSemaphore.Release();
                }

                Logger.Information("No pending operations.");
                Logger.Information("");
                ShutdownTokenSource.Cancel();
                Logger.Information($"{ProgramName} is shutting down...");

                // shutdown all OPC sessions
                await SessionShutdownAsync();

                // shutdown diagnostics
                await ShutdownAsync();

                ShutdownTokenSource = null;
            }
            catch (Exception e)
            {
                Logger.Fatal(e, e.StackTrace);
                Environment.ExitCode = 1;
                e = e.InnerException ?? null;
                while (e != null)
                {
                    Logger.Fatal(e, e.StackTrace);
                    e = e.InnerException ?? null;
                }
                Logger.Fatal($"{ProgramName} exiting... ");
            }
        }
コード例 #18
0
        /// <summary>
        /// Method to remove the node from the subscription and stop publishing telemetry to IoTHub.
        /// </summary>
        private ServiceResult OnUnpublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (inputArguments[0] == null || inputArguments[1] == null)
            {
                Trace("UnpublishNode: Invalid arguments!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            NodeId nodeId      = null;
            Uri    endpointUri = null;

            try
            {
                if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
                {
                    Trace($"UnpublishNode: Arguments (0 (nodeId), 1 (endpointUrl)) are not valid strings!");
                    return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
                }
                nodeId      = inputArguments[0] as string;
                endpointUri = new Uri(inputArguments[1] as string);
            }
            catch (UriFormatException)
            {
                Trace($"UnpublishNode: The endpointUrl is invalid '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }

            // find the session and stop monitoring the node.
            try
            {
                if (PublisherShutdownInProgress)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                try
                {
                    OpcSessionsSemaphore.Wait();
                    opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUri == endpointUri);
                }
                catch
                {
                    opcSession = null;
                }
                finally
                {
                    OpcSessionsSemaphore.Release();
                }
                if (opcSession == null)
                {
                    // do nothing if there is no session for this endpoint.
                    Trace($"UnpublishNode: Session for endpoint '{endpointUri.OriginalString}' not found.");
                    return(ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for endpoint of published node not found!"));
                }
                else
                {
                    Trace($"UnpublishNode: Session found for endpoint '{endpointUri.OriginalString}'");
                }

                // remove the node from the sessions monitored items list.
                opcSession.TagNodeForMonitoringStop(nodeId);
                Trace("UnpublishNode: Requested to stop monitoring of node.");

                // remove node from persisted config file
                try
                {
                    PublishDataSemaphore.Wait();
                    var entryToRemove = PublisherConfigFileEntries.Find(l => l.NodeId == nodeId && l.EndpointUri == endpointUri);
                    PublisherConfigFileEntries.Remove(entryToRemove);
                    File.WriteAllText(NodesToPublishAbsFilename, JsonConvert.SerializeObject(PublisherConfigFileEntries));
                }
                finally
                {
                    PublishDataSemaphore.Release();
                }
            }
            catch (Exception e)
            {
                Trace(e, $"UnpublishNode: Exception while trying to configure publishing node '{nodeId.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
            return(ServiceResult.Good);
        }
コード例 #19
0
        /// <summary>
        /// Method exposed as a node in the server to stop monitoring it and no longer publish telemetry of it.
        /// </summary>
        private ServiceResult UnPublishNodeMethod(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (inputArguments[0] == null || inputArguments[1] == null)
            {
                Trace("UnPublishNodeMethod: Invalid arguments!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            string nodeId      = inputArguments[0] as string;
            string endpointUrl = inputArguments[1] as string;

            if (string.IsNullOrEmpty(nodeId) || string.IsNullOrEmpty(endpointUrl))
            {
                Trace($"UnPublishNodeMethod: Arguments (0 (nodeId): '{nodeId}', 1 (endpointUrl):'{endpointUrl}') are not valid strings!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
            }

            NodeToPublish nodeToUnpublish = new NodeToPublish();

            try
            {
                nodeToUnpublish = new NodeToPublish(nodeId, endpointUrl);
            }
            catch (UriFormatException)
            {
                Trace($"UnPublishNodeMethod: The endpointUrl is invalid (0 (nodeId): '{nodeId}', 1 (endpointUrl):'{endpointUrl}')!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }

            // Find the session and stop monitoring the node.
            try
            {
                // find the session we need to monitor the node
                OpcSession opcSession = OpcSessions.First(s => s.EndpointUri == nodeToUnpublish.EndPointUri);
                if (opcSession == null)
                {
                    // do nothing if there is no session for this endpoint.
                    Trace($"UnPublishNodeMethod: Session for endpoint '{nodeToUnpublish.EndPointUri.AbsolutePath}' not found.");
                    return(ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for endpoint of published node not found!"));
                }
                else
                {
                    Trace($"UnPublishNodeMethod: Session found for endpoint '{nodeToUnpublish.EndPointUri.AbsolutePath}'");
                }

                // remove the node from the sessions monitored items list.
                opcSession.TagNodeForMonitoringStop(nodeToUnpublish.NodeId);
                Trace("UnPublishNodeMethod: Requested to stop monitoring of node.");

                // stop monitoring the node
                Task monitorTask = Task.Run(async() => await opcSession.ConnectAndMonitor());
                monitorTask.Wait();
                Trace("UnPublishNodeMethod: Session processing completed.");

                // remove node from our persisted data set.
                var itemToRemove = NodesToPublish.Find(l => l.NodeId == nodeToUnpublish.NodeId && l.EndPointUri == nodeToUnpublish.EndPointUri);
                NodesToPublish.Remove(itemToRemove);

                // persist data
                File.WriteAllText(NodesToPublishAbsFilename, JsonConvert.SerializeObject(NodesToPublish));
            }
            catch (Exception e)
            {
                Trace(e, $"DoPublish: Exception while trying to configure publishing node '{nodeToUnpublish.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
            return(ServiceResult.Good);
        }
コード例 #20
0
        /// <summary>
        /// Method exposed as a node in the server to publish a node to IoT Hub that it is connected to
        /// </summary>
        private ServiceResult PublishNodeMethod(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (inputArguments[0] == null || inputArguments[1] == null)
            {
                Trace("PublishNodeMethod: Invalid Arguments!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            NodeToPublish nodeToPublish;
            string        nodeId      = inputArguments[0] as string;
            string        endpointUrl = inputArguments[1] as string;

            if (string.IsNullOrEmpty(nodeId) || string.IsNullOrEmpty(endpointUrl))
            {
                Trace($"PublishNodeMethod: Arguments (0 (nodeId): '{nodeId}', 1 (endpointUrl):'{endpointUrl}') are not valid strings!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
            }

            try
            {
                nodeToPublish = new NodeToPublish(nodeId, endpointUrl);
            }
            catch (UriFormatException)
            {
                Trace($"PublishNodeMethod: The endpointUrl is invalid (0 (nodeId): '{nodeId}', 1 (endpointUrl):'{endpointUrl}')!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }

            // Create session and add item to monitor, what ever is needed.
            try
            {
                // find the session we need to monitor the node
                OpcSession opcSession = OpcSessions.First(s => s.EndpointUri == nodeToPublish.EndPointUri);

                // Add a new session.
                if (opcSession == null)
                {
                    // create new session info.
                    opcSession = new OpcSession(nodeToPublish.EndPointUri, OpcSessionCreationTimeout);
                    OpcSessions.Add(opcSession);
                    Trace($"DoPublish: No matching session found for endpoint '{nodeToPublish.EndPointUri.AbsolutePath}'. Requested to create a new one.");
                }
                else
                {
                    Trace($"DoPublish: Session found for endpoint '{nodeToPublish.EndPointUri.AbsolutePath}'");
                }

                // add the node info to the sessions monitored items list.
                opcSession.AddNodeForMonitoring(nodeToPublish.NodeId);
                Trace("DoPublish: Requested to monitor item.");

                // start monitoring the node
                Task monitorTask = Task.Run(async() => await opcSession.ConnectAndMonitor());
                monitorTask.Wait();
                Trace("DoPublish: Session processing completed.");

                // update our data
                NodesToPublish.Add(nodeToPublish);

                // persist it to disk
                File.WriteAllText(NodesToPublishAbsFilename, JsonConvert.SerializeObject(NodesToPublish));

                Trace($"DoPublish: Now publishing: {nodeToPublish.ToString()}");
                return(ServiceResult.Good);
            }
            catch (Exception e)
            {
                Trace(e, $"DoPublish: Exception while trying to configure publishing node '{nodeToPublish.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
        }
コード例 #21
0
        /// <summary>
        /// Method to start monitoring a node and publish the data to IoTHub. Executes synchronously.
        /// </summary>
        private ServiceResult OnPublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
            {
                Trace("PublishNode: Invalid Arguments when trying to publish a node.");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
            }

            NodeId nodeId      = null;
            Uri    endpointUri = null;

            try
            {
                nodeId      = NodeId.Parse(inputArguments[0] as string);
                endpointUri = new Uri(inputArguments[1] as string);
            }
            catch (UriFormatException)
            {
                Trace($"PublishNode: The EndpointUri has an invalid format '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }
            catch (Exception e)
            {
                Trace(e, $"PublishNode: The NodeId has an invalid format '{inputArguments[0] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in NodeId format as first argument!"));
            }

            // find/create a session to the endpoint URL and start monitoring the node.
            try
            {
                // lock the publishing configuration till we are done
                OpcSessionsListSemaphore.WaitAsync();

                if (ShutdownTokenSource.IsCancellationRequested)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUri.AbsoluteUri.Equals(endpointUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase));
                string         namespaceUri   = null;
                ExpandedNodeId expandedNodeId = null;

                // add a new session.
                if (opcSession == null)
                {
                    // create new session info.
                    opcSession = new OpcSession(endpointUri, true, OpcSessionCreationTimeout);
                    OpcSessions.Add(opcSession);
                    Trace($"PublishNode: No matching session found for endpoint '{endpointUri.OriginalString}'. Requested to create a new one.");
                }
                else
                {
                    Trace($"PublishNode: Session found for endpoint '{endpointUri.OriginalString}'");

                    // check if node is already published
                    namespaceUri = opcSession.GetNamespaceUri(nodeId.NamespaceIndex);
                    if (string.IsNullOrEmpty(namespaceUri))
                    {
                        return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"The namespace index of the node id is invalid."));
                    }
                    expandedNodeId = new ExpandedNodeId(nodeId.Identifier, nodeId.NamespaceIndex, namespaceUri, 0);
                }

                // add the node info to the subscription with the default publishing interval, execute syncronously
                opcSession.AddNodeForMonitoring(nodeId, expandedNodeId, OpcPublishingInterval, OpcSamplingInterval, ShutdownTokenSource.Token).Wait();
                Trace($"PublishNode: Requested to monitor item with NodeId '{nodeId.ToString()}' (PublishingInterval: {OpcPublishingInterval}, SamplingInterval: {OpcSamplingInterval})");
            }
            catch (Exception e)
            {
                Trace(e, $"PublishNode: Exception while trying to configure publishing node '{nodeId.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
            }
            return(ServiceResult.Good);
        }
コード例 #22
0
        public async Task ConnectAndMonitor()
        {
            _opcSessionSemaphore.Wait();
            Trace($"Connect and monitor session and nodes on endpoint '{EndpointUri.AbsoluteUri}'.");
            try
            {
                // if the session is disconnected, create it.
                if (State == SessionState.Disconnected)
                {
                    EndpointDescription selectedEndpoint   = CoreClientUtils.SelectEndpoint(EndpointUri.AbsoluteUri, true);
                    ConfiguredEndpoint  configuredEndpoint = new ConfiguredEndpoint(selectedEndpoint.Server, EndpointConfiguration.Create(OpcConfiguration));
                    configuredEndpoint.Update(selectedEndpoint);

                    try
                    {
                        uint timeout = SessionTimeout * ((UnsuccessfulConnectionCount >= OpcSessionCreationBackoffMax) ? OpcSessionCreationBackoffMax : UnsuccessfulConnectionCount + 1);
                        Trace($"Create session for endpoint URI '{EndpointUri.AbsoluteUri}' with timeout of {timeout} ms.");
                        Session = await Session.Create(
                            OpcConfiguration,
                            configuredEndpoint,
                            true,
                            false,
                            OpcConfiguration.ApplicationName,
                            timeout,
                            new UserIdentity(new AnonymousIdentityToken()),
                            null);

                        if (Session != null)
                        {
                            Trace($"Session successfully created with Id {Session.SessionId}.");
                            if (!selectedEndpoint.EndpointUrl.Equals(configuredEndpoint.EndpointUrl))
                            {
                                Trace($"the Server has updated the EndpointUrl to '{selectedEndpoint.EndpointUrl}'");
                            }

                            // init object state and install keep alive
                            UnsuccessfulConnectionCount = 0;
                            State = SessionState.Connected;
                            Session.KeepAliveInterval = OpcKeepAliveIntervalInSec * 1000;
                            Session.KeepAlive        += new KeepAliveEventHandler((sender, e) => StandardClient_KeepAlive(sender, e, Session));

                            // fetch the namespace array and cache it. it will not change as long the session exists.
                            NodeId    namespaceArrayNodeId    = new NodeId(Variables.Server_NamespaceArray, 0);
                            DataValue namespaceArrayNodeValue = Session.ReadValue(namespaceArrayNodeId);
                            _namespaceTable.Update(namespaceArrayNodeValue.GetValue <string[]>(null));

                            // show the available namespaces
                            Trace($"The session to endpoint '{selectedEndpoint.EndpointUrl}' has {_namespaceTable.Count} entries in its namespace array:");
                            int i = 0;
                            foreach (var ns in _namespaceTable.ToArray())
                            {
                                Trace($"Namespace index {i++}: {ns}");
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Trace(e, $"Session creation to endpoint '{EndpointUri.AbsoluteUri}' failed {++UnsuccessfulConnectionCount} time(s). Please verify if server is up and Publisher configuration is correct.");
                        State   = SessionState.Disconnected;
                        Session = null;
                        return;
                    }
                }

                // stop monitoring of nodes if requested and remove them from the monitored items list.
                var itemsToRemove = MonitoredItemsInfo.Where(i => i.State == MonitoredItemInfo.MonitoredItemState.StopMonitoring);
                if (itemsToRemove.GetEnumerator().MoveNext())
                {
                    itemsToRemove.GetEnumerator().Reset();
                    Trace($"Remove nodes on endpoint '{EndpointUri.AbsoluteUri}'");
                    Session.DefaultSubscription.RemoveItems(itemsToRemove.Select(i => i.MonitoredItem));
                }

                // ensure all nodes on this session are monitored.
                Trace($"Start monitoring nodes on endpoint '{EndpointUri.AbsoluteUri}'");
                var unmonitoredItems = MonitoredItemsInfo.Where(i => i.State == MonitoredItemInfo.MonitoredItemState.Unmonitored);
                foreach (var item in unmonitoredItems)
                {
                    // if the session is disconnected, we stop trying and wait for the next cycle
                    if (State == SessionState.Disconnected)
                    {
                        break;
                    }

                    NodeId currentNodeId;
                    try
                    {
                        Subscription subscription = Session.DefaultSubscription;
                        if (Session.AddSubscription(subscription))
                        {
                            Trace("Create default subscription.");
                            subscription.Create();
                        }

                        // lookup namespace index if ExpandedNodeId format has been used and build NodeId identifier.
                        if (!string.IsNullOrEmpty(item.StartNodeId.NamespaceUri))
                        {
                            currentNodeId = NodeId.Create(item.StartNodeId.Identifier, item.StartNodeId.NamespaceUri, _namespaceTable);
                        }
                        else
                        {
                            currentNodeId = new NodeId((NodeId)item.StartNodeId);
                        }

                        // get the DisplayName for the node, otherwise use the nodeId
                        Node node = Session.ReadNode(currentNodeId);
                        item.DisplayName = node.DisplayName.Text ?? currentNodeId.ToString();

                        // add the new monitored item.
                        MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem)
                        {
                            StartNodeId      = currentNodeId,
                            AttributeId      = item.AttributeId,
                            DisplayName      = node.DisplayName.Text,
                            MonitoringMode   = item.MonitoringMode,
                            SamplingInterval = item.SamplingInterval,
                            QueueSize        = item.QueueSize,
                            DiscardOldest    = item.DiscardOldest
                        };
                        monitoredItem.Notification += item.Notification;
                        subscription.AddItem(monitoredItem);
                        subscription.ApplyChanges();
                        item.MonitoredItem = monitoredItem;
                        item.State         = MonitoredItemInfo.MonitoredItemState.Monitoreded;
                        item.EndpointUri   = EndpointUri;
                        Trace($"Created monitored item for node '{currentNodeId}' on endpoint '{EndpointUri.AbsoluteUri}'");
                    }
                    catch (Exception e) when(e.GetType() == typeof(ServiceResultException))
                    {
                        ServiceResultException sre = (ServiceResultException)e;

                        switch ((uint)sre.Result.StatusCode)
                        {
                        case StatusCodes.BadSessionIdInvalid:
                        {
                            Trace($"Session with Id {Session.SessionId} is no longer available on endpoint '{EndpointUri}'. Cleaning up.");
                            // clean up the session
                            _opcSessionSemaphore.Release();
                            Disconnect();
                            break;
                        }

                        case StatusCodes.BadNodeIdInvalid:
                        case StatusCodes.BadNodeIdUnknown:
                        {
                            Trace($"Failed to monitor node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'.");
                            Trace($"OPC UA ServiceResultException is '{sre.Result}'. Please check your publisher configuration for this node.");
                            break;
                        }

                        default:
                        {
                            Trace($"Unhandled OPC UA ServiceResultException '{sre.Result}' when monitoring node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'. Continue.");
                            break;
                        }
                        }
                    }
                    catch (Exception e)
                    {
                        Trace(e, $"Failed to monitor node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'");
                    }
                }

                // shutdown unused sessions.
                var unusedSessions = OpcSessions.Where(s => s.MonitoredItemsInfo.Count == 0);
                foreach (var unusedSession in unusedSessions)
                {
                    await unusedSession.Shutdown();

                    OpcSessions.Remove(unusedSession);
                }
            }
            finally
            {
                _opcSessionSemaphore.Release();
            }
        }
コード例 #23
0
        /// <summary>
        /// Method to remove the node from the subscription and stop publishing telemetry to IoTHub. Executes synchronously.
        /// </summary>
        private ServiceResult OnUnpublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
            {
                Trace("UnpublishNode: Invalid arguments!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            NodeId nodeId      = null;
            Uri    endpointUri = null;

            try
            {
                nodeId      = NodeId.Parse(inputArguments[0] as string);
                endpointUri = new Uri(inputArguments[1] as string);
            }
            catch (UriFormatException)
            {
                Trace($"UnpublishNode: The endpointUrl is invalid '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }

            // find the session and stop monitoring the node.
            try
            {
                if (ShutdownTokenSource.IsCancellationRequested)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                try
                {
                    OpcSessionsListSemaphore.WaitAsync();
                    opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUri.AbsoluteUri.Equals(endpointUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase));
                }
                catch
                {
                    opcSession = null;
                }
                finally
                {
                    OpcSessionsListSemaphore.Release();
                }


                ExpandedNodeId expandedNodeId = null;
                if (opcSession == null)
                {
                    // do nothing if there is no session for this endpoint.
                    Trace($"UnpublishNode: Session for endpoint '{endpointUri.OriginalString}' not found.");
                    return(ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for endpoint of node to unpublished not found!"));
                }
                else
                {
                    // check if node is already published
                    string namespaceUri = opcSession.GetNamespaceUri(nodeId.NamespaceIndex);
                    if (string.IsNullOrEmpty(namespaceUri))
                    {
                        return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"The namespace index of the node id is invalid."));
                    }
                    expandedNodeId = new ExpandedNodeId(nodeId.Identifier, nodeId.NamespaceIndex, namespaceUri, 0);
                    if (!OpcSession.IsNodePublished(nodeId, expandedNodeId, endpointUri))
                    {
                        Trace($"UnpublishNode: Node with id '{nodeId.Identifier.ToString()}' on endpoint '{endpointUri.OriginalString}' is not published.");
                        return(ServiceResult.Good);
                    }
                }

                // remove the node from the sessions monitored items list.
                opcSession.RequestMonitorItemRemoval(nodeId, expandedNodeId, ShutdownTokenSource.Token).Wait();
                Trace("UnpublishNode: Requested to stop monitoring of node.");
            }
            catch (Exception e)
            {
                Trace(e, $"UnpublishNode: Exception while trying to configure publishing node '{nodeId.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error unpublishing node: {e.Message}"));
            }
            return(ServiceResult.Good);
        }
コード例 #24
0
        /// <summary>
        /// Method to start monitoring a node and publish the data to IoTHub.
        /// </summary>
        private ServiceResult OnPublishNodeCall(ISystemContext context, MethodState method, IList <object> inputArguments, IList <object> outputArguments)
        {
            if (inputArguments[0] == null || inputArguments[1] == null)
            {
                Trace("PublishNode: Invalid Arguments when trying to publish a node.");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!"));
            }

            NodeToPublishConfig nodeToPublish;
            NodeId nodeId      = null;
            Uri    endpointUri = null;

            try
            {
                if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
                {
                    Trace($"PublishNode: Arguments (0 (nodeId), 1 (endpointUrl)) are not valid strings!");
                    return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!"));
                }
                nodeId        = NodeId.Parse(inputArguments[0] as string);
                endpointUri   = new Uri(inputArguments[1] as string);
                nodeToPublish = new NodeToPublishConfig(nodeId, endpointUri, OpcSamplingInterval, OpcPublishingInterval);
            }
            catch (UriFormatException)
            {
                Trace($"PublishNode: The EndpointUri has an invalid format '{inputArguments[1] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!"));
            }
            catch (Exception e)
            {
                Trace(e, $"PublishNode: The NodeId has an invalid format '{inputArguments[0] as string}'!");
                return(ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in 'ns=' syntax as first argument!"));
            }

            // find/create a session to the endpoint URL and start monitoring the node.
            try
            {
                if (PublisherShutdownInProgress)
                {
                    return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress."));
                }

                // find the session we need to monitor the node
                OpcSession opcSession = null;
                try
                {
                    OpcSessionsSemaphore.Wait();
                    opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUri == nodeToPublish.EndpointUri);

                    // add a new session.
                    if (opcSession == null)
                    {
                        // create new session info.
                        opcSession = new OpcSession(nodeToPublish.EndpointUri, OpcSessionCreationTimeout);
                        OpcSessions.Add(opcSession);
                        Trace($"PublishNode: No matching session found for endpoint '{nodeToPublish.EndpointUri.OriginalString}'. Requested to create a new one.");
                    }
                    else
                    {
                        Trace($"PublishNode: Session found for endpoint '{nodeToPublish.EndpointUri.OriginalString}'");
                    }

                    // add the node info to the subscription with the default publishing interval
                    opcSession.AddNodeForMonitoring(OpcPublishingInterval, OpcSamplingInterval, nodeToPublish.NodeId);
                    Trace($"PublishNode: Requested to monitor item with NodeId '{nodeToPublish.NodeId.ToString()}' (PublishingInterval: {OpcPublishingInterval}, SamplingInterval: {OpcSamplingInterval})");
                }
                finally
                {
                    OpcSessionsSemaphore.Release();
                }

                // update our data
                try
                {
                    PublishDataSemaphore.Wait();
                    PublishConfig.Add(nodeToPublish);

                    // add it also to the publish file
                    var publisherConfigFileEntry = new PublisherConfigFileEntry()
                    {
                        EndpointUri = endpointUri,
                        NodeId      = nodeId
                    };
                    PublisherConfigFileEntries.Add(publisherConfigFileEntry);
                    File.WriteAllText(NodesToPublishAbsFilename, JsonConvert.SerializeObject(PublisherConfigFileEntries));
                }
                finally
                {
                    PublishDataSemaphore.Release();
                }
                return(ServiceResult.Good);
            }
            catch (Exception e)
            {
                Trace(e, $"PublishNode: Exception while trying to configure publishing node '{nodeToPublish.NodeId.ToString()}'");
                return(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}"));
            }
        }
コード例 #25
0
        /// <summary>
        /// Create the publisher data structures to manage OPC sessions, subscriptions and monitored items.
        /// </summary>
        /// <returns></returns>
        public static async Task <bool> CreateOpcPublishingDataAsync()
        {
            // create a list to manage sessions, subscriptions and monitored items.
            try
            {
                await PublisherNodeConfigurationSemaphore.WaitAsync().ConfigureAwait(false);

                await OpcSessionsListSemaphore.WaitAsync().ConfigureAwait(false);

                var uniqueEndpointUrls = _nodePublishingConfiguration.Select(n => n.EndpointUrl).Distinct();
                foreach (var endpointUrl in uniqueEndpointUrls)
                {
                    // create new session info.
                    OpcSession opcSession = new OpcSession(endpointUrl, _nodePublishingConfiguration.Where(n => n.EndpointUrl == endpointUrl).First().UseSecurity, OpcSessionCreationTimeout);

                    // create a subscription for each distinct publishing inverval
                    var nodesDistinctPublishingInterval = _nodePublishingConfiguration.Where(n => n.EndpointUrl.Equals(endpointUrl, StringComparison.OrdinalIgnoreCase)).Select(c => c.OpcPublishingInterval).Distinct();
                    foreach (var nodeDistinctPublishingInterval in nodesDistinctPublishingInterval)
                    {
                        // create a subscription for the publishing interval and add it to the session.
                        OpcSubscription opcSubscription = new OpcSubscription(nodeDistinctPublishingInterval);

                        // add all nodes with this OPC publishing interval to this subscription.
                        var nodesWithSamePublishingInterval = _nodePublishingConfiguration.Where(n => n.EndpointUrl.Equals(endpointUrl, StringComparison.OrdinalIgnoreCase)).Where(n => n.OpcPublishingInterval == nodeDistinctPublishingInterval);
                        foreach (var nodeInfo in nodesWithSamePublishingInterval)
                        {
                            // differentiate if NodeId or ExpandedNodeId format is used
                            if (nodeInfo.ExpandedNodeId != null)
                            {
                                // create a monitored item for the node, we do not have the namespace index without a connected session.
                                // so request a namespace update.
                                OpcMonitoredItem opcMonitoredItem = new OpcMonitoredItem(nodeInfo.ExpandedNodeId, opcSession.EndpointUrl, nodeInfo.OpcSamplingInterval, nodeInfo.DisplayName);
                                opcSubscription.OpcMonitoredItems.Add(opcMonitoredItem);
                                Interlocked.Increment(ref NodeConfigVersion);
                            }
                            else if (nodeInfo.NodeId != null)
                            {
                                // create a monitored item for the node with the configured or default sampling interval
                                OpcMonitoredItem opcMonitoredItem = new OpcMonitoredItem(nodeInfo.NodeId, opcSession.EndpointUrl, nodeInfo.OpcSamplingInterval, nodeInfo.DisplayName);
                                opcSubscription.OpcMonitoredItems.Add(opcMonitoredItem);
                                Interlocked.Increment(ref NodeConfigVersion);
                            }
                            else
                            {
                                Logger.Error($"Node {nodeInfo.OriginalId} has an invalid format. Skipping...");
                            }
                        }

                        // add subscription to session.
                        opcSession.OpcSubscriptions.Add(opcSubscription);
                    }

                    // add session.
                    OpcSessions.Add(opcSession);
                }
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "Creation of the internal OPC data managment structures failed. Exiting...");
                return(false);
            }
            finally
            {
                OpcSessionsListSemaphore.Release();
                PublisherNodeConfigurationSemaphore.Release();
            }
            return(true);
        }
コード例 #26
0
        /// <summary>
        /// This task is executed regularily and ensures:
        /// - disconnected sessions are reconnected.
        /// - monitored nodes are no longer monitored if requested to do so.
        /// - monitoring for a node starts if it is required.
        /// - unused subscriptions (without any nodes to monitor) are removed.
        /// - sessions with out subscriptions are removed.
        /// </summary>
        /// <returns></returns>
        public async Task ConnectAndMonitor()
        {
            await _opcSessionSemaphore.WaitAsync();

            try
            {
                // if the session is disconnected, create one.
                if (State == SessionState.Disconnected)
                {
                    Trace($"Connect and monitor session and nodes on endpoint '{EndpointUri.AbsoluteUri}'.");
                    try
                    {
                        // release the session to not block for high network timeouts.
                        _opcSessionSemaphore.Release();

                        // start connecting
                        EndpointDescription selectedEndpoint   = CoreClientUtils.SelectEndpoint(EndpointUri.AbsoluteUri, true);
                        ConfiguredEndpoint  configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(OpcConfiguration));
                        uint timeout = SessionTimeout * ((UnsuccessfulConnectionCount >= OpcSessionCreationBackoffMax) ? OpcSessionCreationBackoffMax : UnsuccessfulConnectionCount + 1);
                        Trace($"Create session for endpoint URI '{EndpointUri.AbsoluteUri}' with timeout of {timeout} ms.");
                        Session = await Session.Create(
                            OpcConfiguration,
                            configuredEndpoint,
                            true,
                            false,
                            OpcConfiguration.ApplicationName,
                            timeout,
                            new UserIdentity(new AnonymousIdentityToken()),
                            null);

                        if (Session != null)
                        {
                            Trace($"Session successfully created with Id {Session.SessionId}.");
                            if (!selectedEndpoint.EndpointUrl.Equals(configuredEndpoint.EndpointUrl.AbsoluteUri))
                            {
                                Trace($"the Server has updated the EndpointUrl to '{selectedEndpoint.EndpointUrl}'");
                            }

                            // init object state and install keep alive
                            UnsuccessfulConnectionCount = 0;
                            Session.KeepAliveInterval   = OpcKeepAliveIntervalInSec * 1000;
                            Session.KeepAlive          += StandardClient_KeepAlive;

                            // fetch the namespace array and cache it. it will not change as long the session exists.
                            DataValue namespaceArrayNodeValue = Session.ReadValue(VariableIds.Server_NamespaceArray);
                            _namespaceTable.Update(namespaceArrayNodeValue.GetValue <string[]>(null));

                            // show the available namespaces
                            Trace($"The session to endpoint '{selectedEndpoint.EndpointUrl}' has {_namespaceTable.Count} entries in its namespace array:");
                            int i = 0;
                            foreach (var ns in _namespaceTable.ToArray())
                            {
                                Trace($"Namespace index {i++}: {ns}");
                            }

                            // fetch the minimum supported item sampling interval from the server.
                            DataValue minSupportedSamplingInterval = Session.ReadValue(VariableIds.Server_ServerCapabilities_MinSupportedSampleRate);
                            _minSupportedSamplingInterval = minSupportedSamplingInterval.GetValue(0);
                            Trace($"The server on endpoint '{selectedEndpoint.EndpointUrl}' supports a minimal sampling interval of {_minSupportedSamplingInterval} ms.");
                        }
                    }
                    catch (Exception e)
                    {
                        Trace(e, $"Session creation to endpoint '{EndpointUri.AbsoluteUri}' failed {++UnsuccessfulConnectionCount} time(s). Please verify if server is up and Publisher configuration is correct.");
                        State   = SessionState.Disconnected;
                        Session = null;
                        return;
                    }
                    finally
                    {
                        await _opcSessionSemaphore.WaitAsync();

                        if (Session != null)
                        {
                            State = SessionState.Connected;
                        }
                    }
                }

                // stop monitoring of nodes if requested and remove them from the monitored items list.
                foreach (var opcSubscription in OpcSubscriptions)
                {
                    var itemsToRemove = opcSubscription.OpcMonitoredItems.Where(i => i.State == OpcMonitoredItem.OpcMonitoredItemState.StopMonitoring);
                    if (itemsToRemove.Any())
                    {
                        Trace($"Remove nodes in subscription with id {opcSubscription.Subscription.Id} on endpoint '{EndpointUri.AbsoluteUri}'");
                        opcSubscription.Subscription.RemoveItems(itemsToRemove.Select(i => i.MonitoredItem));
                    }
                }

                // ensure all nodes in all subscriptions of this session are monitored.
                foreach (var opcSubscription in OpcSubscriptions)
                {
                    // create the subscription, if it is not yet there.
                    if (opcSubscription.Subscription == null)
                    {
                        int revisedPublishingInterval;
                        opcSubscription.Subscription       = CreateSubscription(opcSubscription.RequestedPublishingInterval, out revisedPublishingInterval);
                        opcSubscription.PublishingInterval = revisedPublishingInterval;
                        Trace($"Create subscription on endpoint '{EndpointUri.AbsoluteUri}' requested OPC publishing interval is {opcSubscription.RequestedPublishingInterval} ms. (revised: {revisedPublishingInterval} ms)");
                    }

                    // process all unmonitored items.
                    var unmonitoredItems = opcSubscription.OpcMonitoredItems.Where(i => i.State == OpcMonitoredItem.OpcMonitoredItemState.Unmonitored);

                    foreach (var item in unmonitoredItems)
                    {
                        // if the session is disconnected, we stop trying and wait for the next cycle
                        if (State == SessionState.Disconnected)
                        {
                            break;
                        }

                        Trace($"Start monitoring nodes on endpoint '{EndpointUri.AbsoluteUri}'.");
                        NodeId currentNodeId;
                        try
                        {
                            // lookup namespace index if ExpandedNodeId format has been used and build NodeId identifier.
                            if (!string.IsNullOrEmpty(item.StartNodeId.NamespaceUri))
                            {
                                currentNodeId = NodeId.Create(item.StartNodeId.Identifier, item.StartNodeId.NamespaceUri, _namespaceTable);
                            }
                            else
                            {
                                currentNodeId = new NodeId((NodeId)item.StartNodeId);
                            }

                            // get the DisplayName for the node, otherwise use the nodeId
                            Node node = Session.ReadNode(currentNodeId);
                            item.DisplayName = node.DisplayName.Text ?? currentNodeId.ToString();

                            // add the new monitored item.
                            MonitoredItem monitoredItem = new MonitoredItem()
                            {
                                StartNodeId      = currentNodeId,
                                AttributeId      = item.AttributeId,
                                DisplayName      = node.DisplayName.Text,
                                MonitoringMode   = item.MonitoringMode,
                                SamplingInterval = item.RequestedSamplingInterval,
                                QueueSize        = item.QueueSize,
                                DiscardOldest    = item.DiscardOldest
                            };
                            monitoredItem.Notification += item.Notification;
                            opcSubscription.Subscription.AddItem(monitoredItem);
                            opcSubscription.Subscription.SetPublishingMode(true);
                            opcSubscription.Subscription.ApplyChanges();
                            item.MonitoredItem = monitoredItem;
                            item.State         = OpcMonitoredItem.OpcMonitoredItemState.Monitoreded;
                            item.EndpointUri   = EndpointUri;
                            Trace($"Created monitored item for node '{currentNodeId}' in subscription with id {opcSubscription.Subscription.Id} on endpoint '{EndpointUri.AbsoluteUri}'");
                            if (item.RequestedSamplingInterval != monitoredItem.SamplingInterval)
                            {
                                Trace($"Sampling interval: requested: {item.RequestedSamplingInterval}; revised: {monitoredItem.SamplingInterval}");
                                item.SamplingInterval = monitoredItem.SamplingInterval;
                            }
                        }
                        catch (Exception e) when(e.GetType() == typeof(ServiceResultException))
                        {
                            ServiceResultException sre = (ServiceResultException)e;

                            switch ((uint)sre.Result.StatusCode)
                            {
                            case StatusCodes.BadSessionIdInvalid:
                            {
                                Trace($"Session with Id {Session.SessionId} is no longer available on endpoint '{EndpointUri}'. Cleaning up.");
                                // clean up the session
                                _opcSessionSemaphore.Release();
                                await Disconnect();

                                break;
                            }

                            case StatusCodes.BadNodeIdInvalid:
                            case StatusCodes.BadNodeIdUnknown:
                            {
                                Trace($"Failed to monitor node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'.");
                                Trace($"OPC UA ServiceResultException is '{sre.Result}'. Please check your publisher configuration for this node.");
                                break;
                            }

                            default:
                            {
                                Trace($"Unhandled OPC UA ServiceResultException '{sre.Result}' when monitoring node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'. Continue.");
                                break;
                            }
                            }
                        }
                        catch (Exception e)
                        {
                            Trace(e, $"Failed to monitor node '{item.StartNodeId.Identifier}' on endpoint '{EndpointUri}'");
                        }
                    }
                }

                // remove unused subscriptions.
                foreach (var opcSubscription in OpcSubscriptions)
                {
                    if (opcSubscription.OpcMonitoredItems.Count == 0)
                    {
                        Trace($"Subscription with id {opcSubscription.Subscription.Id} on endpoint '{EndpointUri}' is not used and will be deleted.");
                        Session.RemoveSubscription(opcSubscription.Subscription);
                        opcSubscription.Subscription = null;
                    }
                }

                // shutdown unused sessions.
                try
                {
                    await OpcSessionsSemaphore.WaitAsync();

                    var unusedSessions = OpcSessions.Where(s => s.OpcSubscriptions.Count == 0);
                    foreach (var unusedSession in unusedSessions)
                    {
                        OpcSessions.Remove(unusedSession);
                        await unusedSession.Shutdown();
                    }

                    // Shutdown everything on shutdown.
                    if (PublisherShutdownInProgress == true)
                    {
                        var allSessions = OpcSessions;
                        foreach (var session in allSessions)
                        {
                            OpcSessions.Remove(session);
                            await session.Shutdown();
                        }
                    }
                }
                finally
                {
                    OpcSessionsSemaphore.Release();
                }
            }
            catch (Exception e)
            {
                Trace(e, "Error during ConnectAndMonitor.");
            }
            finally
            {
                _opcSessionSemaphore.Release();
            }
        }