/***************************************************************************** * Open MCT, Copyright (c) 2014-2018, 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. *****************************************************************************/ /** * Module defining TelemetryCapability. Created by vwoeltje on 11/12/14. */ define( [ '../../../src/api/objects/object-utils', 'lodash' ], function ( objectUtils, _ ) { var ZERO = function () { return 0; }, EMPTY_SERIES = { getPointCount: ZERO, getDomainValue: ZERO, getRangeValue: ZERO }; /** * Provides metadata about telemetry associated with a * given domain object. * * @typedef TelemetryMetadata * @property {string} source the machine-readable identifier for * the source of telemetry data for this object; used by * {@link TelemetryService} implementations to determine * whether or not they provide data for this object. * @property {string} key the machine-readable identifier for * telemetry data associated with this specific object, * within that `source`. * @property {TelemetryDomainMetadata[]} domains supported domain * options for telemetry data associated with this object, * to use in interpreting a {@link TelemetrySeries} * @property {TelemetryRangeMetadata[]} ranges supported range * options for telemetry data associated with this object, * to use in interpreting a {@link TelemetrySeries} */ /** * Provides metadata about range options within a telemetry series. * Range options describe distinct properties within any given datum * of a telemetry series; for instance, a telemetry series containing * both raw and uncalibrated values may provide separate ranges for * each. * * @typedef TelemetryRangeMetadata * @property {string} key machine-readable identifier for this range * @property {string} name human-readable name for this range * @property {string} [units] human-readable units for this range * @property {string} [format] data format for this range; usually, * one of `number`, or `string`. If `undefined`, * should presume to be a `number`. Custom formats * may be indicated here. */ /** * Provides metadata about domain options within a telemetry series. * Domain options describe distinct properties within any given datum * of a telemtry series; for instance, a telemetry series containing * both spacecraft event time and earth received times may provide * separate domains for each. * * Domains are typically used to represent timestamps in a telemetry * series, but more generally may express any property which will * have unique values for each datum in a series. It is this property * which makes domains distinct from ranges, as it makes these values * appropriate and meaningful for use to sort and bound a series. * * @typedef TelemetryDomainMetadata * @property {string} key machine-readable identifier for this range * @property {string} name human-readable name for this range * @property {string} [system] machine-readable identifier for the * time/date system associated with this domain; * used by {@link DateService} */ /** * A telemetry capability provides a means of requesting telemetry * for a specific object, and for unwrapping the response (to get * at the specific data which is appropriate to the domain object.) * * @memberof platform/telemetry * @implements {Capability} * @constructor */ function TelemetryCapability(openmct, $injector, $q, $log, domainObject) { // We could depend on telemetryService directly, but // there isn't a platform implementation of this. this.initializeTelemetryService = function () { try { return (this.telemetryService = $injector.get("telemetryService")); } catch (e) { $log.info("Telemetry service unavailable"); return (this.telemetryService = null); } }; this.openmct = openmct; this.$q = $q; this.$log = $log; this.domainObject = domainObject; } // Build a request object. This takes the request that was // passed to the capability, and adds source, id, and key // fields associated with the object (from its type definition // and/or its model) TelemetryCapability.prototype.buildRequest = function (request) { // Start with any "telemetry" field in type; use that as a // basis for the request. var domainObject = this.domainObject, type = domainObject.getCapability("type"), typeRequest = (type && type.getDefinition().telemetry) || {}, modelTelemetry = domainObject.getModel().telemetry, fullRequest = Object.create(typeRequest), newObject = objectUtils.toNewFormat( domainObject.getModel(), domainObject.getId() ), metadata = this.openmct.telemetry.getMetadata(newObject), bounds, timeSystem; // Add properties from the telemetry field of this // specific domain object. Object.keys(modelTelemetry).forEach(function (k) { fullRequest[k] = modelTelemetry[k]; }); // Add properties from this specific requestData call. Object.keys(request).forEach(function (k) { fullRequest[k] = request[k]; }); // Ensure an ID and key are present if (!fullRequest.id) { fullRequest.id = domainObject.getId(); } if (!fullRequest.key) { fullRequest.key = domainObject.getId(); } if (request.start === undefined && request.end === undefined) { bounds = this.openmct.time.bounds(); fullRequest.start = bounds.start; fullRequest.end = bounds.end; } if (request.domain === undefined) { timeSystem = this.openmct.time.timeSystem(); if (timeSystem !== undefined) { fullRequest.domain = timeSystem.key; } } if (!fullRequest.ranges) { fullRequest.ranges = metadata.valuesForHints(['range']); } if (!fullRequest.domains) { fullRequest.domains = metadata.valuesForHints(['domain']); } return fullRequest; }; function asSeries(telemetry, defaultDomain, defaultRange, sourceMap) { function getValue(index, key) { return telemetry[index][sourceMap[key].source]; } return { getRangeValue: function (index, range) { return getValue(index, range || defaultRange); }, getDomainValue: function (index, domain) { return getValue(index, domain || defaultDomain); }, getPointCount: function () { return telemetry.length; }, getData: function () { return telemetry; } }; } /** * Request telemetry data for this specific domain object. * @param {TelemetryRequest} [request] parameters for this * specific request * @returns {Promise} a promise for the resulting telemetry * object */ TelemetryCapability.prototype.requestData = function requestTelemetry(request) { // Bring in any defaults from the object model var fullRequest = this.buildRequest(request || {}); var source = fullRequest.source; var key = fullRequest.key; var telemetryService = this.telemetryService || this.initializeTelemetryService(); // Lazy initialization var domainObject = objectUtils.toNewFormat(this.domainObject.getModel(), this.domainObject.getId()); var telemetryAPI = this.openmct.telemetry; var metadata = telemetryAPI.getMetadata(domainObject); var defaultDomain = metadata.valuesForHints(['domain'])[0].key; var defaultRange = metadata.valuesForHints(['range'])[0]; defaultRange = defaultRange ? defaultRange.key : undefined; var sourceMap = _.indexBy(metadata.values(), 'key'); var isLegacyProvider = telemetryAPI.findRequestProvider(domainObject) === telemetryAPI.legacyProvider; // Pull out the relevant field from the larger, // structured response. function getRelevantResponse(response) { return ((response || {})[source] || {})[key] || EMPTY_SERIES; } // Issue a request to the service function requestTelemetryFromService() { return telemetryService.requestTelemetry([fullRequest]); } if (isLegacyProvider) { // If a telemetryService is not available, // getTelemetryService() should reject, and this should // bubble through subsequent then calls. return telemetryService && requestTelemetryFromService().then(getRelevantResponse); } else { return telemetryAPI.request(domainObject, fullRequest).then(function (telemetry) { return asSeries(telemetry, defaultDomain, defaultRange, sourceMap); }); } }; /** * Get metadata about this domain object's associated * telemetry. * @returns {TelemetryMetadata} metadata about this object's telemetry */ TelemetryCapability.prototype.getMetadata = function () { // metadata just looks like a request, // so use buildRequest to bring in both // type-level and object-level telemetry // properties return (this.metadata = this.metadata || this.buildRequest({})); }; /** * Subscribe to updates to telemetry data for this domain * object. * @param {Function} callback a function to call when new * data becomes available; the telemetry series * containing the data will be given as an argument. * @param {TelemetryRequest} [request] parameters for the * subscription request */ TelemetryCapability.prototype.subscribe = function subscribe(callback, request) { var fullRequest = this.buildRequest(request || {}); var telemetryService = this.telemetryService || this.initializeTelemetryService(); // Lazy initialization var domainObject = objectUtils.toNewFormat(this.domainObject.getModel(), this.domainObject.getId()); var telemetryAPI = this.openmct.telemetry; var metadata = telemetryAPI.getMetadata(domainObject); var defaultDomain = metadata.valuesForHints(['domain'])[0].key; var defaultRange = metadata.valuesForHints(['range'])[0]; defaultRange = defaultRange ? defaultRange.key : undefined; var sourceMap = _.indexBy(metadata.values(), 'key'); var isLegacyProvider = telemetryAPI.findSubscriptionProvider(domainObject) === telemetryAPI.legacyProvider; function update(telemetry) { callback(asSeries([telemetry], defaultDomain, defaultRange, sourceMap)); } // Unpack the relevant telemetry series function updateLegacy(telemetries) { var source = fullRequest.source, key = fullRequest.key, result = ((telemetries || {})[source] || {})[key]; if (result) { callback(result); } } // Avoid a loop here... if (isLegacyProvider) { return telemetryService && telemetryService.subscribe(updateLegacy, [fullRequest]); } else { return telemetryAPI.subscribe(domainObject, update, fullRequest); } }; /** * The telemetry capability is applicable when a * domain object model has a "telemetry" field. */ TelemetryCapability.appliesTo = function (model) { return (model && model.telemetry) ? true : false; }; return TelemetryCapability; } );