/// <summary> /// Handle publish event node method call. /// </summary> public virtual async Task <MethodResponse> HandlePublishEventsMethodAsync(MethodRequest methodRequest, object userContext) { var logPrefix = "HandlePublishEventsMethodAsync:"; var useSecurity = true; Guid endpointId = Guid.Empty; string endpointName = null; Uri endpointUri = null; OpcAuthenticationMode? desiredAuthenticationMode = null; EncryptedNetworkCredential desiredEncryptedCredential = null; PublishNodesMethodRequestModel publishEventsMethodData = null; PublishNodesMethodResponseModel publishedEventMethodResponse = null; var statusCode = HttpStatusCode.OK; var statusResponse = new List <string>(); string statusMessage; try { _logger.Debug($"{logPrefix} called"); publishEventsMethodData = JsonConvert.DeserializeObject <PublishNodesMethodRequestModel>(methodRequest.DataAsJson); endpointId = publishEventsMethodData.EndpointId == null ? Guid.Empty : new Guid(publishEventsMethodData.EndpointId); endpointName = publishEventsMethodData.EndpointName; endpointUri = new Uri(publishEventsMethodData.EndpointUrl); useSecurity = publishEventsMethodData.UseSecurity; if (publishEventsMethodData.OpcAuthenticationMode == OpcAuthenticationMode.UsernamePassword) { if (string.IsNullOrWhiteSpace(publishEventsMethodData.UserName) && string.IsNullOrWhiteSpace(publishEventsMethodData.Password)) { throw new ArgumentException($"If {nameof(publishEventsMethodData.OpcAuthenticationMode)} is set to '{OpcAuthenticationMode.UsernamePassword}', you have to specify '{nameof(publishEventsMethodData.UserName)}' and/or '{nameof(publishEventsMethodData.Password)}'."); } desiredAuthenticationMode = OpcAuthenticationMode.UsernamePassword; desiredEncryptedCredential = await EncryptedNetworkCredential.FromPlainCredential(publishEventsMethodData.UserName, publishEventsMethodData.Password); } if (publishEventsMethodData.OpcEvents.Count != 1) { statusMessage = $"You can only configure one Event simultaneously, but you trying to configure {publishEventsMethodData.OpcEvents.Count + 1} events"; _logger.Error($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.NotAcceptable; } } catch (UriFormatException e) { statusMessage = $"Exception ({e.Message}) while parsing EndpointUrl '{publishEventsMethodData?.EndpointUrl}'"; _logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.NotAcceptable; } catch (FormatException e) { statusMessage = $"Exception ({e.Message}) while parsing EndpointId '{publishEventsMethodData?.EndpointId}'"; _logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.NotAcceptable; } catch (Exception e) { statusMessage = $"Exception ({e.Message}) while deserializing message payload"; _logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } if (statusCode == HttpStatusCode.OK) { // find/create a session to the endpoint URL and start monitoring the node. try { // lock the publishing configuration till we are done await NodeConfiguration.OpcSessionsListSemaphore.WaitAsync(_shutdownToken).ConfigureAwait(false); if (ShutdownTokenSource.IsCancellationRequested) { statusMessage = $"Publisher is in shutdown"; _logger.Warning($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.Gone; } else { IOpcSession opcSession = null; /* we create new sessions in two cases * 1. For new endpoints * 2. For existing endpoints which do not have a OpcSession configured: * this happens if for an existing endpoint all monitored items, commands and events are removed (unused sessions are removed). */ var isNewEndpoint = endpointId == Guid.Empty; var isExistingEndpointWithoutSession = !isNewEndpoint && NodeConfiguration.OpcSessions.FirstOrDefault(s => s.EndpointId.Equals(endpointId)) == null; if (isNewEndpoint || isExistingEndpointWithoutSession) { // if the no OpcAuthenticationMode is specified, we create the new session with "Anonymous" auth if (!desiredAuthenticationMode.HasValue) { desiredAuthenticationMode = OpcAuthenticationMode.Anonymous; } if (isNewEndpoint) { endpointId = Guid.NewGuid(); } // create new session info. opcSession = new OpcSession(endpointId, endpointName, endpointUri.OriginalString, useSecurity, OpcSessionCreationTimeout, desiredAuthenticationMode.Value, desiredEncryptedCredential); NodeConfiguration.OpcSessions.Add(opcSession); Logger.Information($"{logPrefix} No matching session found for endpoint '{endpointUri.OriginalString}'. Requested to create a new one."); } else { // find the session we need to monitor the node opcSession = NodeConfiguration.OpcSessions.FirstOrDefault(s => s.EndpointUrl.Equals(endpointUri?.OriginalString, StringComparison.OrdinalIgnoreCase)); // a session already exists, so we check, if we need to change authentication settings. This is only true, if the payload contains an OpcAuthenticationMode-Property if (desiredAuthenticationMode.HasValue) { bool reconnectRequired = false; if (opcSession.OpcAuthenticationMode != desiredAuthenticationMode.Value) { opcSession.OpcAuthenticationMode = desiredAuthenticationMode.Value; reconnectRequired = true; } if (opcSession.EncryptedAuthCredential != desiredEncryptedCredential) { opcSession.EncryptedAuthCredential = desiredEncryptedCredential; reconnectRequired = true; } if (reconnectRequired) { await opcSession.Reconnect(); } } } // process all nodes if (publishEventsMethodData?.OpcEvents != null) { foreach (var eventNode in publishEventsMethodData?.OpcEvents) { NodeId nodeId = null; ExpandedNodeId expandedNodeId = null; bool isNodeIdFormat; try { if (eventNode.Id.Contains("nsu=", StringComparison.InvariantCulture)) { expandedNodeId = ExpandedNodeId.Parse(eventNode.Id); isNodeIdFormat = false; } else { nodeId = NodeId.Parse(eventNode.Id); isNodeIdFormat = true; } } catch (Exception e) { statusMessage = $"Exception in ({e.Message}) while formatting node '{eventNode.Id}'!"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.NotAcceptable; continue; } try { HttpStatusCode nodeStatusCode; if (isNodeIdFormat) { // add the event node info to the subscription with the default publishing interval, execute synchronously Logger.Debug( $"{logPrefix} Request to monitor eventNode with NodeId '{eventNode.Id}'"); nodeStatusCode = await opcSession.AddEventNodeForMonitoringAsync(nodeId, null, 5000, 2000, eventNode.DisplayName, null, null, ShutdownTokenSource.Token, null, publishEventsMethodData) .ConfigureAwait(false); } else { // add the event node info to the subscription with the default publishing interval, execute synchronously Logger.Debug( $"{logPrefix} Request to monitor eventNode with ExpandedNodeId '{eventNode.Id}'"); nodeStatusCode = await opcSession.AddEventNodeForMonitoringAsync(null, expandedNodeId, 5000, 2000, eventNode.DisplayName, null, null, ShutdownTokenSource.Token, null, publishEventsMethodData) .ConfigureAwait(false); } // check and store a result message in case of an error switch (nodeStatusCode) { case HttpStatusCode.OK: statusMessage = $"'{eventNode.Id}': already monitored"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); break; case HttpStatusCode.Accepted: statusMessage = $"'{eventNode.Id}': added"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); break; case HttpStatusCode.Gone: statusMessage = $"'{eventNode.Id}': session to endpoint does not exist anymore"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.Gone; break; case HttpStatusCode.InternalServerError: statusMessage = $"'{eventNode.Id}': error while trying to configure"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; break; } } catch (Exception e) { statusMessage = $"Exception ({e.Message}) while trying to configure publishing node '{eventNode.Id}'"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } } } else { statusMessage = $"There are no EventConfigurations provided with the current call, provided JSON Data was: {methodRequest.DataAsJson}"; Logger.Error($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.BadRequest; } } } catch (AggregateException e) { foreach (var ex in e.InnerExceptions) { Logger.Error(ex, $"{logPrefix} Exception"); } statusMessage = $"EndpointUrl: '{publishEventsMethodData.EndpointUrl}': exception ({e.Message}) while trying to publish"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } catch (Exception e) { statusMessage = $"EndpointUrl: '{publishEventsMethodData.EndpointUrl}': exception ({e.Message}) while trying to publish"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } finally { NodeConfiguration.OpcSessionsListSemaphore.Release(); } } // build response publishedEventMethodResponse = new PublishNodesMethodResponseModel(endpointId.ToString()); string resultString = statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted ? JsonConvert.SerializeObject(publishedEventMethodResponse): JsonConvert.SerializeObject(statusResponse); byte[] result = Encoding.UTF8.GetBytes(resultString); if (result.Length > MaxResponsePayloadLength) { Logger.Error($"{logPrefix} Response size is too long"); Array.Resize(ref result, result.Length > MaxResponsePayloadLength ? MaxResponsePayloadLength : result.Length); } MethodResponse methodResponse = new MethodResponse(result, (int)statusCode); Logger.Information($"{logPrefix} completed with result {statusCode.ToString()}"); return(methodResponse); }
/// <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 IOpcSession opcSession = null; opcSession = NodeConfiguration.OpcSessions.FirstOrDefault(s => s.EndpointUrl.Equals(endpointUri.OriginalString, StringComparison.OrdinalIgnoreCase)); // add a new session. if (opcSession == null) { // create new session info. // endpointName is null here. Since we currently do not have to support configuration of OPC Publisher by OPC server this is ok for now. opcSession = new OpcSession(Guid.NewGuid(), null, 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."); } // Add a node. The key for the new node is the node id / expanded node id 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, nodeId.ToString(), null, null, null, ShutdownTokenSource.Token, null).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, expandedNodeId.ToString(), null, null, null, ShutdownTokenSource.Token, null).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 IOpcSession 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!")); }
private async Task <(HttpStatusCode statusCode, List <string> statusResponse)> UnpublishEventsAsync(Guid endpointId, IEnumerable <OpcEventOnEndpointModel> opcEvents) { string logPrefix = "UnpublishEventsAsync:"; IOpcSession opcSession = null; HttpStatusCode nodeStatusCode = HttpStatusCode.InternalServerError; HttpStatusCode statusCode = HttpStatusCode.OK; List <string> statusResponse = new List <string>(); string statusMessage = string.Empty; if (statusCode == HttpStatusCode.OK) { try { await NodeConfiguration.OpcSessionsListSemaphore.WaitAsync().ConfigureAwait(false); if (ShutdownTokenSource.IsCancellationRequested) { statusMessage = $"Publisher is in shutdown"; Logger.Error($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.Gone; } else { // find the session we need to monitor the node try { opcSession = NodeConfiguration.OpcSessions.FirstOrDefault(s => s.EndpointId.Equals(endpointId)); } catch { opcSession = null; } if (opcSession == null) { // do nothing if there is no session for this endpoint. statusMessage = $"Session for endpoint '{endpointId.ToString()}' not found."; Logger.Error($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.Gone; } else { // unpublish all nodes on one endpoint or nodes requested if (opcEvents != null && opcEvents.Any()) { foreach (var opcEvent in opcEvents) { try { // stop monitoring the node, execute synchronously Logger.Information($"{logPrefix} Request to stop monitoring item with id '{opcEvent.Id.ToString()}')"); nodeStatusCode = await opcSession.RequestEventNodeRemovalAsync(opcEvent.Id, ShutdownTokenSource.Token).ConfigureAwait(false); // check and store a result message in case of an error switch (nodeStatusCode) { case HttpStatusCode.OK: statusMessage = $"Id '{opcEvent.Id}': was not configured"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); break; case HttpStatusCode.Accepted: statusMessage = $"Id '{opcEvent.Id}': tagged for removal"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); break; case HttpStatusCode.Gone: statusMessage = $"Id '{opcEvent.Id}': session to endpoint does not exist anymore"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.Gone; break; case HttpStatusCode.InternalServerError: statusMessage = $"Id '{opcEvent.Id}': error while trying to remove"; Logger.Debug($"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; break; } } catch (Exception e) { statusMessage = $"Exception ({e.Message}) while trying to tag node '{opcEvent.Id}' for removal"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } } } } } } catch (AggregateException e) { foreach (Exception ex in e.InnerExceptions) { Logger.Error(ex, $"{logPrefix} Exception"); } statusMessage = $"EndpointUrl: '{endpointId}': exception while trying to unpublish"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } catch (Exception e) { statusMessage = $"EndpointUrl: '{endpointId}': exception ({e.Message}) while trying to unpublish"; Logger.Error(e, $"{logPrefix} {statusMessage}"); statusResponse.Add(statusMessage); statusCode = HttpStatusCode.InternalServerError; } finally { NodeConfiguration.OpcSessionsListSemaphore.Release(); } } // wait until the session is saved if (opcSession != null) { await opcSession.ConnectAndMonitorAsync().ConfigureAwait(false); } return(statusCode, statusResponse); }