diff --git a/src/MCT.js b/src/MCT.js index 3bfe5417a1..32380baf88 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -192,7 +192,7 @@ define([ * @memberof module:openmct.MCT# * @name telemetry */ - this.telemetry = new api.TelemetryAPI(); + this.telemetry = new api.TelemetryAPI(this); this.TimeConductor = this.conductor; // compatibility for prototype this.on('navigation', this.selection.clear.bind(this.selection)); diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 6e310b1aa8..086decd9e8 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -21,9 +21,13 @@ *****************************************************************************/ define([ + './TelemetryMetadataManager', + './TelemetryValueFormatter', 'lodash', 'EventEmitter' ], function ( + TelemetryMetadataManager, + TelemetryValueFormatter, _, EventEmitter ) { @@ -155,9 +159,13 @@ define([ * @augments module:openmct.TelemetryAPI~TelemetryProvider * @memberof module:openmct */ - function TelemetryAPI() { + function TelemetryAPI(MCT) { + this.MCT = MCT; this.providersByStrategy = {}; this.defaultProviders = []; + this.metadataCache = new WeakMap(); + this.formatMapCache = new WeakMap(); + this.valueFormatterCache = new WeakMap(); } /** @@ -240,6 +248,80 @@ define([ 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)) { + this.metadataCache.set( + domainObject, + new TelemetryMetadataManager(domainObject) + ); + } + 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.getValueMetadatasForHints(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} + */ + 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. * The callback will be called whenever data is received from a diff --git a/src/api/telemetry/TelemetryMetadataManager.js b/src/api/telemetry/TelemetryMetadataManager.js new file mode 100644 index 0000000000..ca3be1519f --- /dev/null +++ b/src/api/telemetry/TelemetryMetadataManager.js @@ -0,0 +1,142 @@ +/***************************************************************************** + * 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 = []; + + metadata.domains.forEach(function (domain, index) { + var valueMetadata = _.clone(domain); + valueMetadata.hints = { + x: index, + domain: index + }; + valueMetadatas.push(valueMetadata); + }); + + metadata.ranges.forEach(function (range, index) { + var valueMetadata = _.clone(range); + valueMetadata.hints = { + y: index, + range: index, + priority: index + metadata.domains.length + }; + + 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) { + this.metadata = domainObject.telemetry || {}; + + if (this.metadata.values) { + this.valueMetadatas = this.metadata.values; + } else { + 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) { + 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; + +}); diff --git a/src/api/telemetry/TelemetryValueFormatter.js b/src/api/telemetry/TelemetryValueFormatter.js new file mode 100644 index 0000000000..432da7dc81 --- /dev/null +++ b/src/api/telemetry/TelemetryValueFormatter.js @@ -0,0 +1,92 @@ +/***************************************************************************** + * 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; +});