mirror of
https://github.com/nasa/openmct.git
synced 2025-04-09 20:31:26 +00:00
[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:
parent
0bacc03e58
commit
51de44a3e5
2
platform/telemetry/README.md
Normal file
2
platform/telemetry/README.md
Normal 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.
|
34
platform/telemetry/bundle.json
Normal file
34
platform/telemetry/bundle.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
38
platform/telemetry/src/Telemetry.js
Normal file
38
platform/telemetry/src/Telemetry.js
Normal 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;
|
||||
}
|
||||
);
|
43
platform/telemetry/src/TelemetryAggregator.js
Normal file
43
platform/telemetry/src/TelemetryAggregator.js
Normal 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;
|
||||
}
|
||||
);
|
70
platform/telemetry/src/TelemetryCapability.js
Normal file
70
platform/telemetry/src/TelemetryCapability.js
Normal 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;
|
||||
}
|
||||
);
|
209
platform/telemetry/src/TelemetryController.js
Normal file
209
platform/telemetry/src/TelemetryController.js
Normal 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;
|
||||
}
|
||||
);
|
73
platform/telemetry/src/TelemetryHelper.js
Normal file
73
platform/telemetry/src/TelemetryHelper.js
Normal 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;
|
||||
}
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user