/// <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>
        /// 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>
        /// 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>
        /// 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);
        }
        private bool ProcessPublisherMessage(string opcUri, string nodeId, string sourceTimestamp, string value)
        {
            // Get the OPC UA node object.
            ContosoOpcUaNode opcUaNode = Startup.Topology.GetOpcUaNode(opcUri.ToLower(), nodeId);

            if (opcUaNode == null)
            {
                return(false);
            }

            // Update last value.
            opcUaNode.LastValue          = value;
            opcUaNode.LastValueTimestamp = sourceTimestamp;
            return(true);
        }
Beispiel #7
0
        public string GetDataForOpcUaNode(string key, string nodeId, ContosoTopologyNode.AggregationView view)
        {
            AggregatedTimeSeriesResult timeSeries;
            ContosoOpcUaNode           contosoOpcUaNode = Startup.Topology.GetOpcUaNode(key, nodeId);
            Station station = Startup.Topology.GetStation(key);

            string[] data = new string[3];

            if ((contosoOpcUaNode == null) || (station == null))
            {
                data[0] = "Error";
                return(JsonConvert.SerializeObject(data));
            }

            if (!(RDXUtils.IsAggregatedOperator(contosoOpcUaNode.OpCode)))
            {
                data[0] = "NoTimeSeries";
                // Non aggregating opcodes are not updated unless they have a relevance
                data[1] = contosoOpcUaNode.Last.Value.ToString("0.###", CultureInfo.InvariantCulture);

                if (contosoOpcUaNode.Units != null)
                {
                    data[2] = contosoOpcUaNode.Units.ToString();
                }
                else
                {
                    data[2] = "";
                }
                return(JsonConvert.SerializeObject(data));
            }

            Task <AggregatedTimeSeriesResult> task = Task.Run(() => RDXUtils.AggregatedNodeId(station, contosoOpcUaNode, view));

            // Will block until the task is completed...
            timeSeries = task.Result;

            return(JsonConvert.SerializeObject(timeSeries));
        }
        /// <summary>
        /// Return the time series for a given OPC UA server and a given node
        /// </summary>
        /// <param name="station">The OPC UA server</param>
        /// <param name="node">The OPC UA node Id</param>
        /// <param name="aggregationView">The hourly, daily or weekly view</param>
        /// <param name="getCount">Get event Count aggregate</param>
        public static async Task <AggregatedTimeSeriesResult> AggregatedNodeId(
            Station station,
            ContosoOpcUaNode node,
            ContosoTopologyNode.AggregationView aggregationView,
            bool getCount = false)
        {
            int             aggregateIndex = (int)aggregationView;
            RDXOpcUaQueries opcUaQuery     = new RDXOpcUaQueries(CancellationToken.None);
            ContosoAggregatedOeeKpiHistogram aggregatedTimeSpan = station[aggregateIndex];
            DateTimeRange searchSpan    = RDXUtils.TotalSearchRangeFromNow(aggregatedTimeSpan);
            DateTimeRange aggregateSpan = new DateTimeRange(
                searchSpan.To.Subtract(aggregatedTimeSpan.TotalTimeSpan),
                searchSpan.To
                );
            double roundFactor = 100.0;

            int index = (int)RDXOpcUaQueries.AggregateIndex.Count;

            if (!getCount)
            {
                if (!IsAggregatedOperator(node.OpCode))
                {
                    throw new Exception("Unsupported Operator for aggregation");
                }

                // handle special case for SubMaxMin, result is derived by substraction
                if (node.OpCode == ContosoOpcNodeOpCode.SubMaxMin)
                {
                    index = (int)RDXOpcUaQueries.AggregateIndex.Max;
                }
                else
                {
                    index = AggregatedOperatorIndex(node.OpCode);
                }
            }

            int resultDimension = aggregatedTimeSpan.Intervals.Count;

            DateTime [] dateTimeResult = CreateAggregateDateTimeArray(aggregateSpan.From, aggregatedTimeSpan.IntervalTimeSpan, resultDimension);

            AggregateResult queryResult = await opcUaQuery.GetAllAggregatedNodesWithInterval(
                aggregateSpan,
                station.Key,
                node.NodeId,
                aggregatedTimeSpan.IntervalTimeSpan
                );

            int count = queryResult.Aggregate.Dimension.Count;
            AggregatedTimeSeriesResult result = new AggregatedTimeSeriesResult(resultDimension, aggregateSpan.To, aggregatedTimeSpan.IntervalTimeSpan, node.Units);

            if (queryResult != null)
            {
                for (int i = 0; i < count; i++)
                {
                    double?value = queryResult.Aggregate.Aggregate.Measures.TryGetPropertyMeasure <double?>(new int[] { 0, i, index });
                    if (value != null)
                    {
                        // find matching date/time slot for value
                        var dateTime    = queryResult.Aggregate.Dimension[i];
                        int resultIndex = Array.IndexOf(dateTimeResult, dateTime);
                        if (resultIndex >= 0)
                        {
                            result.YValues[resultIndex] = roundFactor * (double)value;
                            if (node.OpCode == ContosoOpcNodeOpCode.SubMaxMin)
                            {
                                value = queryResult.Aggregate.Aggregate.Measures.TryGetPropertyMeasure <double?>(new int[] { 0, i, (int)RDXOpcUaQueries.AggregateIndex.Min });
                                if (value != null)
                                {
                                    result.YValues[resultIndex] -= roundFactor * (double)value;
                                }
                            }
                            result.YValues[resultIndex] = Math.Round(result.YValues[resultIndex]) / roundFactor;
                        }
                        else
                        {
                            throw new Exception("DateTime not found in aggregated query array");
                        }
                    }
                }
            }
            return(result);
        }
        public async Task <ActionResult> GetChildren(string jstreeNode)
        {
            // This delimiter is used to allow the storing of the OPC UA parent node ID together with the OPC UA child node ID in jstree data structures and provide it as parameter to
            // Ajax calls.
            string[] delimiter       = { "__$__" };
            string[] jstreeNodeSplit = jstreeNode.Split(delimiter, 3, StringSplitOptions.None);
            string   node;

            if (jstreeNodeSplit.Length == 1)
            {
                node = jstreeNodeSplit[0];
            }
            else
            {
                node = jstreeNodeSplit[1];
            }

            List <object> jsonTree    = new List <object>();
            string        endpointId  = Session["EndpointId"].ToString();
            string        endpointUrl = Session["EndpointUrl"].ToString();
            string        ProductUri  = Session["ProductUri"].ToString();

            // read the currently published nodes
            PublishedItemListResponseApiModel publishedNodes = new PublishedItemListResponseApiModel();

            try
            {
                PublishedItemListRequestApiModel publishModel = new PublishedItemListRequestApiModel();
                publishedNodes = await TwinService.GetPublishedNodesAsync(endpointId, publishModel);
            }
            catch (Exception e)
            {
                // do nothing, since we still want to show the tree
                Trace.TraceWarning("Can not read published nodes for endpoint '{0}'.", endpointUrl);
                string errorMessage = string.Format(Strings.BrowserOpcException, e.Message,
                                                    e.InnerException?.Message ?? "--", e?.StackTrace ?? "--");
                Trace.TraceWarning(errorMessage);
            }

            BrowseResponseApiModel browseData = new BrowseResponseApiModel();

            try
            {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                try
                {
                    BrowseRequestApiModel model = new BrowseRequestApiModel();
                    model.NodeId          = node;
                    model.TargetNodesOnly = false;

                    browseData = TwinService.NodeBrowse(endpointId, model);
                }
                catch (Exception e)
                {
                    // skip this node
                    Trace.TraceError("Can not browse node '{0}'", node);
                    string errorMessage = string.Format(Strings.BrowserOpcException, e.Message,
                                                        e.InnerException?.Message ?? "--", e?.StackTrace ?? "--");
                    Trace.TraceError(errorMessage);
                }

                Trace.TraceInformation("Browsing node '{0}' data took {0} ms", node.ToString(), stopwatch.ElapsedMilliseconds);

                if (browseData.References != null)
                {
                    var idList = new List <string>();
                    foreach (var nodeReference in browseData.References)
                    {
                        bool idFound = false;
                        foreach (var id in idList)
                        {
                            if (id == nodeReference.Target.NodeId.ToString())
                            {
                                idFound = true;
                            }
                        }
                        if (idFound == true)
                        {
                            continue;
                        }

                        Trace.TraceInformation("Browse '{0}' count: {1}", nodeReference.Target.NodeId, jsonTree.Count);

                        NodeApiModel currentNode = nodeReference.Target;

                        currentNode = nodeReference.Target;

                        byte currentNodeAccessLevel   = 0;
                        byte currentNodeEventNotifier = 0;
                        bool currentNodeExecutable    = false;

                        switch (currentNode.NodeClass)
                        {
                        case NodeClass.Variable:
                            currentNodeAccessLevel = (byte)currentNode.UserAccessLevel;
                            if (!PermsChecker.HasPermission(Permission.ControlOpcServer))
                            {
                                currentNodeAccessLevel = (byte)((uint)currentNodeAccessLevel & ~0x2);
                            }
                            break;

                        case NodeClass.Object:
                            currentNodeEventNotifier = (byte)currentNode.EventNotifier;
                            break;

                        case NodeClass.View:
                            currentNodeEventNotifier = (byte)currentNode.EventNotifier;
                            break;

                        case NodeClass.Method:
                            if (PermsChecker.HasPermission(Permission.ControlOpcServer))
                            {
                                currentNodeExecutable = (bool)currentNode.UserExecutable;
                            }
                            break;

                        default:
                            break;
                        }

                        var isPublished = false;
                        var isRelevant  = false;
                        if (publishedNodes.Items != null)
                        {
                            foreach (var item in publishedNodes.Items)
                            {
                                if (item.NodeId == nodeReference.Target.NodeId.ToString())
                                {
                                    isPublished = true;
                                    ContosoOpcUaNode contosoOpcUaNode = Startup.Topology.GetOpcUaNode(ProductUri, item.NodeId);
                                    if (contosoOpcUaNode?.Relevance != null)
                                    {
                                        isRelevant = true;
                                    }
                                }
                            }
                        }

                        jsonTree.Add(new
                        {
                            id            = ("__" + node + delimiter[0] + nodeReference.Target.NodeId.ToString()),
                            text          = nodeReference.Target.DisplayName.ToString(),
                            nodeClass     = nodeReference.Target.NodeClass.ToString(),
                            accessLevel   = currentNodeAccessLevel.ToString(),
                            eventNotifier = currentNodeEventNotifier.ToString(),
                            executable    = currentNodeExecutable.ToString(),
                            children      = nodeReference.Target.HasChildren,
                            publishedNode = isPublished,
                            relevantNode  = isRelevant
                        });
                        idList.Add(nodeReference.Target.NodeId.ToString());
                    }
                }

                stopwatch.Stop();
                Trace.TraceInformation("Browing all childeren info of node '{0}' took {0} ms", node, stopwatch.ElapsedMilliseconds);

                return(Json(jsonTree, JsonRequestBehavior.AllowGet));
            }
            catch (Exception exception)
            {
                return(Content(CreateOpcExceptionActionString(exception)));
            }
        }
 /// <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>
        /// 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>
        /// Test alert condition for nodes
        /// </summary>
        /// <param name="searchSpan"></param>
        /// <param name="appUri"></param>
        /// <param name="topologyNode"></param>
        /// <param name="node"></param>
        public async Task <ContosoPerformanceStatus> CheckAlert(DateTimeRange searchSpan, string appUri, ContosoTopologyNode topologyNode, ContosoOpcUaNode node)
        {
            double value = await _opcUaQueries.GetLatestQuery(searchSpan.To, appUri, node.NodeId);

            // Check for an alert condition.
            if (node.Minimum != null && value < node.Minimum)
            {
                // Add an alert to the server this OPC UA node belongs to.
                ContosoAlert alert = new ContosoAlert(ContosoAlertCause.AlertCauseValueBelowMinimum, appUri, node.NodeId, searchSpan.To);
                topologyNode.AddAlert(alert);
                node.Status = ContosoPerformanceStatus.Poor;
            }
            else if (node.Maximum != null && value > node.Maximum)
            {
                // Add an alert to the server this OPC UA node belongs to.
                ContosoAlert alert = new ContosoAlert(ContosoAlertCause.AlertCauseValueAboveMaximum, appUri, node.NodeId, searchSpan.To);
                topologyNode.AddAlert(alert);
                node.Status = ContosoPerformanceStatus.Poor;
            }
            else
            {
                node.Status = ContosoPerformanceStatus.Good;
            }
            return(node.Status);
        }
 /// <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()));
 }
Beispiel #15
0
        public async Task <ActionResult> GetChildren(string jstreeNode)
        {
            // This delimiter is used to allow the storing of the OPC UA parent node ID together with the OPC UA child node ID in jstree data structures and provide it as parameter to
            // Ajax calls.
            string[] delimiter       = { "__$__" };
            string[] jstreeNodeSplit = jstreeNode.Split(delimiter, 3, StringSplitOptions.None);
            string   node;

            if (jstreeNodeSplit.Length == 1)
            {
                node = jstreeNodeSplit[0];
            }
            else
            {
                node = jstreeNodeSplit[1];
            }

            ReferenceDescriptionCollection references;

            Byte[] continuationPoint;
            var    jsonTree = new List <object>();

            // read the currently published nodes
            Session session = null;

            string[] publishedNodes = null;
            string   endpointUrl    = null;

            try
            {
                session = await OpcSessionHelper.Instance.GetSessionAsync(Session.SessionID, (string)Session["EndpointUrl"]);

                endpointUrl    = session.ConfiguredEndpoint.EndpointUrl.AbsoluteUri;
                publishedNodes = await GetPublishedNodes(endpointUrl);

                Trace.TraceInformation("Currently is/are {0} node(s) published on endpoint '{1}'.", publishedNodes.Length, endpointUrl);
            }
            catch (Exception e)
            {
                // do nothing, since we still want to show the tree
                Trace.TraceWarning("Could not read published nodes for endpoint '{0}'.", endpointUrl);
            }

            bool retry = true;

            while (true)
            {
                try
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    session.Browse(
                        null,
                        null,
                        node,
                        0u,
                        BrowseDirection.Forward,
                        ReferenceTypeIds.HierarchicalReferences,
                        true,
                        0,
                        out continuationPoint,
                        out references);

                    Trace.TraceInformation("Browse {0} ms", stopwatch.ElapsedMilliseconds);

                    if (references != null)
                    {
                        var idList = new List <string>();
                        foreach (var nodeReference in references)
                        {
                            bool idFound = false;
                            foreach (var id in idList)
                            {
                                if (id == nodeReference.NodeId.ToString())
                                {
                                    idFound = true;
                                }
                            }
                            if (idFound == true)
                            {
                                continue;
                            }

                            ReferenceDescriptionCollection childReferences = null;
                            Byte[] childContinuationPoint;

                            session.Browse(
                                null,
                                null,
                                ExpandedNodeId.ToNodeId(nodeReference.NodeId, session.NamespaceUris),
                                0u,
                                BrowseDirection.Forward,
                                ReferenceTypeIds.HierarchicalReferences,
                                true,
                                0,
                                out childContinuationPoint,
                                out childReferences);

                            INode currentNode = null;
                            try
                            {
                                currentNode = session.ReadNode(ExpandedNodeId.ToNodeId(nodeReference.NodeId, session.NamespaceUris));
                            }
                            catch (Exception)
                            {
                                // skip this node
                                continue;
                            }

                            byte currentNodeAccessLevel   = 0;
                            byte currentNodeEventNotifier = 0;
                            bool currentNodeExecutable    = false;

                            VariableNode variableNode = currentNode as VariableNode;
                            if (variableNode != null)
                            {
                                currentNodeAccessLevel = variableNode.UserAccessLevel;
                                if (!PermsChecker.HasPermission(Permission.ControlOpcServer))
                                {
                                    currentNodeAccessLevel = (byte)((uint)currentNodeAccessLevel & ~0x2);
                                }
                            }

                            ObjectNode objectNode = currentNode as ObjectNode;
                            if (objectNode != null)
                            {
                                currentNodeEventNotifier = objectNode.EventNotifier;
                            }

                            ViewNode viewNode = currentNode as ViewNode;
                            if (viewNode != null)
                            {
                                currentNodeEventNotifier = viewNode.EventNotifier;
                            }

                            MethodNode methodNode = currentNode as MethodNode;
                            if (methodNode != null && PermsChecker.HasPermission(Permission.ControlOpcServer))
                            {
                                currentNodeExecutable = methodNode.UserExecutable;
                            }

                            var isPublished = false;
                            var isRelevant  = false;
                            if (publishedNodes != null)
                            {
                                Session stationSession = await OpcSessionHelper.Instance.GetSessionAsync(Session.SessionID, (string)Session["EndpointUrl"]);

                                string urn = stationSession.ServerUris.GetString(0);
                                foreach (var nodeId in publishedNodes)
                                {
                                    if (nodeId == nodeReference.NodeId.ToString())
                                    {
                                        isPublished = true;
                                        ContosoOpcUaNode contosoOpcUaNode = Startup.Topology.GetOpcUaNode(urn, nodeId);
                                        if (contosoOpcUaNode?.Relevance != null)
                                        {
                                            isRelevant = true;
                                        }
                                    }
                                }
                            }

                            jsonTree.Add(new
                            {
                                id            = ("__" + node + delimiter[0] + nodeReference.NodeId.ToString()),
                                text          = nodeReference.DisplayName.ToString(),
                                nodeClass     = nodeReference.NodeClass.ToString(),
                                accessLevel   = currentNodeAccessLevel.ToString(),
                                eventNotifier = currentNodeEventNotifier.ToString(),
                                executable    = currentNodeExecutable.ToString(),
                                children      = (childReferences.Count == 0) ? false : true,
                                publishedNode = isPublished,
                                relevantNode  = isRelevant
                            });
                            idList.Add(nodeReference.NodeId.ToString());
                        }

                        // If there are no children, then this is a call to read the properties of the node itself.
                        if (jsonTree.Count == 0)
                        {
                            INode currentNode = session.ReadNode(new NodeId(node));

                            byte currentNodeAccessLevel   = 0;
                            byte currentNodeEventNotifier = 0;
                            bool currentNodeExecutable    = false;

                            VariableNode variableNode = currentNode as VariableNode;

                            if (variableNode != null)
                            {
                                currentNodeAccessLevel = variableNode.UserAccessLevel;
                                if (!PermsChecker.HasPermission(Permission.ControlOpcServer))
                                {
                                    currentNodeAccessLevel = (byte)((uint)currentNodeAccessLevel & ~0x2);
                                }
                            }

                            ObjectNode objectNode = currentNode as ObjectNode;

                            if (objectNode != null)
                            {
                                currentNodeEventNotifier = objectNode.EventNotifier;
                            }

                            ViewNode viewNode = currentNode as ViewNode;

                            if (viewNode != null)
                            {
                                currentNodeEventNotifier = viewNode.EventNotifier;
                            }

                            MethodNode methodNode = currentNode as MethodNode;

                            if (methodNode != null && PermsChecker.HasPermission(Permission.ControlOpcServer))
                            {
                                currentNodeExecutable = methodNode.UserExecutable;
                            }

                            jsonTree.Add(new
                            {
                                id            = jstreeNode,
                                text          = currentNode.DisplayName.ToString(),
                                nodeClass     = currentNode.NodeClass.ToString(),
                                accessLevel   = currentNodeAccessLevel.ToString(),
                                eventNotifier = currentNodeEventNotifier.ToString(),
                                executable    = currentNodeExecutable.ToString(),
                                children      = false
                            });
                        }
                    }

                    stopwatch.Stop();
                    Trace.TraceInformation("GetChildren took {0} ms", stopwatch.ElapsedMilliseconds);

                    return(Json(jsonTree, JsonRequestBehavior.AllowGet));
                }
                catch (Exception exception)
                {
                    OpcSessionHelper.Instance.Disconnect(Session.SessionID);
                    if (!retry)
                    {
                        return(Content(CreateOpcExceptionActionString(exception)));
                    }
                    retry = false;
                }
            }
        }
 /// <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);
 }