/// <summary>
        /// Timespan delegate. Returns the timespan as value.
        /// </summary>
        public static Task Timespan(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
        {
            TimeSpan timespan = rdxQuery.SearchSpan.To.Subtract(rdxQuery.SearchSpan.From);

            switch (opcUaNode.Units)
            {
            case "h":
                opcUaNode.Last.Value = timespan.TotalHours;
                break;

            case "m":
                opcUaNode.Last.Value = timespan.TotalMinutes;
                break;

            case "s":
                opcUaNode.Last.Value = timespan.TotalSeconds;
                break;

            case "t":
                opcUaNode.Last.Value = timespan.Ticks;
                break;

            case "ms":
            default:
                opcUaNode.Last.Value = timespan.TotalMilliseconds;
                break;
            }
            opcUaNode.Last.Time = rdxQuery.SearchSpan.To;
            opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
            return(Task.CompletedTask);
        }
        /// <summary>
        /// Diff delegate. Calculate the difference between first value and last value in timespan.
        /// </summary>
        public static async Task Diff(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
        {
            opcUaNode.Last.Value = await rdxQuery.OpcUaQueries.DiffQuery(rdxQuery.SearchSpan, rdxQuery.AppUri, opcUaNode.NodeId);

            opcUaNode.Last.Time = rdxQuery.SearchSpan.To;
            opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
        }
        /// <summary>
        /// Query for a single value.
        /// </summary>
        private static async Task Aggregate(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode, AggregateExpression expression)
        {
            opcUaNode.Last.Value = await rdxQuery.OpcUaQueries.GetAggregatedNode(rdxQuery.SearchSpan, rdxQuery.AppUri, opcUaNode.NodeId, expression);

            opcUaNode.Last.Time = rdxQuery.SearchSpan.To;
            opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
        }
 /// <summary>
 /// Const delegate. Returns a constant value defined in the Node Id field.
 /// </summary>
 public static Task Const(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
 {
     opcUaNode.Last.Value = (double)opcUaNode.ConstValue;
     opcUaNode.Last.Time  = DateTime.MinValue;
     opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
     return(Task.CompletedTask);
 }
        /// <summary>
        /// Calls the delegate for an operator.
        /// </summary>
        /// <param name="opCode">Operation to execute</param>
        /// <param name="rdxQuery">Query info.</param>
        /// <param name="opcUaNode">Topology node.</param>
        public static Task CallOperator(ContosoOpcNodeOpCode opCode, RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
        {
            RDXOpCode opCodeFunc = GetOperator(opCode);

            if (opCodeFunc == null)
            {
                return(Task.CompletedTask);
            }
            return(opCodeFunc(rdxQuery, opcUaNode));
        }
        /// <summary>
        /// SubMaxMin delegate. Subtracts min from max value in a timespan.
        /// </summary>
        /// <param name="rdxQuery"></param>
        /// <param name="opcUaNode"></param>
        public static async Task SubMaxMin(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
        {
            double max = await rdxQuery.OpcUaQueries.GetAggregatedNode(rdxQuery.SearchSpan, rdxQuery.AppUri, opcUaNode.NodeId, RDXOpcUaQueries.MaxValues());

            double min = await rdxQuery.OpcUaQueries.GetAggregatedNode(rdxQuery.SearchSpan, rdxQuery.AppUri, opcUaNode.NodeId, RDXOpcUaQueries.MinValues());

            opcUaNode.Last.Value = max - min;
            opcUaNode.Last.Time  = rdxQuery.SearchSpan.To;
            opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
        }
 /// <summary>
 /// Call the operator on a query and node.
 /// Gets value from cache if operation is supported,
 /// if not forwards the call to single query engine.
 /// </summary>
 /// <param name="opCode">Operation to get the value</param>
 /// <param name="rdxQuery">The query</param>
 /// <param name="opcUaNode">The Topology node </param>
 public async Task CallOperator(ContosoOpcNodeOpCode opCode, RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
 {
     if (RDXUtils.IsAggregatedOperator(opCode) &&
         _task != null)
     {
         _result = await _task;
         if (_result != null)
         {
             double?value = GetValue(opCode, rdxQuery.AppUri, opcUaNode.NodeId);
             if (value != null)
             {
                 opcUaNode.Last.Value = (double)value;
                 opcUaNode.Last.Time  = rdxQuery.SearchSpan.To;
                 opcUaNode.UpdateRelevance(rdxQuery.TopologyNode);
             }
         }
         return;
     }
     // issue a single query if the operator can not be handled from aggregated values
     await RDXContosoOpCodes.CallOperator(opCode, rdxQuery, opcUaNode);
 }
 /// <summary>
 /// NOP delegate. Just updates the time, value is not updated.
 /// </summary>
 public static Task Nop(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
 {
     opcUaNode.Last.Time = DateTime.UtcNow;
     return(Task.CompletedTask);
 }
        /// <summary>
        /// Queries all intervals, servers and nodes in a topology for new values.
        /// Updates all relevances and checks for alerts.
        /// Returns list of task.
        /// Caller must wait for tasks to run to completion before topology is fully up to date.
        /// </summary>
        /// <param name="totalSearchSpan">Search span of query</param>
        /// <param name="topology">Topology to update</param>
        /// <param name="fullQuery">The cached query for the search span</param>
        /// <param name="opcUaServers">List of OPC UA servers to query for values</param>
        /// <param name="tasks">List of async tasks processing queries</param>
        /// <param name="aggregateIndex">The index of the aggregate in the topology nodes</param>
        public async Task ScheduleAllOeeKpiQueries(
            DateTimeRange totalSearchSpan,
            ContosoTopology topology,
            RDXCachedAggregatedQuery fullQuery,
            List <string> opcUaServers,
            List <Task> tasks,
            int aggregateIndex)
        {
            RDXAggregatedQueryCache queryCache = new RDXAggregatedQueryCache();

            queryCache.List.Add(fullQuery);

            foreach (string appUri in opcUaServers)
            {
                Station station = topology[appUri] as Station;
                if (station != null)
                {
                    ContosoAggregatedOeeKpiHistogram oeeKpiHistogram = station[aggregateIndex];
                    DateTime toTime    = totalSearchSpan.To;
                    int      intervals = oeeKpiHistogram.Intervals.Count;
                    oeeKpiHistogram.EndTime         = toTime;
                    station[aggregateIndex].EndTime = toTime;

                    foreach (ContosoAggregatedOeeKpiTimeSpan oeeKpiTimeSpan in oeeKpiHistogram.Intervals)
                    {
                        DateTime fromTime;

                        // first interval is current time
                        if (totalSearchSpan.To != toTime || intervals == 1)
                        {
                            fromTime = toTime.Subtract(oeeKpiTimeSpan.IntervalTimeSpan);
                        }
                        else
                        {
                            fromTime = RDXUtils.RoundDateTimeToTimeSpan(toTime.Subtract(TimeSpan.FromSeconds(1)), oeeKpiTimeSpan.IntervalTimeSpan);
                        }

                        DateTimeRange intervalSearchSpan = new DateTimeRange(fromTime, toTime);

                        if (toTime <= oeeKpiTimeSpan.EndTime)
                        {
                            // The interval is still up to date from a previous query, skip
                            toTime = fromTime;
                            continue;
                        }

                        oeeKpiTimeSpan.EndTime = toTime;
                        toTime = fromTime;

                        // find a cached query, if not try to cache the aggregation for this search span
                        RDXCachedAggregatedQuery aggregatedQuery = queryCache.Find(intervalSearchSpan);
                        if (aggregatedQuery == null)
                        {
                            RDXCachedAggregatedQuery newQuery = new RDXCachedAggregatedQuery(_opcUaQueries);
                            queryCache.List.Add(newQuery);
                            tasks.Add(newQuery.Execute(intervalSearchSpan));
                            aggregatedQuery = queryCache.Find(intervalSearchSpan);
                        }

                        RDXOeeKpiQuery oeeKpiQuery = new RDXOeeKpiQuery(_opcUaQueries, intervalSearchSpan, appUri, oeeKpiTimeSpan);

                        await oeeKpiQuery.QueryAllNodes(tasks, station.NodeList, oeeKpiHistogram.AwaitTasks, aggregatedQuery);

                        if (oeeKpiHistogram.CheckAlerts)
                        {
                            station.Status = ContosoPerformanceStatus.Good;
                            foreach (ContosoOpcUaNode node in station.NodeList)
                            {
                                if (node.Minimum != null || node.Maximum != null)
                                {
                                    station.Status |= await CheckAlert(intervalSearchSpan, appUri, station, node);
                                }
                            }
                        }
                    }
                }
            }
        }
 /// <summary>
 /// Count delegate. Returns number of events in a timespan.
 /// </summary>
 public static Task Count(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
 {
     return(Aggregate(rdxQuery, opcUaNode, Expression.Count()));
 }
 /// <summary>
 /// Max delegate. Returns largest value in a timespan.
 /// </summary>
 public static Task Max(RDXOeeKpiQuery rdxQuery, ContosoOpcUaNode opcUaNode)
 {
     return(Aggregate(rdxQuery, opcUaNode, RDXOpcUaQueries.MaxValues()));
 }