Merge pull request #1362 from nasa/open1348

[Telemetry] Added legacy provider to expose old style telemetry providers to users of the new telemetry API
This commit is contained in:
Victor Woeltjen 2016-12-20 09:12:00 -08:00 committed by GitHub
commit 6f417fc4c7
8 changed files with 502 additions and 4 deletions

View File

@ -14,7 +14,8 @@
"nonew": true, "nonew": true,
"predef": [ "predef": [
"define", "define",
"Promise" "Promise",
"WeakMap"
], ],
"shadow": "outer", "shadow": "outer",
"strict": "implied", "strict": "implied",

View File

@ -192,7 +192,7 @@ define([
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name telemetry * @name telemetry
*/ */
this.telemetry = new api.TelemetryAPI(); this.telemetry = new api.TelemetryAPI(this);
this.TimeConductor = this.conductor; // compatibility for prototype this.TimeConductor = this.conductor; // compatibility for prototype
this.on('navigation', this.selection.clear.bind(this.selection)); this.on('navigation', this.selection.clear.bind(this.selection));

View File

@ -0,0 +1,148 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([
'../objects/object-utils'
], function (
utils
) {
/**
* @implements module:openmct.TelemetryAPI~TelemetryProvider
* @constructor
*/
function LegacyTelemetryProvider(instantiate) {
this.instantiate = instantiate;
}
/**
* Can provide telemetry for all objects that have the "telemetry"
* capability
*
* @see module:openmct.TelemetryAPI~TelemetryProvider#canProvideTelemetry
*/
LegacyTelemetryProvider.prototype.canProvideTelemetry = function (domainObject) {
return this.instantiate(utils.toOldFormat(domainObject),
utils.makeKeyString(domainObject.identifier)).hasCapability("telemetry");
};
function createDatum(domainObject, metadata, legacySeries, i) {
var datum = {};
metadata.domains.reduce(function (d, domain) {
d[domain.key] = legacySeries.getDomainValue(i, domain.key);
return d;
}, datum);
metadata.ranges.reduce(function (d, range) {
d[range.key] = legacySeries.getRangeValue(i, range.key);
return d;
}, datum);
datum.name = domainObject.name;
return datum;
}
function adaptSeries(domainObject, metadata, legacySeries) {
var series = [];
for (var i = 0; i < legacySeries.getPointCount(); i++) {
series.push(createDatum(domainObject, metadata, legacySeries, i));
}
return series;
}
/**
* @typedef {object} ConvertedTelemetryObject
* Telemetry data objects are converted from TelemetrySeries. Metadata is used
* to populate the returned object with attributes corresponding to the keys
* of domains and ranges. The attribute values are those returned by calls to
* [TelemetrySeries.getDomainValue()]{@link TelemetrySeries#getDomainValue}
* and [TelemetrySeries.getRangeValue()]{@link TelemetrySeries#getRangeValue}.
*/
/**
* @see module:openmct.TelemetryAPI~TelemetryProvider#request
* @param {module:openmct.DomainObject}
* @param {module:openmct.TelemetryAPI~TelemetryRequest} options
* options for this request. Passed straight through to legacy provider
* @returns {Promise.<ConvertedTelemetryObject[]>} a promise for an array of
* telemetry data.
*/
LegacyTelemetryProvider.prototype.request = function (domainObject, request) {
var oldObject = this.instantiate(utils.toOldFormat(domainObject), utils.makeKeyString(domainObject.identifier));
var capability = oldObject.getCapability("telemetry");
return capability.requestData(request).then(function (telemetrySeries) {
return Promise.resolve(adaptSeries(domainObject, capability.getMetadata(), telemetrySeries));
}).catch(function (error) {
return Promise.reject(error);
});
};
/**
* @callback LegacyTelemetryProvider~SubscribeCallback
* @param {ConvertedTelemetryObject}
*/
/**
* @see module:openmct.TelemetryAPI~TelemetryProvider#request
* @param {module:openmct.DomainObject}
* @param {LegacyTelemetryProvider~SubscribeCallback} callback will be called with a single datum when
* new data is available.
* @param {module:openmct.TelemetryAPI~TelemetryRequest} options
* options for this request. Passed straight through to legacy provider
* @returns {platform|telemetry.TelemetrySubscription|*}
*/
LegacyTelemetryProvider.prototype.subscribe = function (domainObject, callback, request) {
var oldObject = this.instantiate(utils.toOldFormat(domainObject), utils.makeKeyString(domainObject.identifier));
var capability = oldObject.getCapability("telemetry");
function callbackWrapper(series) {
callback(createDatum(domainObject, capability.getMetadata(), series, series.getPointCount() - 1));
}
return capability.subscribe(callbackWrapper, request);
};
LegacyTelemetryProvider.prototype.limitEvaluator = function (domainObject) {
var oldObject = this.instantiate(
utils.toOldFormat(domainObject),
utils.makeKeyString(domainObject.identifier));
var limitEvaluator = oldObject.getCapability("limit");
return {
evaluate: function (datum, property) {
return limitEvaluator.evaluate(datum, property.key);
}
};
};
return function (openmct, instantiate) {
// Push onto the start of the default providers array so that it's
// always the last resort
openmct.telemetry.defaultProviders.unshift(
new LegacyTelemetryProvider(instantiate));
};
});

View File

@ -21,9 +21,13 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'./TelemetryMetadataManager',
'./TelemetryValueFormatter',
'lodash', 'lodash',
'EventEmitter' 'EventEmitter'
], function ( ], function (
TelemetryMetadataManager,
TelemetryValueFormatter,
_, _,
EventEmitter EventEmitter
) { ) {
@ -155,9 +159,13 @@ define([
* @augments module:openmct.TelemetryAPI~TelemetryProvider * @augments module:openmct.TelemetryAPI~TelemetryProvider
* @memberof module:openmct * @memberof module:openmct
*/ */
function TelemetryAPI() { function TelemetryAPI(MCT) {
this.MCT = MCT;
this.providersByStrategy = {}; this.providersByStrategy = {};
this.defaultProviders = []; this.defaultProviders = [];
this.metadataCache = new WeakMap();
this.formatMapCache = new WeakMap();
this.valueFormatterCache = new WeakMap();
} }
/** /**
@ -240,6 +248,85 @@ define([
Promise.reject([]); Promise.reject([]);
}; };
/**
* Get telemetry metadata for a given domain object. Returns a telemetry
* metadata manager which provides methods for interrogating telemetry
* metadata.
*
* @returns {TelemetryMetadataManager}
*/
TelemetryAPI.prototype.getMetadata = function (domainObject) {
if (!this.metadataCache.has(domainObject)) {
if (!this.typeService) {
this.typeService = this.MCT.$injector.get('typeService');
}
this.metadataCache.set(
domainObject,
new TelemetryMetadataManager(domainObject, this.typeService)
);
}
return this.metadataCache.get(domainObject);
};
/**
* Return an array of valueMetadatas that are common to all supplied
* telemetry objects and match the requested hints.
*
*/
TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) {
var options = metadatas.map(function (metadata) {
var values = metadata.valuesForHints(hints);
return _.indexBy(values, 'key');
}).reduce(function (a, b) {
var results = {};
Object.keys(a).forEach(function (key) {
if (b.hasOwnProperty(key)) {
results[key] = a[key];
}
});
return results;
});
var sortKeys = hints.map(function (h) {
return 'hints.' + h;
});
return _.sortByAll(options, sortKeys);
};
/**
* Get a value formatter for a given valueMetadata.
*
* @returns {TelemetryValueFormatter}
*/
TelemetryAPI.prototype.getValueFormatter = function (valueMetadata) {
if (!this.valueFormatterCache.has(valueMetadata)) {
if (!this.formatService) {
this.formatService = this.MCT.$injector.get('formatService');
}
this.valueFormatterCache.set(
valueMetadata,
new TelemetryValueFormatter(valueMetadata, this.formatService)
);
}
return this.valueFormatterCache.get(valueMetadata);
};
/**
* Get a format map of all value formatters for a given piece of telemetry
* metadata.
*
* @returns {Object<String, {TelemetryValueFormatter}>}
*/
TelemetryAPI.prototype.getFormatMap = function (metadata) {
if (!this.formatMapCache.has(metadata)) {
var formatMap = metadata.values().reduce(function (map, valueMetadata) {
map[valueMetadata.key] = this.getValueFormatter(valueMetadata);
return map;
}.bind(this), {});
this.formatMapCache.set(metadata, formatMap);
}
return this.formatMapCache.get(metadata);
};
/** /**
* Subscribe to realtime telemetry for a specific domain object. * Subscribe to realtime telemetry for a specific domain object.
* The callback will be called whenever data is received from a * The callback will be called whenever data is received from a

View File

@ -0,0 +1,152 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([
'lodash'
], function (
_
) {
function valueMetadatasFromOldFormat(metadata) {
var valueMetadatas = [];
valueMetadatas.push({
key: 'name',
name: 'Name'
});
metadata.domains.forEach(function (domain, index) {
var valueMetadata = _.clone(domain);
valueMetadata.hints = {
x: index + 1,
domain: index + 1
};
valueMetadatas.push(valueMetadata);
});
metadata.ranges.forEach(function (range, index) {
var valueMetadata = _.clone(range);
valueMetadata.hints = {
y: index,
range: index,
priority: index + metadata.domains.length + 1
};
if (valueMetadata.type === 'enum') {
valueMetadata.key = 'enum';
valueMetadata.hints.y -= 10;
valueMetadata.hints.range -= 10;
valueMetadata.enumerations =
_.sortBy(valueMetadata.enumerations.map(function (e) {
return {
string: e.string,
value: +e.value
};
}), 'e.value');
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
valueMetadata.max = _.max(valueMetadata.values);
valueMetadata.min = _.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);
});
return valueMetadatas;
}
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints = valueMetadata.hints || {};
if (!valueMetadata.hints.hasOwnProperty('priority')) {
valueMetadata.hints.priority = index;
}
return valueMetadata;
}
/**
* Utility class for handling telemetry metadata for a domain object.
* Wraps old format metadata to new format metadata.
* Provides methods for interrogating telemetry metadata.
*/
function TelemetryMetadataManager(domainObject, typeService) {
this.metadata = domainObject.telemetry || {};
if (this.metadata.values) {
this.valueMetadatas = this.metadata.values;
} else {
var typeMetadata = typeService
.getType(domainObject.type).typeDef.telemetry;
_.extend(this.metadata, typeMetadata);
this.valueMetadatas = valueMetadatasFromOldFormat(this.metadata);
}
this.valueMetadatas = this.valueMetadatas.map(applyReasonableDefaults);
}
/**
* Get value metadata for a single key.
*/
TelemetryMetadataManager.prototype.value = function (key) {
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key;
})[0];
};
/**
* Returns all value metadatas, sorted by priority.
*/
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
/**
* Get an array of valueMetadatas that posess all hints requested.
* Array is sorted based on hint priority.
*
*/
TelemetryMetadataManager.prototype.valuesForHints = function (
hints
) {
function hasHint(hint) {
/*jshint validthis: true */
return this.hints.hasOwnProperty(hint);
}
function hasHints(metadata) {
return hints.every(hasHint, metadata);
}
var matchingMetadata = this.valueMetadatas.filter(hasHints);
var sortedMetadata = _.sortBy(matchingMetadata, function (metadata) {
return hints.map(function (hint) {
return metadata.hints[hint];
});
});
return sortedMetadata;
};
return TelemetryMetadataManager;
});

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([
'lodash'
], function (
_
) {
// TODO: needs reference to formatService;
function TelemetryValueFormatter(valueMetadata, formatService) {
this.valueMetadata = valueMetadata;
this.parseCache = new WeakMap();
this.formatCache = new WeakMap();
try {
this.formatter = formatService
.getFormat(valueMetadata.format, valueMetadata);
} catch (e) {
// TODO: Better formatting
this.formatter = {
parse: function (x) {
return Number(x);
},
format: function (x) {
return x;
},
validate: function (x) {
return true;
}
};
}
if (valueMetadata.type === 'enum') {
this.formatter = {};
this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
vm.byValue[e.value] = e.string;
vm.byString[e.string] = e.value;
return vm;
}, {byValue: {}, byString: {}});
this.formatter.format = function (value) {
return this.enumerations.byValue[value];
}.bind(this);
this.formatter.parse = function (string) {
if (typeof string === "string" && this.enumerations.hasOwnProperty(string)) {
return this.enumerations.byString[string];
}
return Number(string);
}.bind(this);
}
}
TelemetryValueFormatter.prototype.parse = function (datum) {
if (_.isObject(datum)) {
if (!this.parseCache.has(datum)) {
this.parseCache.set(
datum,
this.formatter.parse(datum[this.valueMetadata.source])
);
}
return this.parseCache.get(datum);
}
return this.formatter.parse(datum);
};
TelemetryValueFormatter.prototype.format = function (datum) {
if (_.isObject(datum)) {
if (!this.formatCache.has(datum)) {
this.formatCache.set(
datum,
this.formatter.format(datum[this.valueMetadata.source])
);
}
return this.formatCache.get(datum);
}
return this.formatter.format(datum);
};
return TelemetryValueFormatter;
});

View File

@ -22,12 +22,14 @@
define([ define([
'./TelemetryAPI', './TelemetryAPI',
'./LegacyTelemetryProvider',
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
TelemetryAPI, TelemetryAPI,
LegacyTelemetryProvider,
legacyRegistry legacyRegistry
) { ) {
legacyRegistry.register('api/telemetry-api', { legacyRegistry.register('src/api/telemetry', {
name: 'Telemetry API', name: 'Telemetry API',
description: 'The public Telemetry API', description: 'The public Telemetry API',
extensions: { extensions: {
@ -38,6 +40,14 @@ define([
depends: [ depends: [
'formatService' 'formatService'
] ]
},
{
key: "LegacyTelemetryAdapter",
implementation: LegacyTelemetryProvider,
depends: [
"openmct",
"instantiate"
]
} }
] ]
} }

View File

@ -25,6 +25,7 @@ define([
'../src/adapter/bundle', '../src/adapter/bundle',
'../src/api/objects/bundle', '../src/api/objects/bundle',
'../src/api/telemetry/bundle',
'../example/builtins/bundle', '../example/builtins/bundle',
'../example/composite/bundle', '../example/composite/bundle',
@ -96,6 +97,7 @@ define([
var DEFAULTS = [ var DEFAULTS = [
'src/adapter', 'src/adapter',
'src/api/objects', 'src/api/objects',
'src/api/telemetry',
'platform/framework', 'platform/framework',
'platform/core', 'platform/core',
'platform/representation', 'platform/representation',