Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Woeltjen <victor.woeltjen@nasa.gov>2016-06-08 00:01:08 +0300
committerVictor Woeltjen <victor.woeltjen@nasa.gov>2016-06-08 00:01:08 +0300
commit580a4e52b59e316e3c776a3ccfd0e13030ece68f (patch)
tree1cb62a3ed4d436769ab704a9b7d2534d4784c3cd
parent4ca2f51d5e58236f928652a82ef3d94ac84bcb80 (diff)
parent9c4e17bfab56bf5295b068dfa2dabb067e893b58 (diff)
Merge branch 'api-tutorials' into api-type-drivenapi-type-driven
-rw-r--r--tutorial-server/app.js127
-rw-r--r--tutorial-server/dictionary.json66
-rw-r--r--tutorials/bargraph/bundle.js66
-rw-r--r--tutorials/bargraph/res/templates/bargraph.html35
-rw-r--r--tutorials/bargraph/src/controllers/BarGraphController.js75
-rw-r--r--tutorials/telemetry/bundle.js90
-rw-r--r--tutorials/telemetry/src/ExampleTelemetryInitializer.js47
-rw-r--r--tutorials/telemetry/src/ExampleTelemetryModelProvider.js78
-rw-r--r--tutorials/telemetry/src/ExampleTelemetryProvider.js80
-rw-r--r--tutorials/telemetry/src/ExampleTelemetrySeries.js23
-rw-r--r--tutorials/telemetry/src/ExampleTelemetryServerAdapter.js60
11 files changed, 747 insertions, 0 deletions
diff --git a/tutorial-server/app.js b/tutorial-server/app.js
new file mode 100644
index 000000000..ed4cbc5e0
--- /dev/null
+++ b/tutorial-server/app.js
@@ -0,0 +1,127 @@
+/*global require,process,console*/
+
+var CONFIG = {
+ port: 8081,
+ dictionary: "dictionary.json",
+ interval: 1000
+};
+
+(function () {
+ "use strict";
+
+ var WebSocketServer = require('ws').Server,
+ fs = require('fs'),
+ wss = new WebSocketServer({ port: CONFIG.port }),
+ dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")),
+ spacecraft = {
+ "prop.fuel": 77,
+ "prop.thrusters": "OFF",
+ "comms.recd": 0,
+ "comms.sent": 0,
+ "pwr.temp": 245,
+ "pwr.c": 8.15,
+ "pwr.v": 30
+ },
+ histories = {},
+ listeners = [];
+
+ function updateSpacecraft() {
+ spacecraft["prop.fuel"] = Math.max(
+ 0,
+ spacecraft["prop.fuel"] -
+ (spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0)
+ );
+ spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985
+ + Math.random() * 0.25 + Math.sin(Date.now());
+ spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985;
+ spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3);
+ }
+
+ function generateTelemetry() {
+ var timestamp = Date.now(), sent = 0;
+ Object.keys(spacecraft).forEach(function (id) {
+ var state = { timestamp: timestamp, value: spacecraft[id] };
+ histories[id] = histories[id] || []; // Initialize
+ histories[id].push(state);
+ spacecraft["comms.sent"] += JSON.stringify(state).length;
+ });
+ listeners.forEach(function (listener) {
+ listener();
+ });
+ }
+
+ function update() {
+ updateSpacecraft();
+ generateTelemetry();
+ }
+
+ function handleConnection(ws) {
+ var subscriptions = {}, // Active subscriptions for this connection
+ handlers = { // Handlers for specific requests
+ dictionary: function () {
+ ws.send(JSON.stringify({
+ type: "dictionary",
+ value: dictionary
+ }));
+ },
+ subscribe: function (id) {
+ subscriptions[id] = true;
+ },
+ unsubscribe: function (id) {
+ delete subscriptions[id];
+ },
+ history: function (id) {
+ ws.send(JSON.stringify({
+ type: "history",
+ id: id,
+ value: histories[id]
+ }));
+ }
+ };
+
+ function notifySubscribers() {
+ Object.keys(subscriptions).forEach(function (id) {
+ var history = histories[id];
+ if (history) {
+ ws.send(JSON.stringify({
+ type: "data",
+ id: id,
+ value: history[history.length - 1]
+ }));
+ }
+ });
+ }
+
+ // Listen for requests
+ ws.on('message', function (message) {
+ var parts = message.split(' '),
+ handler = handlers[parts[0]];
+ if (handler) {
+ handler.apply(handlers, parts.slice(1));
+ }
+ });
+
+ // Stop sending telemetry updates for this connection when closed
+ ws.on('close', function () {
+ listeners = listeners.filter(function (listener) {
+ return listener !== notifySubscribers;
+ });
+ });
+
+ // Notify subscribers when telemetry is updated
+ listeners.push(notifySubscribers);
+ }
+
+ update();
+ setInterval(update, CONFIG.interval);
+
+ wss.on('connection', handleConnection);
+
+ console.log("Example spacecraft running on port ");
+ console.log("Press Enter to toggle thruster state.");
+ process.stdin.on('data', function (data) {
+ spacecraft['prop.thrusters'] =
+ (spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF";
+ console.log("Thrusters " + spacecraft["prop.thrusters"]);
+ });
+}());
diff --git a/tutorial-server/dictionary.json b/tutorial-server/dictionary.json
new file mode 100644
index 000000000..645e15012
--- /dev/null
+++ b/tutorial-server/dictionary.json
@@ -0,0 +1,66 @@
+{
+ "name": "Example Spacecraft",
+ "identifier": "sc",
+ "subsystems": [
+ {
+ "name": "Propulsion",
+ "identifier": "prop",
+ "measurements": [
+ {
+ "name": "Fuel",
+ "identifier": "prop.fuel",
+ "units": "kilograms",
+ "type": "float"
+ },
+ {
+ "name": "Thrusters",
+ "identifier": "prop.thrusters",
+ "units": "None",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "Communications",
+ "identifier": "comms",
+ "measurements": [
+ {
+ "name": "Received",
+ "identifier": "comms.recd",
+ "units": "bytes",
+ "type": "integer"
+ },
+ {
+ "name": "Sent",
+ "identifier": "comms.sent",
+ "units": "bytes",
+ "type": "integer"
+ }
+ ]
+ },
+ {
+ "name": "Power",
+ "identifier": "pwr",
+ "measurements": [
+ {
+ "name": "Generator Temperature",
+ "identifier": "pwr.temp",
+ "units": "\u0080C",
+ "type": "float"
+ },
+ {
+ "name": "Generator Current",
+ "identifier": "pwr.c",
+ "units": "A",
+ "type": "float"
+ },
+ {
+ "name": "Generator Voltage",
+ "identifier": "pwr.v",
+ "units": "V",
+ "type": "float"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tutorials/bargraph/bundle.js b/tutorials/bargraph/bundle.js
new file mode 100644
index 000000000..5eb16564c
--- /dev/null
+++ b/tutorials/bargraph/bundle.js
@@ -0,0 +1,66 @@
+define([
+ 'legacyRegistry',
+ './src/controllers/BarGraphController'
+], function (
+ legacyRegistry,
+ BarGraphController
+ ) {
+ legacyRegistry.register("tutorials/bargraph", {
+ "name": "Bar Graph",
+ "description": "Provides the Bar Graph view of telemetry elements.",
+ "extensions": {
+ "views": [
+ {
+ "name": "Bar Graph",
+ "key": "example.bargraph",
+ "glyph": "H",
+ "templateUrl": "templates/bargraph.html",
+ "needs": [ "telemetry" ],
+ "delegation": true,
+ "editable": true,
+ "toolbar": {
+ "sections": [
+ {
+ "items": [
+ {
+ "name": "Low",
+ "property": "low",
+ "required": true,
+ "control": "textfield",
+ "size": 4
+ },
+ {
+ "name": "Middle",
+ "property": "middle",
+ "required": true,
+ "control": "textfield",
+ "size": 4
+ },
+ {
+ "name": "High",
+ "property": "high",
+ "required": true,
+ "control": "textfield",
+ "size": 4
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ],
+ "stylesheets": [
+ {
+ "stylesheetUrl": "css/bargraph.css"
+ }
+ ],
+ "controllers": [
+ {
+ "key": "BarGraphController",
+ "implementation": BarGraphController,
+ "depends": [ "$scope", "telemetryHandler" ]
+ }
+ ]
+ }
+ });
+});
diff --git a/tutorials/bargraph/res/templates/bargraph.html b/tutorials/bargraph/res/templates/bargraph.html
new file mode 100644
index 000000000..a5bfbd3e5
--- /dev/null
+++ b/tutorials/bargraph/res/templates/bargraph.html
@@ -0,0 +1,35 @@
+<div class="example-bargraph" ng-controller="BarGraphController">
+ <div class="example-tick-labels">
+ <div ng-repeat="value in [low, middle, high] track by $index"
+ class="example-tick-label"
+ style="bottom: {{ toPercent(value) }}%">
+ {{value}}
+ </div>
+ </div>
+
+ <div class="example-graph-area">
+ <div ng-repeat="telemetryObject in telemetryObjects"
+ style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
+ class="example-bar-holder">
+ <div class="example-bar"
+ ng-style="{
+ bottom: getBottom(telemetryObject) + '%',
+ top: getTop(telemetryObject) + '%'
+ }">
+ </div>
+ </div>
+ <div style="bottom: {{ toPercent(middle) }}%"
+ class="example-graph-tick">
+ </div>
+ </div>
+
+ <div class="example-bar-labels">
+ <div ng-repeat="telemetryObject in telemetryObjects"
+ style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
+ class="example-bar-holder example-label">
+ <mct-representation key="'label'"
+ mct-object="telemetryObject">
+ </mct-representation>
+ </div>
+ </div>
+</div>
diff --git a/tutorials/bargraph/src/controllers/BarGraphController.js b/tutorials/bargraph/src/controllers/BarGraphController.js
new file mode 100644
index 000000000..1d4a3e474
--- /dev/null
+++ b/tutorials/bargraph/src/controllers/BarGraphController.js
@@ -0,0 +1,75 @@
+define(function () {
+ function BarGraphController($scope, telemetryHandler) {
+ var handle;
+
+ // Expose configuration constants directly in scope
+ function exposeConfiguration() {
+ $scope.low = $scope.configuration.low;
+ $scope.middle = $scope.configuration.middle;
+ $scope.high = $scope.configuration.high;
+ }
+
+ // Populate a default value in the configuration
+ function setDefault(key, value) {
+ if ($scope.configuration[key] === undefined) {
+ $scope.configuration[key] = value;
+ }
+ }
+
+ // Getter-setter for configuration properties (for view proxy)
+ function getterSetter(property) {
+ return function (value) {
+ value = parseFloat(value);
+ if (!isNaN(value)) {
+ $scope.configuration[property] = value;
+ exposeConfiguration();
+ }
+ return $scope.configuration[property];
+ };
+ }
+
+ // Add min/max defaults
+ setDefault('low', -1);
+ setDefault('middle', 0);
+ setDefault('high', 1);
+ exposeConfiguration($scope.configuration);
+
+ // Expose view configuration options
+ if ($scope.selection) {
+ $scope.selection.proxy({
+ low: getterSetter('low'),
+ middle: getterSetter('middle'),
+ high: getterSetter('high')
+ });
+ }
+
+ // Convert value to a percent between 0-100
+ $scope.toPercent = function (value) {
+ var pct = 100 * (value - $scope.low) /
+ ($scope.high - $scope.low);
+ return Math.min(100, Math.max(0, pct));
+ };
+
+ // Get bottom and top (as percentages) for current value
+ $scope.getBottom = function (telemetryObject) {
+ var value = handle.getRangeValue(telemetryObject);
+ return $scope.toPercent(Math.min($scope.middle, value));
+ };
+ $scope.getTop = function (telemetryObject) {
+ var value = handle.getRangeValue(telemetryObject);
+ return 100 - $scope.toPercent(Math.max($scope.middle, value));
+ };
+
+ // Use the telemetryHandler to get telemetry objects here
+ handle = telemetryHandler.handle($scope.domainObject, function () {
+ $scope.telemetryObjects = handle.getTelemetryObjects();
+ $scope.barWidth =
+ 100 / Math.max(($scope.telemetryObjects).length, 1);
+ });
+
+ // Release subscriptions when scope is destroyed
+ $scope.$on('$destroy', handle.unsubscribe);
+ }
+
+ return BarGraphController;
+});
diff --git a/tutorials/telemetry/bundle.js b/tutorials/telemetry/bundle.js
new file mode 100644
index 000000000..306e3737c
--- /dev/null
+++ b/tutorials/telemetry/bundle.js
@@ -0,0 +1,90 @@
+define([
+ 'legacyRegistry',
+ './src/ExampleTelemetryServerAdapter',
+ './src/ExampleTelemetryInitializer',
+ './src/ExampleTelemetryModelProvider'
+], function (
+ legacyRegistry,
+ ExampleTelemetryServerAdapter,
+ ExampleTelemetryInitializer,
+ ExampleTelemetryModelProvider
+) {
+ legacyRegistry.register("tutorials/telemetry", {
+ "name": "Example Telemetry Adapter",
+ "extensions": {
+ "types": [
+ {
+ "name": "Spacecraft",
+ "key": "example.spacecraft",
+ "glyph": "o"
+ },
+ {
+ "name": "Subsystem",
+ "key": "example.subsystem",
+ "glyph": "o",
+ "model": { "composition": [] }
+ },
+ {
+ "name": "Measurement",
+ "key": "example.measurement",
+ "glyph": "T",
+ "model": { "telemetry": {} },
+ "telemetry": {
+ "source": "example.source",
+ "domains": [
+ {
+ "name": "Time",
+ "key": "timestamp"
+ }
+ ]
+ }
+ }
+ ],
+ "roots": [
+ {
+ "id": "example:sc",
+ "priority": "preferred",
+ "model": {
+ "type": "example.spacecraft",
+ "name": "My Spacecraft",
+ "composition": []
+ }
+ }
+ ],
+ "services": [
+ {
+ "key": "example.adapter",
+ "implementation": "ExampleTelemetryServerAdapter.js",
+ "depends": [ "$q", "EXAMPLE_WS_URL" ]
+ }
+ ],
+ "constants": [
+ {
+ "key": "EXAMPLE_WS_URL",
+ "priority": "fallback",
+ "value": "ws://localhost:8081"
+ }
+ ],
+ "runs": [
+ {
+ "implementation": "ExampleTelemetryInitializer.js",
+ "depends": [ "example.adapter", "objectService" ]
+ }
+ ],
+ "components": [
+ {
+ "provides": "modelService",
+ "type": "provider",
+ "implementation": "ExampleTelemetryModelProvider.js",
+ "depends": [ "example.adapter", "$q" ]
+ },
+ {
+ "provides": "telemetryService",
+ "type": "provider",
+ "implementation": "ExampleTelemetryProvider.js",
+ "depends": [ "example.adapter", "$q" ]
+ }
+ ]
+ }
+ });
+});
diff --git a/tutorials/telemetry/src/ExampleTelemetryInitializer.js b/tutorials/telemetry/src/ExampleTelemetryInitializer.js
new file mode 100644
index 000000000..4d53b4179
--- /dev/null
+++ b/tutorials/telemetry/src/ExampleTelemetryInitializer.js
@@ -0,0 +1,47 @@
+define(
+ function () {
+ "use strict";
+
+ var TAXONOMY_ID = "example:sc",
+ PREFIX = "example_tlm:";
+
+ function ExampleTelemetryInitializer(adapter, objectService) {
+ // Generate a domain object identifier for a dictionary element
+ function makeId(element) {
+ return PREFIX + element.identifier;
+ }
+
+ // When the dictionary is available, add all subsystems
+ // to the composition of My Spacecraft
+ function initializeTaxonomy(dictionary) {
+ // Get the top-level container for dictionary objects
+ // from a group of domain objects.
+ function getTaxonomyObject(domainObjects) {
+ return domainObjects[TAXONOMY_ID];
+ }
+
+ // Populate
+ function populateModel(taxonomyObject) {
+ return taxonomyObject.useCapability(
+ "mutation",
+ function (model) {
+ model.name =
+ dictionary.name;
+ model.composition =
+ dictionary.subsystems.map(makeId);
+ }
+ );
+ }
+
+ // Look up My Spacecraft, and populate it accordingly.
+ objectService.getObjects([TAXONOMY_ID])
+ .then(getTaxonomyObject)
+ .then(populateModel);
+ }
+
+ adapter.dictionary().then(initializeTaxonomy);
+ }
+
+ return ExampleTelemetryInitializer;
+ }
+);
diff --git a/tutorials/telemetry/src/ExampleTelemetryModelProvider.js b/tutorials/telemetry/src/ExampleTelemetryModelProvider.js
new file mode 100644
index 000000000..27e4b72de
--- /dev/null
+++ b/tutorials/telemetry/src/ExampleTelemetryModelProvider.js
@@ -0,0 +1,78 @@
+define(
+ function () {
+ "use strict";
+
+ var PREFIX = "example_tlm:",
+ FORMAT_MAPPINGS = {
+ float: "number",
+ integer: "number",
+ string: "string"
+ };
+
+ function ExampleTelemetryModelProvider(adapter, $q) {
+ var modelPromise, empty = $q.when({});
+
+ // Check if this model is in our dictionary (by prefix)
+ function isRelevant(id) {
+ return id.indexOf(PREFIX) === 0;
+ }
+
+ // Build a domain object identifier by adding a prefix
+ function makeId(element) {
+ return PREFIX + element.identifier;
+ }
+
+ // Create domain object models from this dictionary
+ function buildTaxonomy(dictionary) {
+ var models = {};
+
+ // Create & store a domain object model for a measurement
+ function addMeasurement(measurement) {
+ var format = FORMAT_MAPPINGS[measurement.type];
+ models[makeId(measurement)] = {
+ type: "example.measurement",
+ name: measurement.name,
+ telemetry: {
+ key: measurement.identifier,
+ ranges: [{
+ key: "value",
+ name: "Value",
+ units: measurement.units,
+ format: format
+ }]
+ }
+ };
+ }
+
+ // Create & store a domain object model for a subsystem
+ function addSubsystem(subsystem) {
+ var measurements =
+ (subsystem.measurements || []);
+ models[makeId(subsystem)] = {
+ type: "example.subsystem",
+ name: subsystem.name,
+ composition: measurements.map(makeId)
+ };
+ measurements.forEach(addMeasurement);
+ }
+
+ (dictionary.subsystems || []).forEach(addSubsystem);
+
+ return models;
+ }
+
+ // Begin generating models once the dictionary is available
+ modelPromise = adapter.dictionary().then(buildTaxonomy);
+
+ return {
+ getModels: function (ids) {
+ // Return models for the dictionary only when they
+ // are relevant to the request.
+ return ids.some(isRelevant) ? modelPromise : empty;
+ }
+ };
+ }
+
+ return ExampleTelemetryModelProvider;
+ }
+);
diff --git a/tutorials/telemetry/src/ExampleTelemetryProvider.js b/tutorials/telemetry/src/ExampleTelemetryProvider.js
new file mode 100644
index 000000000..e7d71260e
--- /dev/null
+++ b/tutorials/telemetry/src/ExampleTelemetryProvider.js
@@ -0,0 +1,80 @@
+define(
+ ['./ExampleTelemetrySeries'],
+ function (ExampleTelemetrySeries) {
+ "use strict";
+
+ var SOURCE = "example.source";
+
+ function ExampleTelemetryProvider(adapter, $q) {
+ var subscribers = {};
+
+ // Used to filter out requests for telemetry
+ // from some other source
+ function matchesSource(request) {
+ return (request.source === SOURCE);
+ }
+
+ // Listen for data, notify subscribers
+ adapter.listen(function (message) {
+ var packaged = {};
+ packaged[SOURCE] = {};
+ packaged[SOURCE][message.id] =
+ new ExampleTelemetrySeries([message.value]);
+ (subscribers[message.id] || []).forEach(function (cb) {
+ cb(packaged);
+ });
+ });
+
+ return {
+ requestTelemetry: function (requests) {
+ var packaged = {},
+ relevantReqs = requests.filter(matchesSource);
+
+ // Package historical telemetry that has been received
+ function addToPackage(history) {
+ packaged[SOURCE][history.id] =
+ new ExampleTelemetrySeries(history.value);
+ }
+
+ // Retrieve telemetry for a specific measurement
+ function handleRequest(request) {
+ var key = request.key;
+ return adapter.history(key).then(addToPackage);
+ }
+
+ packaged[SOURCE] = {};
+ return $q.all(relevantReqs.map(handleRequest))
+ .then(function () { return packaged; });
+ },
+ subscribe: function (callback, requests) {
+ var keys = requests.filter(matchesSource)
+ .map(function (req) { return req.key; });
+
+ function notCallback(cb) {
+ return cb !== callback;
+ }
+
+ function unsubscribe(key) {
+ subscribers[key] =
+ (subscribers[key] || []).filter(notCallback);
+ if (subscribers[key].length < 1) {
+ adapter.unsubscribe(key);
+ }
+ }
+
+ keys.forEach(function (key) {
+ subscribers[key] = subscribers[key] || [];
+ adapter.subscribe(key);
+ subscribers[key].push(callback);
+ });
+
+ return function () {
+ keys.forEach(unsubscribe);
+ };
+ }
+ };
+ }
+
+ return ExampleTelemetryProvider;
+ }
+);
diff --git a/tutorials/telemetry/src/ExampleTelemetrySeries.js b/tutorials/telemetry/src/ExampleTelemetrySeries.js
new file mode 100644
index 000000000..b2b1c840d
--- /dev/null
+++ b/tutorials/telemetry/src/ExampleTelemetrySeries.js
@@ -0,0 +1,23 @@
+/*global define*/
+
+define(
+ function () {
+ "use strict";
+
+ function ExampleTelemetrySeries(data) {
+ return {
+ getPointCount: function () {
+ return data.length;
+ },
+ getDomainValue: function (index) {
+ return (data[index] || {}).timestamp;
+ },
+ getRangeValue: function (index) {
+ return (data[index] || {}).value;
+ }
+ };
+ }
+
+ return ExampleTelemetrySeries;
+ }
+);
diff --git a/tutorials/telemetry/src/ExampleTelemetryServerAdapter.js b/tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
new file mode 100644
index 000000000..83e8372b8
--- /dev/null
+++ b/tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
@@ -0,0 +1,60 @@
+define(
+ [],
+ function () {
+ "use strict";
+
+ function ExampleTelemetryServerAdapter($q, wsUrl) {
+ var ws = new WebSocket(wsUrl),
+ histories = {},
+ listeners = [],
+ dictionary = $q.defer();
+
+ // Handle an incoming message from the server
+ ws.onmessage = function (event) {
+ var message = JSON.parse(event.data);
+
+ switch (message.type) {
+ case "dictionary":
+ dictionary.resolve(message.value);
+ break;
+ case "history":
+ histories[message.id].resolve(message);
+ delete histories[message.id];
+ break;
+ case "data":
+ listeners.forEach(function (listener) {
+ listener(message);
+ });
+ break;
+ }
+ };
+
+ // Request dictionary once connection is established
+ ws.onopen = function () {
+ ws.send("dictionary");
+ };
+
+ return {
+ dictionary: function () {
+ return dictionary.promise;
+ },
+ history: function (id) {
+ histories[id] = histories[id] || $q.defer();
+ ws.send("history " + id);
+ return histories[id].promise;
+ },
+ subscribe: function (id) {
+ ws.send("subscribe " + id);
+ },
+ unsubscribe: function (id) {
+ ws.send("unsubscribe " + id);
+ },
+ listen: function (callback) {
+ listeners.push(callback);
+ }
+ };
+ }
+
+ return ExampleTelemetryServerAdapter;
+ }
+);