/// <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 NodeConfiguration.OpcSessionsListSemaphore.Wait(); if (ShutdownTokenSource.IsCancellationRequested) { return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress.")); } // find the session we need to monitor the node OpcUaSessionManager opcSession = NodeConfiguration.OpcSessions.FirstOrDefault(s => s.EndpointUrl.Equals(endpointUri.OriginalString, StringComparison.OrdinalIgnoreCase)); // add a new session. if (opcSession == null) { // create new session info. opcSession = new OpcUaSessionManager(endpointUri.OriginalString, true, OpcSessionCreationTimeout, OpcAuthenticationMode.Anonymous, null); NodeConfiguration.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 { NodeConfiguration.OpcSessionsListSemaphore.Release(); } if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted) { return(ServiceResult.Good); } return(ServiceResult.Create(StatusCodes.Bad, "Can not start monitoring node! Reason unknown.")); }
/// <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 { NodeConfiguration.OpcSessionsListSemaphore.Wait(); if (ShutdownTokenSource.IsCancellationRequested) { return(ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress.")); } // find the session we need to monitor the node OpcUaSessionManager opcSession = null; try { opcSession = NodeConfiguration.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 { NodeConfiguration.OpcSessionsListSemaphore.Release(); } return(statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted ? ServiceResult.Good : ServiceResult.Create(StatusCodes.Bad, "Can not stop monitoring node!")); }
/// <summary> /// Create the publisher data structures to manage OPC sessions, subscriptions and monitored items. /// </summary> /// <returns></returns> public 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) { var currentNodePublishingConfiguration = _nodePublishingConfiguration.First(n => n.EndpointUrl == endpointUrl); EncryptedNetworkCredential encryptedAuthCredential = null; if (currentNodePublishingConfiguration.OpcAuthenticationMode == OpcAuthenticationMode.UsernamePassword) { if (currentNodePublishingConfiguration.EncryptedAuthCredential == null) { throw new NullReferenceException($"Could not retrieve credentials to authenticate to the server. Please check if 'OpcAuthenticationUsername' and 'OpcAuthenticationPassword' are set in configuration."); } encryptedAuthCredential = currentNodePublishingConfiguration.EncryptedAuthCredential; } // create new session info. OpcUaSessionManager opcSession = new OpcUaSessionManager(endpointUrl, currentNodePublishingConfiguration.UseSecurity, OpcSessionCreationTimeout, currentNodePublishingConfiguration.OpcAuthenticationMode, encryptedAuthCredential); // 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. OpcUaSubscriptionManager opcSubscription = new OpcUaSubscriptionManager(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. OpcUaMonitoredItemManager opcMonitoredItem = new OpcUaMonitoredItemManager(nodeInfo.ExpandedNodeId, opcSession.EndpointUrl, nodeInfo.OpcSamplingInterval, nodeInfo.DisplayName, nodeInfo.HeartbeatInterval, nodeInfo.SkipFirst); 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 OpcUaMonitoredItemManager opcMonitoredItem = new OpcUaMonitoredItemManager(nodeInfo.NodeId, opcSession.EndpointUrl, nodeInfo.OpcSamplingInterval, nodeInfo.DisplayName, nodeInfo.HeartbeatInterval, nodeInfo.SkipFirst); 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); }