/// <summary> /// Query for latest event of the given node and /// given OPC UA server from the given time and date /// </summary> /// <param name="endTime">Time to start from querying in the past</param> /// <param name="appUri">The OPC UA server application Uri</param> /// <param name="nodeId">The node id in the OPC UA server namespace</param> public async Task <double> GetLatestQuery(DateTime endTime, string appUri, string nodeId) { try { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task <IEnumerable <IEvent> > lastTask = GetLatestEvent(endTime, appUri, nodeId); IEnumerable <IEvent> lastEvent = await lastTask; stopwatch.Stop(); RDXTrace.TraceInformation("GetLatestQuery took {0} ms", stopwatch.ElapsedMilliseconds); double last; try { last = GetValueOfProperty <double>(lastEvent.First <IEvent>(), OpcMonitoredItemValue); } catch { last = GetValueOfProperty <long>(lastEvent.First <IEvent>(), OpcMonitoredItemValue); } return(last); } catch (Exception e) { RDXTrace.TraceError("GetLatestQuery Exception {0}", e.Message); return(0); } }
/// <summary> /// Helper to wait for all tasks in the list. /// </summary> /// <param name="what">Text for Trace Information</param> /// <param name="tasks">List of tasks</param> /// <param name="stopWatch">Stop watch of tasks</param> public static async Task WhenAllTasks(string what, List <Task> tasks, Stopwatch stopWatch) { // wait for all query tasks to finish and count statistics int successfullTasks = 0; int failedTasks = 0; int cancelledTasks = 0; int otherTasks = 0; await Task.WhenAll(tasks.ToArray()); foreach (Task task in tasks) { switch (task.Status) { case TaskStatus.RanToCompletion: successfullTasks++; break; case TaskStatus.Canceled: cancelledTasks++; break; case TaskStatus.Faulted: failedTasks++; break; default: otherTasks++; break; } } RDXTrace.TraceInformation("WhenAllTasks {5} finished after {0}ms: Succeeded:{1} Failed:{2} Cancel:{3} Other:{4}", stopWatch.ElapsedMilliseconds, successfullTasks, failedTasks, cancelledTasks, otherTasks, what); }
/// <summary> /// Query to subtract the oldest value of a OPC UA node /// from the newest value in the search span /// </summary> /// <param name="searchSpan">Date and time span for the query</param> /// <param name="appUri">The OPC UA server application Uri</param> /// <param name="nodeId">The node id in the OPC UA server namespace</param> /// <returns>The difference</returns> public async Task <double> DiffQuery(DateTimeRange searchSpan, string appUri, string nodeId) { try { PredicateStringExpression expression = new PredicateStringExpression( String.Format(OpcServerNodePredicate, appUri, nodeId)); BaseLimitClause firstMember = new TopLimitClause(1, new[] { new SortClause(new BuiltInPropertyReferenceExpression(BuiltInProperty.Timestamp), SortOrderType.Asc) }); BaseLimitClause lastMember = new TopLimitClause(1, new[] { new SortClause(new BuiltInPropertyReferenceExpression(BuiltInProperty.Timestamp), SortOrderType.Desc) }); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task <IEnumerable <IEvent> > firstTask = RDXQueryClient.GetEventsAsync( searchSpan, expression, firstMember, _cancellationToken); Task <IEnumerable <IEvent> > lastTask = RDXQueryClient.GetEventsAsync( searchSpan, expression, lastMember, _cancellationToken); await Task.WhenAll(new Task[] { firstTask, lastTask }); IEnumerable <IEvent> firstEvent = firstTask.Result; IEnumerable <IEvent> lastEvent = lastTask.Result; stopwatch.Stop(); RDXTrace.TraceInformation("DiffQuery queries took {0} ms", stopwatch.ElapsedMilliseconds); long first = GetValueOfProperty <long>(firstEvent.First <IEvent>(), OpcMonitoredItemValue); long last = GetValueOfProperty <long>(lastEvent.First <IEvent>(), OpcMonitoredItemValue); return(last - first); } catch (Exception e) { RDXTrace.TraceError("DiffQuery Exception {0}", e.Message); return(0); } }
/// <summary> /// Query for latest event of the given node and given OPC UA server /// from the given time and date to find the display name for the nodeId. /// </summary> /// <param name="endTime">Time to start from querying in the past</param> /// <param name="appUri">The OPC UA server application Uri</param> /// <param name="nodeId">The node id in the OPC UA server namespace</param> /// <returns>The DisplayName of the nodeId</returns> public async Task <string> QueryDisplayName(DateTime endTime, string appUri, string nodeId) { try { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task <IEnumerable <IEvent> > lastTask = GetLatestEvent(endTime, appUri, nodeId); IEnumerable <IEvent> lastEvent = await lastTask; stopwatch.Stop(); string name = GetValueOfProperty <string>(lastEvent.First <IEvent>(), OpcDisplayName); RDXTrace.TraceInformation("GetDisplayName took {0} ms", stopwatch.ElapsedMilliseconds); return(name); } catch (Exception e) { RDXTrace.TraceError("GetDisplayName Exception {0}", e.Message); } return(nodeId); }
/// <summary> /// Test the explorer url for all stations /// </summary> /// <param name="topology"></param> public static void TestExplorerViews(ContosoTopology topology) { List <string> stations = topology.GetAllChildren(topology.TopologyRoot.Key, typeof(Station)); int count = 0; DateTime Now = DateTime.UtcNow; string url; url = GetExplorerDefaultView(Now.Subtract(TimeSpan.FromHours(8)), Now, false); RDXTrace.TraceInformation(url); url = GetExplorerDefaultView(Now.Subtract(TimeSpan.FromDays(1)), Now, true); RDXTrace.TraceInformation(url); foreach (string appUri in stations) { url = GetExplorerStationView(Now.Subtract(TimeSpan.FromHours(1)), Now, appUri, (count & 1) == 0, (count & 2) == 0); RDXTrace.TraceInformation(url); count++; } }
/// <summary> /// Query for aggregation of count, min, max, sum and average for all active /// nodes of the given OPC UA server in the given search span as time histogram /// with interval /// </summary> /// <param name="searchSpan">Date and time span for the query</param> /// <param name="appUri">The OPC UA server application Uri</param> /// <param name="interval">Interval for Date Time Histogram</param> /// <returns>The aggregated nodes</returns> public async Task <AggregateResult> GetAllAggregatedNodesWithInterval(DateTimeRange searchSpan, string appUri, string nodeId, TimeSpan interval) { try { string id = TimeSpanToId(interval); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); PredicateStringExpression predicate = new PredicateStringExpression( String.Format(OpcServerNodePredicate, appUri, nodeId)); Aggregate aggregate = new Aggregate( Expression.UniqueValues(OpcMonitoredItemId, PropertyType.String, opcMaxMonitoredItemId), new Aggregate(Expression.DateHistogram(BuiltInProperty.Timestamp, IntervalSize.FromId(id)), new Aggregate( Expression.Count(), Expression.Min(OpcMonitoredItemValue, PropertyType.Double), Expression.Max(OpcMonitoredItemValue, PropertyType.Double), Expression.Average(OpcMonitoredItemValue, PropertyType.Double), Expression.Sum(OpcMonitoredItemValue, PropertyType.Double) ))); AggregatesResult aggregateResults = await RDXQueryClient.GetAggregatesAsync( searchSpan, predicate, new[] { aggregate }, _cancellationToken); // Since there was 1 top level aggregate in request, there is 1 aggregate result. AggregateResult aggregateResult = aggregateResults[0]; stopwatch.Stop(); RDXTrace.TraceInformation("GetAllAggregatedNodes query took {0} ms", stopwatch.ElapsedMilliseconds); return(aggregateResult); } catch (Exception e) { RDXTrace.TraceError("GetAllAggregatedNodes: Exception {0}", e.Message); return(null); } }
/// <summary> /// Query for aggregation of count, min, max, sum and average for all active /// nodes of all OPC UA servers in the given search span /// </summary> /// <param name="searchSpan">Date and time span for the query</param> /// <returns>The aggregated servers and nodes</returns> public async Task <AggregateResult> GetAllAggregatedStationsAndNodes(DateTimeRange searchSpan) { try { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Aggregate aggregate = new Aggregate( Expression.UniqueValues(OpcServerUri, PropertyType.String, opcMaxServerUri), new Aggregate( Expression.UniqueValues(OpcMonitoredItemId, PropertyType.String, opcMaxMonitoredItemId), new Aggregate( Expression.Count(), Expression.Min(OpcMonitoredItemValue, PropertyType.Double), Expression.Max(OpcMonitoredItemValue, PropertyType.Double), Expression.Average(OpcMonitoredItemValue, PropertyType.Double), Expression.Sum(OpcMonitoredItemValue, PropertyType.Double) ) ) ); AggregatesResult aggregateResults = await RDXQueryClient.GetAggregatesAsync( searchSpan, null, new[] { aggregate }, _cancellationToken); // Since there was 1 top level aggregate in request, there is 1 aggregate result. _getAllAggregatedStationsAndNodesResult = aggregateResults[0]; stopwatch.Stop(); RDXTrace.TraceInformation("GetAllAggregatedStationsAndNodes query took {0} ms", stopwatch.ElapsedMilliseconds); return(_getAllAggregatedStationsAndNodesResult); } catch (Exception e) { RDXTrace.TraceError("GetAllAggregatedStationsAndNodes: Exception {0}", e.Message); return(null); } }
/// <summary> /// Search cached query for all active nodeId of all servers in topology. /// Add new nodes to station topology. /// </summary> /// <param name="fullQuery">A cached query</param> /// <param name="topologyStations">List of stations in topology</param> private async Task AddNewNodes(RDXCachedAggregatedQuery fullQuery, List <string> topologyStations) { List <string> opcUaServers = fullQuery.GetActiveServerList(); foreach (string opcUaServer in opcUaServers) { ContosoOpcUaServer topologyNode = _topology[opcUaServer.ToLower()] as ContosoOpcUaServer; if (topologyNode != null) { List <string> topologyNodeIdList = topologyNode.NodeList.Select(x => x.NodeId).ToList(); List <string> activeNodeIdList = fullQuery.GetActiveNodeIdList(opcUaServer); var newNodeIdList = activeNodeIdList.Except(topologyNodeIdList, StringComparer.InvariantCultureIgnoreCase); if (newNodeIdList.Count() > 0) { RDXOpcUaQueries opcUaQueries = new RDXOpcUaQueries(CancellationToken.None); foreach (string newNodeId in newNodeIdList) { RDXTrace.TraceInformation("RDX Worker adding node {0} to server {1}", newNodeId, opcUaServer); string symbolicName = await opcUaQueries.QueryDisplayName(DateTime.UtcNow, opcUaServer, newNodeId); topologyNode.AddOpcServerNode( newNodeId, symbolicName, null, ContosoOpcNodeOpCode.Avg, null, true, null, null, null, null, null, null, null ); } } } } }
/// <summary> /// Query for aggregation of a given measure for the /// given node and given OPC UA server in the given search span /// </summary> /// <param name="searchSpan">Date and time span for the query</param> /// <param name="appUri">The OPC UA server application Uri</param> /// <param name="nodeId">The node id in the OPC UA server namespace</param> /// <param name="measure"></param> public async Task <double> GetAggregatedNode(DateTimeRange searchSpan, string appUri, string nodeId, AggregateExpression measure) { try { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); PredicateStringExpression predicate = new PredicateStringExpression( String.Format(OpcServerNodePredicate, appUri, nodeId)); Aggregate aggregate = new Aggregate( measures: new AggregateExpression[] { measure } ); AggregatesResult aggregateResults = await RDXQueryClient.GetAggregatesAsync( searchSpan, predicate, new[] { aggregate }, _cancellationToken); // Since there was 1 top level aggregate in request, there is 1 aggregate result. AggregateResult aggregateResult = aggregateResults[0]; stopwatch.Stop(); RDXTrace.TraceInformation("AggregateQuery query took {0} ms", stopwatch.ElapsedMilliseconds); if (aggregateResult.Measures == null) { return(0.00); } return((double)aggregateResult.Measures[0]); } catch (Exception e) { RDXTrace.TraceError("AggregateQuery: Exception {0}", e.Message); return(0); } }
/// <summary> /// Worker to query one aggregate of a topology /// </summary> /// <param name="aggregateIndex">The aggregation view to update by the worker</param> /// <param name="token">CancellationToken</param> public async Task Worker(int aggregateIndex, CancellationToken token) { RDXOpcUaQueries opcUaQueries = new RDXOpcUaQueries(token); RDXOeeKpiQueries oeeKpiQueries = new RDXOeeKpiQueries(opcUaQueries, token); RDXTrace.TraceInformation("RDX Worker {0} started", aggregateIndex); // give app some time to start before updating queries await Task.Delay(10000 *(aggregateIndex + 1)); while (!token.IsCancellationRequested) { Stopwatch stopWatch = new Stopwatch(); DateTime nextUpdate = DateTime.MaxValue; bool resetStartDelayed = false; stopWatch.Start(); Interlocked.Increment(ref _busyWorkers); // the station and node list is updated, other workers are delayed while (_workerStartDelayed != 0) { RDXTrace.TraceInformation("RDX Worker {0} delayed", aggregateIndex); await Task.Delay(1000); } try { List <Task> tasks = new List <Task>(); ContosoTopologyNode rootNode = _topology.GetRootNode(); ContosoAggregatedOeeKpiHistogram aggregatedTimeSpan = rootNode[aggregateIndex]; DateTimeRange searchSpan = RDXUtils.TotalSearchRangeFromNow(aggregatedTimeSpan); RDXTrace.TraceInformation("RDX Worker {0} updating Range {1} to {2}", aggregateIndex, searchSpan.From, searchSpan.To); // calc next update. To time is already rounded for update time span. nextUpdate = searchSpan.To + rootNode[aggregateIndex].UpdateTimeSpan; // query all stations in topology and find all active servers in timespan Task <StringDimensionResult> aggServerTask = opcUaQueries.AggregateServers(searchSpan); // always get an aggregate of all activity for the latest interval, use it as a cache TimeSpan intervalTimeSpan = aggregatedTimeSpan[0].IntervalTimeSpan; DateTimeRange aggregateSpan = RDXUtils.CalcAggregationRange(aggregatedTimeSpan, searchSpan.To); RDXCachedAggregatedQuery fullQuery = new RDXCachedAggregatedQuery(opcUaQueries); Task aggServerAndNodesTask = fullQuery.Execute(aggregateSpan); // wait for all outstanding aggregates tasks.Add(aggServerTask); tasks.Add(aggServerAndNodesTask); await RDXUtils.WhenAllTasks("Aggregates", tasks, stopWatch); List <string> topologyStations = _topology.GetAllChildren(_tree.TopologyRoot.Key, typeof(Station)); List <string> opcUaServers = await aggServerTask; // intersect list of active servers and schedule all queries tasks.Clear(); List <string> opcUaServersToQuery = opcUaServers.Intersect(topologyStations, StringComparer.InvariantCultureIgnoreCase).ToList(); await oeeKpiQueries.ScheduleAllOeeKpiQueries(searchSpan, _topology, fullQuery, opcUaServersToQuery, tasks, aggregateIndex); // wait for all outstanding queries await RDXUtils.WhenAllTasks("Queries", tasks, stopWatch); // Update the topology Oee and KPI values _topology.UpdateAllKPIAndOEEValues(aggregateIndex); // one worker issues the Browser update if (aggregatedTimeSpan.UpdateBrowser) { // Push updates to dashboard BrowserUpdate(); RDXTrace.TraceInformation("BrowserUpdate finished after {0}ms", stopWatch.ElapsedMilliseconds); } // add new stations and nodes to topology if (aggregatedTimeSpan.UpdateTopology) { // delay other workers Interlocked.Exchange(ref _workerStartDelayed, 1); resetStartDelayed = true; // only add stations and nodes if no other worker is busy yet if (Interlocked.Increment(ref _busyWorkers) == 2) { AddNewServers(fullQuery, topologyStations); await AddNewNodes(fullQuery, topologyStations); RDXTrace.TraceInformation("Add New Server and Nodes finished after {0}ms", stopWatch.ElapsedMilliseconds); } } } catch (Exception e) { RDXTrace.TraceError("Exception {0} in Worker after {1}ms", e.Message, stopWatch.ElapsedMilliseconds); } finally { Interlocked.Decrement(ref _busyWorkers); if (resetStartDelayed) { Interlocked.Decrement(ref _busyWorkers); Interlocked.Exchange(ref _workerStartDelayed, 0); } } RDXTrace.TraceInformation("RDX Worker {0} schedule next update for {1}", aggregateIndex, nextUpdate); TimeSpan delay = nextUpdate.Subtract(DateTime.UtcNow); if (delay.TotalMilliseconds > 0) { await Task.Delay(delay, token); } } }
/// <summary> /// Create the singleton instance for the RDX C# client SDK /// Deletes singleton if the access to the specified environment fails /// to prevent subsequent access of worker threads /// </summary> public static async void Create() { while (true) { RDXTrace.TraceInformation("Start RDX Query Client"); try { ServicePointManager.DefaultConnectionLimit = 1000; CommonLogger.SetWriter(TraceLogWriter.Instance); // Authenticate with the RDX service, depending on configuration one option is used // to create the OAuth2 bearer tokens IClientAuthenticator clientAuthenticator; string tenantId = ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationTenantId); IccString clientCertificateThumbprint = new IccString(ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationClientCertificateThumbprint)); if (clientCertificateThumbprint.IsNullOrWhiteSpace()) { string clientId = ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationClientId); string clientSecret = ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationClientSecret); if (String.IsNullOrWhiteSpace(clientSecret)) { Uri redirectUri = new Uri(ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationRedirectUri)); clientAuthenticator = new UserClientAuthenticator(tenantId, clientId, redirectUri, BaseClientAuthenticator.AzureTimeSeriesResource); } else { clientAuthenticator = new ClientCredentialAuthenticator(tenantId, clientId, clientSecret, BaseClientAuthenticator.AzureTimeSeriesResource); } } else { string applicationClientId = ConfigurationProvider.GetConfigurationSettingValue(rdxAuthenticationClientApplicationId); clientAuthenticator = new ApplicationCertificateClientAuthenticator(applicationClientId, clientCertificateThumbprint, tenantId, BaseClientAuthenticator.AzureTimeSeriesResource); } // Create the RDX client with authenticator and DNS resolver _rdxDNSName = ConfigurationProvider.GetConfigurationSettingValue(rdxDnsName); IccString rdxIccDnsName = new IccString("api." + _rdxDNSName); string solutionName = ConfigurationProvider.GetConfigurationSettingValue(rdxApplicationName); _rdxGlobalQueryClient = new RdxGlobalQueryClient(rdxIccDnsName, solutionName, clientAuthenticator); // Test if our environment exists and is accessible _rdxEnvironmentId = new IccString(ConfigurationProvider.GetConfigurationSettingValue(rdxEnvironmentId)); GetEnvironmentsOutput environments = await GetEnvironmentsAsync(CancellationToken.None); Trace.TraceInformation("Got {0} environments: ", environments.Environments.Count); bool foundEnvironment = false; foreach (var env in environments.Environments) { Trace.TraceInformation(" {0} {1}", env.EnvironmentId, env.DisplayName); if (env.EnvironmentId == _rdxEnvironmentId) { foundEnvironment = true; _rdxEnvironmentName = env.DisplayName; _rdxEnvironmentFqdn = env.EnvironmentFqdn; break; } } if (!foundEnvironment) { throw new Exception(String.Format("RDX Environment {0} not found.", _rdxEnvironmentId.ToString())); } _rdxEnvironmentQueryClient = new RdxEnvironmentQueryClient( _rdxEnvironmentFqdn, solutionName, clientAuthenticator); Trace.TraceInformation("..... RDXQueryClient started ....."); return; } catch (Exception e) { RDXTrace.TraceError("RDX CreateQueryClient failed: {0}", e.ExceptionToString()); _rdxGlobalQueryClient = null; _rdxEnvironmentQueryClient = null; } RDXTrace.TraceError("Fatal: RDX environment not found. Retry in 60s."); await Task.Delay(60000); } }