[Telemetry] Bring in telemetry bundle

Bring in initial state for the platform/telemetry
bundle, which provides general-purpose telemetry
conventions and infrastructure to support creating
domain objects which expose telemetry data.
WTD-575.
This commit is contained in:
Victor Woeltjen 2014-11-28 16:06:54 -08:00
parent 0bacc03e58
commit 51de44a3e5
7 changed files with 469 additions and 0 deletions

View File

@ -0,0 +1,2 @@
This bundle is responsible for introducing a reusable infrastructure
and set of APIs for using time-series data in Open MCT Web.

View File

@ -0,0 +1,34 @@
{
"name": "Data bundle",
"description": "Interfaces and infrastructure for real-time and historical data.",
"extensions": {
"components": [
{
"provides": "telemetryService",
"type": "aggregator",
"implementation": "TelemetryAggregator.js",
"depends": [ "$q" ]
}
],
"controllers": [
{
"key": "TelemetryController",
"implementation": "TelemetryController.js",
"depends": [ "$scope", "$q", "$timeout", "$log" ]
}
],
"capabilities": [
{
"key": "telemetry",
"implementation": "TelemetryCapability.js",
"depends": [ "telemetryService" ]
}
],
"services": [
{
"key": "telemetryHelper",
"implementation": "TelemetryHelper.js"
}
]
}
}

View File

@ -0,0 +1,38 @@
/*global define,Promise*/
/**
* Module defining Telemetry. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function Telemetry(array, defaults) {
// Assume array-of-arrays if not otherwise specified,
// where first value is x, second is y
defaults = defaults || {
domain: 0,
range: 1
};
return {
getPointCount: function () {
return array.length;
},
getRangeValue: function (index, range) {
return array[index][range || defaults.range];
},
getDomainValue: function (index, domain) {
return array[index][domain || defaults.domain];
}
};
}
return Telemetry;
}
);

View File

@ -0,0 +1,43 @@
/*global define,Promise*/
/**
* Module defining TelemetryProvider. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function TelemetryAggregator($q, telemetryProviders) {
function mergeResults(results) {
var merged = {};
results.forEach(function (result) {
Object.keys(result).forEach(function (k) {
// Otherwise, just take the result
merged[k] = result[k];
});
});
return merged;
}
function requestTelemetry(requests) {
return $q.all(telemetryProviders.map(function (provider) {
return provider.requestTelemetry(requests);
})).then(mergeResults);
}
return {
requestTelemetry: requestTelemetry
};
}
return TelemetryAggregator;
}
);

View File

@ -0,0 +1,70 @@
/*global define,Promise*/
/**
* Module defining TelemetryCapability. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function TelemetryCapability(telemetryService, domainObject) {
function buildRequest(request) {
var type = domainObject.getCapability("type"),
typeRequest = type.getDefinition().telemetry || {},
modelTelemetry = domainObject.getModel().telemetry,
fullRequest = Object.create(typeRequest);
Object.keys(modelTelemetry).forEach(function (k) {
fullRequest[k] = modelTelemetry[k];
});
Object.keys(request).forEach(function (k) {
fullRequest[k] = request[k];
});
// Include domain object ID, at minimum
if (!fullRequest.id) {
fullRequest.id = domainObject.getId();
}
if (!fullRequest.key) {
fullRequest.key = domainObject.getId();
}
return fullRequest;
}
function requestTelemetry(request) {
// Bring in any defaults from the object model
var fullRequest = buildRequest(request || {}),
source = fullRequest.source,
key = fullRequest.key;
function getRelevantResponse(response) {
return (response[source] || {})[key] || {};
}
return telemetryService.requestTelemetry([fullRequest])
.then(getRelevantResponse);
}
return {
requestData: requestTelemetry,
getMetadata: function () {
return buildRequest({});
}
//subscribe: subscribe
};
}
TelemetryCapability.appliesTo = function (model) {
return model.telemetry;
}
return TelemetryCapability;
}
);

View File

@ -0,0 +1,209 @@
/*global define,Promise*/
/**
* 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, use telemetry controls.
*
* @constructor
*/
function TelemetryController($scope, $q, $timeout, $log) {
/*
Want a notion of "the data set": All the latest data.
It can look like:
{
"source": {
"key": {
...Telemetry object...
}
}
}
Then, telemetry controller should provide:
{
// Element for which there is telemetry data available
elements: [ { <-- the objects to view
name: ...human-readable
metadata: ...
object: ...the domain object
data: ...telemetry data for that element
} ]
}
*/
var self = {
ids: [],
response: {},
request: {},
pending: 0,
metadatas: [],
interval: 1000,
refreshing: false,
broadcasting: false
};
function doBroadcast() {
if (!self.broadcasting) {
self.broadcasting = true;
$timeout(function () {
self.broadcasting = false;
$scope.$broadcast("telemetryUpdate");
});
}
}
function requestTelemetryForId(id, trackPending) {
var responseObject = self.response[id],
domainObject = responseObject.domainObject,
telemetry = domainObject.getCapability('telemetry');
function storeData(data) {
self.pending -= trackPending ? 1 : 0;
responseObject.data = data;
doBroadcast();
}
self.pending += trackPending ? 1 : 0;
if (!telemetry) {
$log.warn([
"Expected telemetry capability for ",
id,
" but found none. Cannot request data."
].join(""));
return;
}
return $q.when(telemetry.requestData(self.request))
.then(storeData);
}
function requestTelemetry(trackPending) {
return $q.all(self.ids.map(function (id) {
return requestTelemetryForId(id, trackPending);
}));
}
function promiseRelevantDomainObjects() {
var domainObject = $scope.domainObject;
if (!domainObject) {
return $q.when([]);
}
return $q.when(domainObject.useCapability(
"delegation",
"telemetry"
)).then(function (result) {
var head = domainObject.hasCapability("telemetry") ?
[ domainObject ] : [],
tail = result || [];
return head.concat(tail);
});
}
function buildResponseContainer(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability("telemetry"),
metadata;
if (telemetry) {
metadata = telemetry.getMetadata();
self.response[domainObject.getId()] = {
name: domainObject.getModel().name,
domainObject: domainObject,
metadata: metadata,
pending: 0,
data: {}
};
} else {
$log.warn([
"Expected telemetry capability for ",
domainObject.getId(),
" but none was found."
].join(""));
}
}
function buildResponseContainers(domainObjects) {
domainObjects.forEach(buildResponseContainer);
self.ids = domainObjects.map(function (obj) {
return obj.getId();
});
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);
}
}
function getTelemetryObjects() {
promiseRelevantDomainObjects().then(buildResponseContainers);
}
function startTimeout() {
if (!self.refreshing && self.interval !== undefined) {
self.refreshing = true;
$timeout(function () {
if (self.request) {
requestTelemetry(false);
}
self.refreshing = false;
startTimeout();
}, 1000);
}
}
$scope.$watch("domainObject", getTelemetryObjects);
startTimeout(); // Begin refreshing
return {
getMetadata: function () {
return self.metadatas;
},
getTelemetryObjects: function () {
return self.ids.map(function (id) {
return self.response[id].domainObject;
});
},
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);
},
isRequestPending: function () {
return self.pending > 0;
},
requestData: function (request) {
self.request = request || {};
return requestTelemetry(true);
},
setRefreshInterval: function (durationMillis) {
self.interval = durationMillis;
startTimeout();
}
};
}
return TelemetryController;
}
);

View File

@ -0,0 +1,73 @@
/*global define,Float32Array,Promise*/
/**
* Module defining TelemetryHelper. Created by vwoeltje on 11/14/14.
*/
define(
[],
function () {
"use strict";
/**
* Helps to interpret the contents of Telemetry objects.
* @constructor
*/
function TelemetryHelper() {
return {
getBufferedForm: function (telemetry, start, end, domain, range) {
var arr = [],
domainMin = Number.MAX_VALUE,
rangeMin = Number.MAX_VALUE,
domainMax = Number.MIN_VALUE,
rangeMax = Number.MIN_VALUE,
domainValue,
rangeValue,
count,
i;
function trackBounds(domainValue, rangeValue) {
domainMin = Math.min(domainMin, domainValue);
domainMax = Math.max(domainMax, domainValue);
rangeMin = Math.min(rangeMin, rangeValue);
rangeMax = Math.max(rangeMax, rangeValue);
}
function applyOffset() {
var j;
for (j = 0; j < arr.length; j += 2) {
arr[j] -= domainMin;
arr[j + 1] -= rangeMin;
}
}
count = telemetry.getPointCount();
if (start === undefined) {
start = telemetry.getDomainValue(0, domain);
}
if (end === undefined) {
end = telemetry.getDomainValue(count - 1, domain);
}
for (i = 0; i < telemetry.getPointCount(); i += 1) {
// TODO: Binary search for start, end
domainValue = telemetry.getDomainValue(i, domain);
if (domainValue >= start && domainValue <= end) {
rangeValue = telemetry.getRangeValue(i, range);
arr.push(domainValue);
arr.push(rangeValue);
trackBounds(domainValue, rangeValue);
}
}
applyOffset();
}
};
}
return TelemetryHelper;
}
);