From bccd018d97d431f101abcddd9ae7de5de1e72f58 Mon Sep 17 00:00:00 2001 From: Pete Richards Date: Fri, 1 Jul 2016 10:26:49 -0700 Subject: [PATCH] Telemetry Draft --- src/api/telemetry/README.md | 71 +++++++++ src/api/telemetry/TelemetryAPI.js | 239 ++++++++++++++++++++++++++++++ src/api/telemetry/bundle.js | 46 ++++++ 3 files changed, 356 insertions(+) create mode 100644 src/api/telemetry/README.md create mode 100644 src/api/telemetry/TelemetryAPI.js create mode 100644 src/api/telemetry/bundle.js diff --git a/src/api/telemetry/README.md b/src/api/telemetry/README.md new file mode 100644 index 0000000000..b54fd4dcdd --- /dev/null +++ b/src/api/telemetry/README.md @@ -0,0 +1,71 @@ +# Telemetry API - Overview + +The Telemetry API provides basic methods for retrieving historical and realtime telemetry data, retrieving telemetry metadata, and registering additional telemetry providers. + +The Telemetry API also provides a set of helpers built upon these basics-- TelemetryFormatters help you format telemetry values for display purposes, LimitEvaluators help you display evaluate and display alarm states, while TelemetryCollections provide a method for seamlessly combining historical and realtime data, while supporting more advanced client side filtering and interactivity. + + +## Getting Telemetry Data + + +### `MCT.telemetry.request(domainObject, options)` + +Request historical telemetry for a domain object. Options allows you to specify filters (start, end, etc.), sort order, and strategies for retrieving telemetry (aggregation, latest available, etc.). + +Returns a `Promise` for an array of telemetry values. + +### `MCT.telemetry.subscribe(domainObject, callback, options)` + +Subscribe to realtime telemetry for a specific domain object. callback will be called whenever data is received from a realtime provider. Options allows you to specify ??? + +## Understanding Telemetry + +### `MCT.telemetry.getMetadata(domainObject)` + +Retrieve telemetry metadata for a domain object. Telemetry metadata helps you understand the sort of telemetry data a domain object can provide-- for instances, the possible enumerations or states, the units, and more. + +### `MCT.telemetry.Formatter` + +Telemetry formatters help you format telemetry values for display. Under the covers, they use telemetry metadata to interpret your telemetry data, and then they use the format API to format that data for display. + + +### `MCT.telemetry.LimitEvaluator` + +Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API. + +## Adding new telemetry sources + +### `MCT.telemetry.registerProvider(telemetryProvider)` + +Register a telemetry provider with the telemetry service. This allows you to connect alternative telemetry sources to For more information, see the `MCT.telemetry.BaseProvider` + +### `MCT.telemetry.BaseProvider` + +The base provider is a great starting point for developers who would like to implement their own telemetry provider. At the same time, you can implement your own telemetry provider as long as it meets the TelemetryProvider (see other docs). + +## Other tools + +### `MCT.telemetry.TelemetryCollection` + +The TelemetryCollection is a useful tool for building advanced displays. It helps you seamlessly handle both historical and realtime telemetry data, while making it easier to deal with large data sets and interactive displays that need to frequently requery data. + + + +# API Reference (TODO) + +* Telemetry Metadata +* Request Options + -- start + -- end + -- sort + -- ??? + -- strategies -- specify which strategies you want. an array provides for fallback strategies without needing decoration. Design fallbacks into API. + +### `MCT.telemetry.request(domainObject, options)` +### `MCT.telemetry.subscribe(domainObject, callback, options)` +### `MCT.telemetry.getMetadata(domainObject)` +### `MCT.telemetry.Formatter` +### `MCT.telemetry.LimitEvaluator` +### `MCT.telemetry.registerProvider(telemetryProvider)` +### `MCT.telemetry.BaseProvider` +### `MCT.telemetry.TelemetryCollection` diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js new file mode 100644 index 0000000000..662ba7c244 --- /dev/null +++ b/src/api/telemetry/TelemetryAPI.js @@ -0,0 +1,239 @@ +/*global define,window,console,MCT*/ + +/** + +var key = '114ced6c-deb7-4169-ae71-68c571665514'; +MCT.objects.getObject([key]) + .then(function (results) { + console.log('got results'); + return results[key]; + }) + .then(function (domainObject) { + console.log('got object'); + MCT.telemetry.subscribe(domainObject, function (datum) { + console.log('gotData!', datum); + }); + }); +}); + + +*/ + +define([ + 'lodash', + 'eventemitter2' +], function ( + _, + EventEmitter +) { + + // format map is a placeholder until we figure out format service. + var FORMAT_MAP = { + generic: function (range) { + return function (datum) { + return datum[range.key]; + }; + }, + enum: function (range) { + var enumMap = _.indexBy(range.enumerations, 'value'); + return function (datum) { + try { + return enumMap[datum[range.valueKey]].text; + } catch (e) { + return datum[range.valueKey]; + } + }; + } + }; + + FORMAT_MAP.number = + FORMAT_MAP.float = + FORMAT_MAP.integer = + FORMAT_MAP.ascii = + FORMAT_MAP.generic; + + + + function TelemetryAPI( + formatService + ) { + + var FORMATTER_CACHE = new WeakMap(), + EVALUATOR_CACHE = new WeakMap(); + + function testAPI() { + var key = '114ced6c-deb7-4169-ae71-68c571665514'; + window.MCT.objects.getObjects([key]) + .then(function (results) { + console.log('got results'); + return results[key]; + }) + .then(function (domainObject) { + var formatter = new MCT.telemetry.Formatter(domainObject); + console.log('got object'); + window.MCT.telemetry.subscribe(domainObject, function (datum) { + var formattedValues = {}; + Object.keys(datum).forEach(function (key) { + formattedValues[key] = formatter.format(datum, key); + }); + console.log( + 'datum:', + datum, + 'formatted:', + formattedValues + ); + }); + }); + } + + function getFormatter(range) { + if (FORMAT_MAP[range.type]) { + return FORMAT_MAP[range.type](range); + } + try { + var format = formatService.getFormat(range.type).format.bind( + formatService.getFormat(range.type) + ), + formatter = function (datum) { + return format(datum[range.key]); + }; + return formatter; + } catch (e) { + console.log('could not retrieve format', range, e, e.message); + return FORMAT_MAP.generic(range); + } + } + + function TelemetryFormatter(domainObject) { + this.metadata = domainObject.getCapability('telemetry').getMetadata(); + this.formats = {}; + var ranges = this.metadata.ranges.concat(this.metadata.domains); + + ranges.forEach(function (range) { + this.formats[range.key] = getFormatter(range); + }, this); + } + + /** + * Retrieve the 'key' from the datum and format it accordingly to + * telemetry metadata in domain object. + */ + TelemetryFormatter.prototype.format = function (datum, key) { + return this.formats[key](datum); + }; + + function LimitEvaluator(domainObject) { + this.domainObject = domainObject; + this.evaluator = domainObject.getCapability('limit'); + if (!this.evaluator) { + this.evalute = function () { + return ''; + } + } + } + + /** TODO: Do we need a telemetry parser, or do we assume telemetry + is numeric by default? */ + + LimitEvaluator.prototype.evaluate = function (datum, key) { + return this.evaluator.evaluate(datum, key); + }; + + /** Basic telemetry collection, needs more magic. **/ + function TelemetryCollection(domainObject) { + this.domainObject = domainObject; + this.data = []; + } + + _.extend(TelemetryCollection.prototype, EventEmitter.prototype); + + TelemetryCollection.prototype.request = function (options) { + request(this.domainObject, options).then(function (data) { + data.forEach(function (datum) { + this.addDatum(datum); + }, this); + }.bind(this)); + }; + + TelemetryCollection.prototype.addDatum = function (datum) { + this.data.push(datum); + this.emit('add', datum); + }; + + TelemetryCollection.prototype.subscribe = function (options) { + if (this.unsubscribe) { + this.unsubscribe(); + delete this.unsubscribe; + } + + this.unsubscribe = subscribe( + this.domainObject, + function (telemetrySeries) { + telemetrySeries.getData().forEach(this.addDatum, this); + }.bind(this), + options + ); + }; + + function registerProvider(provider) { + // Not yet implemented. + console.log('registering provider', provider); + } + + function registerEvaluator(evaluator) { + // not yet implemented. + console.log('registering evaluator', evaluator); + } + + function request(domainObject, options) { + return domainObject.getCapability('telemetry') + .requestData(options) + .then(function (telemetrySeries) { + return telemetrySeries.getData(); + }); + } + + function subscribe(domainObject, callback, options) { + return domainObject.getCapability('telemetry') + .subscribe(function (series) { + series.getData().forEach(callback); + }, options); + } + + var Telemetry = { + registerProvider: registerProvider, + registerEvaluator: registerEvaluator, + request: request, + subscribe: subscribe, + getMetadata: function (domainObject) { + return domainObject.getCapability('telemetry').getMetadata(); + }, + Formatter: function (domainObject) { + if (!FORMATTER_CACHE.has(domainObject)) { + FORMATTER_CACHE.set( + domainObject, + new TelemetryFormatter(domainObject) + ); + } + return FORMATTER_CACHE.get(domainObject); + }, + LimitEvaluator: function (domainObject) { + if (!EVALUATOR_CACHE.has(domainObject)) { + EVALUATOR_CACHE.set( + domainObject, + new LimitEvaluator(domainObject) + ); + } + return EVALUATOR_CACHE.get(domainObject); + } + }; + + window.MCT = window.MCT || {}; + window.MCT.telemetry = Telemetry; + window.testAPI = testAPI; + + return Telemetry; + } + + return TelemetryAPI; +}); diff --git a/src/api/telemetry/bundle.js b/src/api/telemetry/bundle.js new file mode 100644 index 0000000000..c540c618fb --- /dev/null +++ b/src/api/telemetry/bundle.js @@ -0,0 +1,46 @@ +/***************************************************************************** + * 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*/ + +define([ + './TelemetryAPI', + 'legacyRegistry' +], function ( + TelemetryAPI, + legacyRegistry +) { + legacyRegistry.register('api/telemetry-api', { + name: 'Telemetry API', + description: 'The public Telemetry API', + extensions: { + runs: [ + { + key: "TelemetryAPI", + implementation: TelemetryAPI, + depends: [ + 'formatService' + ] + } + ] + } + }); +});