Пример #1
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.");
            }
        }
Пример #2
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);
        }
Пример #3
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}"));
            }
        }
Пример #4
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();
            }
        }