/// <summary> /// Adds a event node to be monitored. If there is no subscription with the requested publishing interval, /// one is created. /// </summary> public async Task <HttpStatusCode> AddEventNodeForMonitoringAsync(NodeId nodeId, ExpandedNodeId expandedNodeId, int?opcPublishingInterval, int?opcSamplingInterval, string displayName, int?heartbeatInterval, bool?skipFirst, CancellationToken ct, IotCentralItemPublishMode?iotCentralItemPublishMode, PublishNodesMethodRequestModel publishEventsMethodData) { string logPrefix = "AddEventNodeForMonitoringAsync:"; bool sessionLocked = false; try { sessionLocked = await LockSessionAsync().ConfigureAwait(false); if (!sessionLocked || ct.IsCancellationRequested) { return(HttpStatusCode.Gone); } // check if there is already a subscription with the same publishing interval, which can be used to monitor the node int opcPublishingIntervalForNode = opcPublishingInterval ?? OpcPublishingIntervalDefault; IOpcSubscription opcEventSubscription = OpcEventSubscriptions.FirstOrDefault(s => s.RequestedPublishingInterval == opcPublishingIntervalForNode); // if there was none found, create one if (opcEventSubscription == null) { if (opcPublishingInterval == null) { Logger.Information($"{logPrefix} No matching subscription with default publishing interval found."); Logger.Information($"Create a new subscription with a default publishing interval."); } else { Logger.Information($"{logPrefix} No matching subscription with publishing interval of {opcPublishingInterval} found."); Logger.Information($"Create a new subscription with a publishing interval of {opcPublishingInterval}."); } opcEventSubscription = new OpcSubscription(opcPublishingInterval); OpcEventSubscriptions.Add(opcEventSubscription); } // create objects for publish check ExpandedNodeId expandedNodeIdCheck = expandedNodeId; NodeId nodeIdCheck = nodeId; if (State == SessionState.Connected) { if (expandedNodeId == null) { string namespaceUri = _namespaceTable.ToArray().ElementAtOrDefault(nodeId.NamespaceIndex); expandedNodeIdCheck = new ExpandedNodeId(nodeId.Identifier, nodeId.NamespaceIndex, namespaceUri, 0); } if (nodeId == null) { nodeIdCheck = new NodeId(expandedNodeId.Identifier, (ushort)(_namespaceTable.GetIndex(expandedNodeId.NamespaceUri))); } } // if it is already published, we do nothing, else we create a new monitored event item if (!IsEventNodePublishedInSessionInternal(nodeIdCheck, expandedNodeIdCheck)) { OpcMonitoredItem opcMonitoredItem = null; EncryptedNetworkCredential encryptedCredentials = null; 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)}'."); } encryptedCredentials = await EncryptedNetworkCredential.FromPlainCredential(publishEventsMethodData.UserName, publishEventsMethodData.Password); } // add a new item to monitor if (expandedNodeId == null) { opcMonitoredItem = new OpcMonitoredItem(new EventConfigurationModel(publishEventsMethodData.EndpointId, publishEventsMethodData.EndpointName, publishEventsMethodData.EndpointUrl, publishEventsMethodData.UseSecurity, publishEventsMethodData.OpcAuthenticationMode ?? OpcAuthenticationMode.Anonymous, encryptedCredentials, publishEventsMethodData.OpcEvents[0].Id, publishEventsMethodData.OpcEvents[0].DisplayName, publishEventsMethodData.OpcEvents[0].SelectClauses, publishEventsMethodData.OpcEvents[0].WhereClause, publishEventsMethodData.OpcEvents[0].IotCentralEventPublishMode), EndpointId, EndpointUrl);; } else { opcMonitoredItem = new OpcMonitoredItem(new EventConfigurationModel(publishEventsMethodData.EndpointId, publishEventsMethodData.EndpointName, publishEventsMethodData.EndpointUrl, publishEventsMethodData.UseSecurity, publishEventsMethodData.OpcAuthenticationMode ?? OpcAuthenticationMode.Anonymous, encryptedCredentials, expandedNodeId.ToString(), publishEventsMethodData.OpcEvents[0].DisplayName, publishEventsMethodData.OpcEvents[0].SelectClauses, publishEventsMethodData.OpcEvents[0].WhereClause, publishEventsMethodData.OpcEvents[0].IotCentralEventPublishMode), EndpointId, EndpointUrl); } opcEventSubscription.OpcMonitoredItems.Add(opcMonitoredItem); Interlocked.Increment(ref NodeConfigVersion); Logger.Debug($"{logPrefix} Added event item with nodeId '{(expandedNodeId == null ? nodeId.ToString() : expandedNodeId.ToString())}' for monitoring."); // trigger the actual OPC communication with the server to be done ConnectAndMonitorSession.Set(); return(HttpStatusCode.Accepted); } else { Logger.Debug($"{logPrefix} Node with Id '{(expandedNodeId == null ? nodeId.ToString() : expandedNodeId.ToString())}' is already monitored."); } } catch (Exception e) { Logger.Error(e, $"{logPrefix} Exception while trying to add node '{(expandedNodeId == null ? nodeId.ToString() : expandedNodeId.ToString())}' for monitoring."); return(HttpStatusCode.InternalServerError); } finally { if (sessionLocked) { ReleaseSession(); } } return(HttpStatusCode.OK); }
/// <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); }