From 1b17c88f1fd76edcb17ccab341c72fc9464aa359 Mon Sep 17 00:00:00 2001 From: Michael Joyce <Michael.J.Joyce@jpl.nasa.gov> Date: Thu, 27 Sep 2018 16:29:11 -0700 Subject: [PATCH] Issue #88 - Add support for integration with OpenMCT --- ait/gui/__init__.py | 43 ++++ ait/gui/openmct_adapter/ait_integration.js | 188 ++++++++++++++++++ .../example_ait_telem_fetch.js | 76 +++++++ 3 files changed, 307 insertions(+) create mode 100644 ait/gui/openmct_adapter/ait_integration.js create mode 100644 ait/gui/openmct_adapter/example_ait_telem_fetch.js diff --git a/ait/gui/__init__.py b/ait/gui/__init__.py index 774c7a74..ece8c1c8 100644 --- a/ait/gui/__init__.py +++ b/ait/gui/__init__.py @@ -732,6 +732,49 @@ def handle(): __setResponseToEventStream() yield 'data: %s\n\n' % json.dumps(msg) +@App.route('/tlm/realtime/openmct') +def handle(): + """Return telemetry packets in realtime to client""" + session = Sessions.create() + pad = bytearray(1) + wsock = bottle.request.environ.get('wsgi.websocket') + + if not wsock: + bottle.abort(400, 'Expected WebSocket request.') + + try: + tlmdict = ait.core.tlm.getDefaultDict() + while not wsock.closed: + try: + uid, data = session.telemetry.popleft(timeout=30) + pkt_defn = None + for k, v in tlmdict.iteritems(): + if v.uid == uid: + pkt_defn = v + break + else: + continue + + wsock.send(json.dumps({ + 'packet': pkt_defn.name, + 'data': ait.core.tlm.Packet(pkt_defn, data=data).toJSON() + })) + + except IndexError: + # If no telemetry has been received by the GUI + # server after timeout seconds, "probe" the client + # websocket connection to make sure it's still + # active and if so, keep it alive. This is + # accomplished by sending a packet with an ID of + # zero and no packet data. Packet ID zero with no + # data is ignored by AIT GUI client-side + # Javascript code. + + if not wsock.closed: + wsock.send(pad + struct.pack('>I', 0)) + except geventwebsocket.WebSocketError: + pass + @App.route('/tlm/realtime') def handle(): diff --git a/ait/gui/openmct_adapter/ait_integration.js b/ait/gui/openmct_adapter/ait_integration.js new file mode 100644 index 00000000..f7fb837a --- /dev/null +++ b/ait/gui/openmct_adapter/ait_integration.js @@ -0,0 +1,188 @@ +/* + * Advanced Multi-Mission Operations System (AMMOS) Instrument Toolkit (AIT) + * Bespoke Link to Instruments and Small Satellites (BLISS) + * + * Copyright 2018, by the California Institute of Technology. ALL RIGHTS + * RESERVED. United States Government Sponsorship acknowledged. Any + * commercial use must be negotiated with the Office of Technology Transfer + * at the California Institute of Technology. + * + * This software may be subject to U.S. export control laws. By accepting + * this software, the user agrees to comply with all applicable U.S. export + * laws and regulations. User has the responsibility to obtain export licenses, + * or other export authority as may be required before exporting such + * information to foreign countries or providing access to foreign persons. + */ + +/* + * Example functions for AIT integration with OpenMCT. The below code can be + * used in place of the examples provided in the OpenMCT Tutorial so that + * AIT telemetry can be viewed in real time. + * + * https://github.com/nasa/openmct-tutorial + * + * Important Notes and Setup: + * - The current AIT backend implementation does not support the OpenMCT + * historical data query implementation. To work around this, ensure + * that the historical data endpoint in OpenMCT returns an empty + * dataset for any queried point. The AITHistoricalTelemetryPlugin + * function calls the default OpenMCT Tutorial historical endpoint. + * - The below example code queries for the AIT telemetry dictionary + * in the getDictionary call so the example implementation follows along + * with the OpenMCT tutorial. The endpoint that it calls can be added + * to the example OpenMCT server file (server.js). The code for this + * can be found in example_ait_telem_fetch.js. + * - Include the below code into OpenMCT by including this file in the + * example index.html file and installing the various components. + * + * <script src="ait_integration.js"></script> + * + * openmct.install(AITIntegration()); + * openmct.install(AITHistoricalTelemetryPlugin()); + * openmct.install(AITRealtimeTelemetryPlugin()); + * + * How to run: + * Assuming the above "Important Notes and Setup" have been handled and + * the installation instructions in the OpenMCT Tutorial have been run: + * + * 1) Run `ait-gui 8081` to start the AIT backend + * 2) Run `npm start` to run the OpenMCT server + * 3) Point your browser to localhost:8080 + */ + +let AIT_HOST = 'localhost' +let AIT_PORT = 8081 +let tlmdict = getDictionary() + +function getDictionary() { + return http.get('/telemdict') + .then(function (result) { + return result.data + }); +}; + +function AITIntegration() { + let objectProvider = { + get: function (identifier) { + return tlmdict.then(function (dictionary) { + if (identifier.key === 'spacecraft') { + return { + identifier: identifier, + name: dictionary.name, + type: 'folder', + location: 'ROOT' + }; + } else { + let measurement = dictionary.measurements.filter(function (m) { + return m.key === identifier.key; + })[0]; + return { + identifier: identifier, + name: measurement.name, + type: 'telemetry', + telemetry: { + values: measurement.values + }, + location: 'taxonomy:spacecraft' + }; + } + }); + } + }; + + let compositionProvider = { + appliesTo: function (domainObject) { + return domainObject.identifier.namespace === 'taxonomy' && + domainObject.type === 'folder'; + }, + load: function (domainObject) { + return tlmdict + .then(function (dictionary) { + return dictionary.measurements.map(function (m) { + return { + namespace: 'taxonomy', + key: m.key + }; + }); + }); + } + }; + + return function install(openmct) { + openmct.objects.addRoot({ + namespace: 'taxonomy', + key: 'spacecraft' + }); + + openmct.objects.addProvider('taxonomy', objectProvider); + + openmct.composition.addProvider(compositionProvider); + + openmct.types.addType('telemetry', { + name: 'Telemetry Point', + description: 'Spacecraft Telemetry point', + cssClass: 'icon-telemetry' + }); + }; +}; + +function AITHistoricalTelemetryPlugin() { + return function install (openmct) { + let provider = { + supportsRequest: function (domainObject) { + return domainObject.type === 'telemetry'; + }, + request: function (domainObject, options) { + let url = '/history/' + domainObject.identifier.key + + '?start=' + options.start + '&end=' + options.end; + + return http.get(url) + .then(function (resp) { + return resp.data + }); + } + }; + + openmct.telemetry.addProvider(provider); + } +}; + +function AITRealtimeTelemetryPlugin() { + return function install(openmct) { + let socket = new WebSocket( + 'ws://' + AIT_HOST + ':' + AIT_PORT + '/tlm/realtime/openmct'); + let listener = {}; + + socket.onmessage = function (event) { + let now = Date.now() + + let data = JSON.parse(event.data) + let packet = data['packet'] + for (let p in data['data']) { + let point = { + 'id': packet + '.' + p, + 'timestamp': Date.now(), + 'value': data['data'][p] + } + + if (listener[point.id]) { + listener[point.id](point); + } + } + }; + + let provider = { + supportsSubscribe: function (domainObject) { + return domainObject.type === 'telemetry'; + }, + subscribe: function (domainObject, callback) { + listener[domainObject.identifier.key] = callback; + return function unsubscribe() { + delete listener[domainObject.identifier.key]; + }; + } + }; + + openmct.telemetry.addProvider(provider); + } +}; diff --git a/ait/gui/openmct_adapter/example_ait_telem_fetch.js b/ait/gui/openmct_adapter/example_ait_telem_fetch.js new file mode 100644 index 00000000..01913f92 --- /dev/null +++ b/ait/gui/openmct_adapter/example_ait_telem_fetch.js @@ -0,0 +1,76 @@ +/* + * Advanced Multi-Mission Operations System (AMMOS) Instrument Toolkit (AIT) + * Bespoke Link to Instruments and Small Satellites (BLISS) + * + * Copyright 2018, by the California Institute of Technology. ALL RIGHTS + * RESERVED. United States Government Sponsorship acknowledged. Any + * commercial use must be negotiated with the Office of Technology Transfer + * at the California Institute of Technology. + * + * This software may be subject to U.S. export control laws. By accepting + * this software, the user agrees to comply with all applicable U.S. export + * laws and regulations. User has the responsibility to obtain export licenses, + * or other export authority as may be required before exporting such + * information to foreign countries or providing access to foreign persons. + */ + +let AIT_HOST = 'localhost' +let AIT_PORT = 8081 + +function AITTlmDict(body) { + let retJSON = JSON.parse(body); + let dict = {} + dict['name'] = 'AIT Telemetry' + dict['key'] = 'ait_telemetry_dictionary' + dict['measurements'] = [] + + Object.keys(retJSON).forEach((packet) => { + let fieldDefns = retJSON[packet]["fields"] + Object.keys(fieldDefns).forEach((key) => { + let fDict = { + 'key': packet + '.' + key, + 'name': packet + ' ' + fieldDefns[key]['name'] + } + + let vals = [ + { + "key": "value", + "name": "Value", + "hints": { + "range": 1 + } + }, + { + "key": "utc", + "source": "timestamp", + "name": "Timestamp", + "format": "utc", + "hints": { + "domain": 1 + } + } + ] + fDict['values'] = vals + dict['measurements'].push(fDict) + }) + }) + return dict +}; + +app.get('/telemdict', function (req, res) { + let url = 'http://' + AIT_HOST + ':' + AIT_PORT + '/tlm/dict' + http.get(url, function(response){ + var body = ''; + + response.on('data', function(chunk){ + body += chunk; + }); + + response.on('end', function() { + let dict = AITTlmDict(body) + res.json(dict) + }); + }).on('error', function(e){ + console.log("Got an error: ", e); + }); +});