openmct/platform/telemetry/src/TelemetryController.js
2015-08-14 16:55:17 -07:00

404 lines
16 KiB
JavaScript

/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining TelemetryController. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
* Serves as a reusable controller for views (or parts of views)
* which need to issue requests for telemetry data and use the
* results
*
* @memberof platform/telemetry
* @constructor
* @deprecated use platform/telemetry.TelemetryHandler instead
*/
function TelemetryController($scope, $q, $timeout, $log) {
// Private to maintain in this scope
var self = {
// IDs of domain objects with telemetry
ids: [],
// Containers for latest responses (id->response)
// Used internally; see buildResponseContainer
// for format
response: {},
// Request fields (id->requests)
request: {},
// Number of outstanding requests
pending: 0,
// Array of object metadatas, for easy retrieval
metadatas: [],
// Interval at which to poll for new data
interval: 1000,
// Flag tracking whether or not a request
// is in progress
refreshing: false,
// Used to track whether a new telemetryUpdate
// is being issued.
broadcasting: false,
// Active subscriptions
subscriptions: [],
// Used for getTelemetryObjects; a reference is
// stored so that this can be called in a watch
telemetryObjects: [],
// Whether or not this controller is active; once
// scope is destroyed, polling should stop.
active: true
};
// Broadcast that a telemetryUpdate has occurred.
function doBroadcast() {
// This may get called multiple times from
// multiple objects, so set a flag to suppress
// multiple simultaneous events from being
// broadcast, then issue the actual broadcast
// later (via $timeout)
if (!self.broadcasting) {
self.broadcasting = true;
$timeout(function () {
self.broadcasting = false;
$scope.$broadcast("telemetryUpdate");
});
}
}
// Issue a request for new telemetry for one of the
// objects being tracked by this controller
function requestTelemetryForId(id, trackPending) {
var responseObject = self.response[id],
domainObject = responseObject.domainObject,
telemetry = domainObject.getCapability('telemetry');
// Callback for when data comes back
function storeData(data) {
self.pending -= trackPending ? 1 : 0;
responseObject.data = data;
doBroadcast();
}
self.pending += trackPending ? 1 : 0;
// Shouldn't happen, but isn't fatal,
// so warn.
if (!telemetry) {
$log.warn([
"Expected telemetry capability for ",
id,
" but found none. Cannot request data."
].join(""));
// Request won't happen, so don't
// mark it as pending.
self.pending -= trackPending ? 1 : 0;
return;
}
// Issue the request using the object's telemetry capability
return $q.when(telemetry.requestData(self.request))
.then(storeData);
}
// Request telemetry for all objects tracked by this
// controller. A flag is passed to indicate whether the
// pending counter should be incremented (this will
// cause isRequestPending() to change, which we only
// want to happen for requests which have originated
// outside of this controller's polling action.)
function requestTelemetry(trackPending) {
return $q.all(self.ids.map(function (id) {
return requestTelemetryForId(id, trackPending);
}));
}
// Subscribe to streaming telemetry updates
function subscribe(telemetryCapability, id) {
return telemetryCapability.subscribe(function () {
requestTelemetryForId(id, false);
});
}
// Stop listening to active subscriptions
function unsubscribe() {
self.subscriptions.forEach(function (s) {
return s && s();
});
self.subscriptions = [];
}
// Look up domain objects which have telemetry capabilities.
// This will either be the object in view, or object that
// this object delegates its telemetry capability to.
function promiseRelevantDomainObjects(domainObject) {
// If object has been cleared, there are no relevant
// telemetry-providing domain objects.
if (!domainObject) {
return $q.when([]);
}
// Otherwise, try delegation first, and attach the
// object itself if it has a telemetry capability.
return $q.when(domainObject.useCapability(
"delegation",
"telemetry"
)).then(function (result) {
var head = domainObject.hasCapability("telemetry") ?
[ domainObject ] : [],
tail = result || [];
return head.concat(tail);
});
}
// Build the response containers that are used internally
// by this controller to track latest responses, etc, for
// a given domain object.
function buildResponseContainer(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability("telemetry"),
id,
metadata;
if (telemetry) {
id = domainObject.getId();
self.subscriptions.push(subscribe(telemetry, id));
metadata = telemetry.getMetadata();
self.response[id] = {
name: domainObject.getModel().name,
domainObject: domainObject,
metadata: metadata,
pending: 0,
data: {}
};
} else {
// Shouldn't happen, as we've checked for
// telemetry capabilities previously, but
// be defensive.
$log.warn([
"Expected telemetry capability for ",
domainObject.getId(),
" but none was found."
].join(""));
// Create an empty container so subsequent
// behavior won't hit an exception.
self.response[domainObject.getId()] = {
name: domainObject.getModel().name,
domainObject: domainObject,
metadata: {},
pending: 0,
data: {}
};
}
}
// Build response containers (as above) for all
// domain objects, and update some controller-internal
// state to support subsequent calls.
function buildResponseContainers(domainObjects) {
// Clear out any existing subscriptions
unsubscribe();
// Build the containers
domainObjects.forEach(buildResponseContainer);
// Store the reference to support getTelemetryObjects
self.telemetryObjects = domainObjects;
// Maintain a list of relevant ids, to convert
// back from dictionary-like container objects to arrays.
self.ids = domainObjects.map(function (obj) {
return obj.getId();
});
// Keep a reference to all applicable metadata
// to return from getMetadata
self.metadatas = self.ids.map(function (id) {
return self.response[id].metadata;
});
// Issue a request for the new objects, if we
// know what our request looks like
if (self.request) {
requestTelemetry(true);
}
}
// Get relevant telemetry-providing domain objects
// for the domain object which is represented in this
// scope. This will be the domain object itself, or
// its telemetry delegates, or both.
function getTelemetryObjects(domainObject) {
unsubscribe();
promiseRelevantDomainObjects(domainObject)
.then(buildResponseContainers);
}
// Handle a polling refresh interval
function startTimeout() {
if (!self.refreshing && self.interval !== undefined) {
self.refreshing = true;
$timeout(function () {
if (self.request) {
requestTelemetry(false);
}
self.refreshing = false;
if (self.active) {
startTimeout();
}
}, self.interval);
}
}
// Stop polling for changes
function deactivate() {
unsubscribe();
self.active = false;
}
// Watch for a represented domain object
$scope.$watch("domainObject", getTelemetryObjects);
// Stop polling when destroyed
$scope.$on("$destroy", deactivate);
// Begin polling for data changes
startTimeout();
return {
/**
* Get all telemetry metadata associated with
* telemetry-providing domain objects managed by
* this controller.
*
* This will ordered in the
* same manner as `getTelemetryObjects()` or
* `getResponse()`; that is, the metadata at a
* given index will correspond to the telemetry-providing
* domain object at the same index.
* @returns {Array} an array of metadata objects
* @memberof platform/telemetry.TelemetryController#
*/
getMetadata: function () {
return self.metadatas;
},
/**
* Get all telemetry-providing domain objects managed by
* this controller.
*
* This will ordered in the
* same manner as `getMetadata()` or
* `getResponse()`; that is, the metadata at a
* given index will correspond to the telemetry-providing
* domain object at the same index.
* @returns {DomainObject[]} an array of metadata objects
* @memberof platform/telemetry.TelemetryController#
*/
getTelemetryObjects: function () {
return self.telemetryObjects;
},
/**
* Get the latest telemetry response for a specific
* domain object (if an argument is given) or for all
* objects managed by this controller (if no argument
* is supplied.)
*
* In the first form, this returns a single object; in
* the second form, it returns an array ordered in
* same manner as `getMetadata()` or
* `getTelemetryObjects()`; that is, the telemetry
* response at a given index will correspond to the
* telemetry-providing domain object at the same index.
* @returns {Array} an array of responses
* @memberof platform/telemetry.TelemetryController#
*/
getResponse: function getResponse(arg) {
var id = arg && (typeof arg === 'string' ?
arg : arg.getId());
if (id) {
return (self.response[id] || {}).data;
}
return (self.ids || []).map(getResponse);
},
/**
* Check if the latest request (not counting
* requests from TelemtryController's own polling)
* is still outstanding. Users of the TelemetryController
* may use this method as a condition upon which to
* show user feedback, such as a wait spinner.
*
* @returns {boolean} true if the request is still outstanding
* @memberof platform/telemetry.TelemetryController#
*/
isRequestPending: function () {
return self.pending > 0;
},
/**
* Issue a new data request. This will change the
* request parameters that are passed along to all
* telemetry capabilities managed by this controller.
* @memberof platform/telemetry.TelemetryController#
*/
requestData: function (request) {
self.request = request || {};
return requestTelemetry(true);
},
/**
* Change the interval at which this controller will
* perform its polling activity.
* @param {number} durationMillis the interval at
* which to poll, in milliseconds
* @memberof platform/telemetry.TelemetryController#
*/
setRefreshInterval: function (durationMillis) {
self.interval = durationMillis;
startTimeout();
}
};
}
return TelemetryController;
}
);