diff --git a/example/generator/bundle.js b/example/generator/bundle.js index 076a04ac8d..259c5cff15 100644 --- a/example/generator/bundle.js +++ b/example/generator/bundle.js @@ -92,7 +92,10 @@ define([ "features": "creation", "model": { "telemetry": { - "period": 10 + "period": 10, + "amplitude": 1, + "offset": 0, + "dataRateInHz": 1 } }, "telemetry": { @@ -135,6 +138,42 @@ define([ "period" ], "pattern": "^\\d*(\\.\\d*)?$" + }, + { + "name": "Amplitude", + "control": "textfield", + "cssclass": "l-input-sm l-numeric", + "key": "amplitude", + "required": true, + "property": [ + "telemetry", + "amplitude" + ], + "pattern": "^\\d*(\\.\\d*)?$" + }, + { + "name": "Offset", + "control": "textfield", + "cssclass": "l-input-sm l-numeric", + "key": "offset", + "required": true, + "property": [ + "telemetry", + "offset" + ], + "pattern": "^\\d*(\\.\\d*)?$" + }, + { + "name": "Data Rate (hz)", + "control": "textfield", + "cssclass": "l-input-sm l-numeric", + "key": "dataRateInHz", + "required": true, + "property": [ + "telemetry", + "dataRateInHz" + ], + "pattern": "^\\d*(\\.\\d*)?$" } ] } diff --git a/example/generator/src/GeneratorProvider.js b/example/generator/src/GeneratorProvider.js new file mode 100644 index 0000000000..52a9a5646c --- /dev/null +++ b/example/generator/src/GeneratorProvider.js @@ -0,0 +1,83 @@ +/***************************************************************************** + * 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([ + './WorkerInterface' +], function ( + WorkerInterface +) { + + var REQUEST_DEFAULTS = { + amplitude: 1, + period: 10, + offset: 0, + dataRateInHz: 1 + }; + + function GeneratorProvider() { + this.workerInterface = new WorkerInterface(); + } + + GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) { + return domainObject.type === 'generator'; + }; + + GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) { + var props = [ + 'amplitude', + 'period', + 'offset', + 'dataRateInHz' + ]; + + var workerRequest = {}; + + props.forEach(function (prop) { + if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) { + workerRequest[prop] = domainObject.telemetry[prop]; + } + if (request.hasOwnProperty(prop)) { + workerRequest[prop] = request[prop]; + } + if (!workerRequest[prop]) { + workerRequest[prop] = REQUEST_DEFAULTS[prop]; + } + workerRequest[prop] = Number(workerRequest[prop]); + }); + + return workerRequest; + }; + + GeneratorProvider.prototype.request = function (domainObject, request) { + var workerRequest = this.makeWorkerRequest(domainObject, request); + workerRequest.start = request.start; + workerRequest.end = request.end; + return this.workerInterface.request(workerRequest); + }; + + GeneratorProvider.prototype.subscribe = function (domainObject, callback, request) { + var workerRequest = this.makeWorkerRequest(domainObject, request); + return this.workerInterface.subscribe(workerRequest, callback); + }; + + return GeneratorProvider; +}); diff --git a/example/generator/src/SinewaveTelemetryProvider.js b/example/generator/src/SinewaveTelemetryProvider.js index 4097ebc75d..a2ac0c5513 100644 --- a/example/generator/src/SinewaveTelemetryProvider.js +++ b/example/generator/src/SinewaveTelemetryProvider.js @@ -24,96 +24,47 @@ /** * Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14. */ -define( - ["./SinewaveTelemetrySeries"], - function (SinewaveTelemetrySeries) { - "use strict"; +define([ + "./SinewaveTelemetrySeries", + "./GeneratorProvider" +], function ( + SinewaveTelemetrySeries, + GeneratorProvider +) { - /** - * - * @constructor - */ - function SinewaveTelemetryProvider($q, $timeout) { - var subscriptions = [], - generating = false; + function SinewaveTelemetryProvider() { + this.provider = new GeneratorProvider(); + } - // - function matchesSource(request) { - return request.source === "generator"; - } - - // Used internally; this will be repacked by doPackage - function generateData(request) { - return { - key: request.key, - telemetry: new SinewaveTelemetrySeries(request) + SinewaveTelemetryProvider.prototype.requestTelemetry = function (requests) { + if (requests[0].source !== 'generator') { + return Promise.resolve({}); + } + return this.provider.request({}, requests[0]) + .then(function (data) { + var res = { + generator: {} }; - } + res.generator[requests[0].key] = new SinewaveTelemetrySeries(data); + return res; + }); + }; - // - function doPackage(results) { - var packaged = {}; - results.forEach(function (result) { - packaged[result.key] = result.telemetry; - }); - // Format as expected (sources -> keys -> telemetry) - return { generator: packaged }; - } - - function requestTelemetry(requests) { - return $timeout(function () { - return doPackage(requests.filter(matchesSource).map(generateData)); - }, 0); - } - - function handleSubscriptions() { - subscriptions.forEach(function (subscription) { - var requests = subscription.requests; - subscription.callback(doPackage( - requests.filter(matchesSource).map(generateData) - )); - }); - } - - function startGenerating() { - generating = true; - $timeout(function () { - handleSubscriptions(); - if (generating && subscriptions.length > 0) { - startGenerating(); - } else { - generating = false; - } - }, 1000); - } - - function subscribe(callback, requests) { - var subscription = { - callback: callback, - requests: requests - }; - - function unsubscribe() { - subscriptions = subscriptions.filter(function (s) { - return s !== subscription; - }); - } - - subscriptions.push(subscription); - - if (!generating) { - startGenerating(); - } - - return unsubscribe; - } - - return { - requestTelemetry: requestTelemetry, - subscribe: subscribe - }; + SinewaveTelemetryProvider.prototype.subscribe = function (callback, requests) { + if (requests[0].source !== 'generator') { + return function unsubscribe() {}; } - return SinewaveTelemetryProvider; - } -); + function wrapper(data) { + var res = { + generator: {} + }; + res.generator[requests[0].key] = new SinewaveTelemetrySeries(data); + callback(res); + } + + return this.provider.subscribe({}, wrapper, requests[0]); + }; + + return SinewaveTelemetryProvider; +}); diff --git a/example/generator/src/SinewaveTelemetrySeries.js b/example/generator/src/SinewaveTelemetrySeries.js index 7ad281d2dd..f92960e1e8 100644 --- a/example/generator/src/SinewaveTelemetrySeries.js +++ b/example/generator/src/SinewaveTelemetrySeries.js @@ -24,55 +24,52 @@ /** * Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14. */ -define( - ['./SinewaveConstants'], - function (SinewaveConstants) { +define([ + +], function ( + +) { "use strict"; - var ONE_DAY = 60 * 60 * 24, - firstObservedTime = Math.floor(SinewaveConstants.START_TIME / 1000); - - /** - * - * @constructor - */ - function SinewaveTelemetrySeries(request) { - var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0, - latestTime = Math.floor(Date.now() / 1000) - timeOffset, - firstTime = firstObservedTime - timeOffset, - endTime = (request.end !== undefined) ? - Math.floor(request.end / 1000) : latestTime, - count = Math.min(endTime, latestTime) - firstTime, - period = +request.period || 30, - generatorData = {}, - requestStart = (request.start === undefined) ? firstTime : - Math.max(Math.floor(request.start / 1000), firstTime), - offset = requestStart - firstTime; - - if (request.size !== undefined) { - offset = Math.max(offset, count - request.size); + function SinewaveTelemetrySeries(data) { + if (!Array.isArray(data)) { + data = [data]; } - - generatorData.getPointCount = function () { - return count - offset; - }; - - generatorData.getDomainValue = function (i, domain) { - // delta uses the same numeric values as the default domain, - // so it's not checked for here, just formatted for display - // differently. - return (i + offset) * 1000 + firstTime * 1000 - - (domain === 'yesterday' ? (ONE_DAY * 1000) : 0); - }; - - generatorData.getRangeValue = function (i, range) { - range = range || "sin"; - return Math[range]((i + offset) * Math.PI * 2 / period); - }; - - return generatorData; + this.data = data; } + SinewaveTelemetrySeries.prototype.getPointCount = function () { + return this.data.length; + }; + + + SinewaveTelemetrySeries.prototype.getDomainValue = function ( + index, + domain + ) { + domain = domain || 'time'; + + return this.getDatum(index)[domain]; + }; + + SinewaveTelemetrySeries.prototype.getRangeValue = function ( + index, + range + ) { + range = range || 'sin'; + return this.getDatum(index)[range]; + }; + + SinewaveTelemetrySeries.prototype.getDatum = function (index) { + if (index > this.data.length || index < 0) { + throw new Error('IndexOutOfRange: index not available in series.'); + } + return this.data[index]; + }; + + SinewaveTelemetrySeries.prototype.getData = function () { + return this.data; + }; + return SinewaveTelemetrySeries; - } -); +}); diff --git a/example/generator/src/WorkerInterface.js b/example/generator/src/WorkerInterface.js new file mode 100644 index 0000000000..6deca0949f --- /dev/null +++ b/example/generator/src/WorkerInterface.js @@ -0,0 +1,115 @@ +/***************************************************************************** + * 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([ + 'text!./generatorWorker.js', + 'uuid' +], function ( + workerText, + uuid +) { + + var workerBlob = new Blob( + [workerText], + {type: 'application/javascript'} + ); + var workerUrl = URL.createObjectURL(workerBlob); + + function WorkerInterface() { + this.worker = new Worker(workerUrl); + this.worker.onmessage = this.onMessage.bind(this); + this.callbacks = {}; + } + + WorkerInterface.prototype.onMessage = function (message) { + message = message.data; + var callback = this.callbacks[message.id]; + if (callback) { + if (callback(message)) { + delete this.callbacks[message.id]; + } + } + }; + + WorkerInterface.prototype.dispatch = function (request, data, callback) { + var message = { + request: request, + data: data, + id: uuid() + }; + + if (callback) { + this.callbacks[message.id] = callback; + } + + this.worker.postMessage(message); + + return message.id; + }; + + WorkerInterface.prototype.request = function (request) { + var deferred = {}; + var promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + function callback(message) { + if (message.error) { + deferred.reject(message.error); + } else { + deferred.resolve(message.data); + } + return true; + } + + this.dispatch('request', request, callback); + + return promise; + }; + + WorkerInterface.prototype.subscribe = function (request, cb) { + var isCancelled = false; + + var callback = function (message) { + if (isCancelled) { + return true; + } + cb(message.data); + }; + + var messageId = this.dispatch('subscribe', request, callback) + + return function () { + isCancelled = true; + this.dispatch('unsubscribe', { + id: messageId + }); + }.bind(this); + + }; + + + + + return WorkerInterface; +}); diff --git a/example/generator/src/generatorWorker.js b/example/generator/src/generatorWorker.js new file mode 100644 index 0000000000..bb4e55ca4b --- /dev/null +++ b/example/generator/src/generatorWorker.js @@ -0,0 +1,150 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +/*global self*/ + +(function () { + + + var handlers = { + subscribe: onSubscribe, + unsubscribe: onUnsubscribe, + request: onRequest + }; + + var subscriptions = {}; + + function workSubscriptions(timestamp) { + var now = Date.now(); + var nextWork = Math.min.apply(Math, Object.values(subscriptions).map(function (subscription) { + return subscription(now); + })); + var wait = nextWork - now; + if (wait < 0) { + wait = 0; + } + + if (Number.isFinite(wait)) { + setTimeout(workSubscriptions, wait); + } + } + + function onSubscribe(message) { + var data = message.data; + + var start = Date.now(); + var step = 1000 / data.dataRateInHz; + var nextStep = start - (start % step) + step; + + function work(now) { + while (nextStep < now) { + self.postMessage({ + id: message.id, + data: { + utc: nextStep, + yesterday: nextStep - 60*60*24*1000, + delta: 60*60*24*1000, + sin: sin(nextStep, data.period, data.amplitude, data.offset), + cos: cos(nextStep, data.period, data.amplitude, data.offset) + } + }); + nextStep += step; + } + return nextStep; + } + + subscriptions[message.id] = work; + workSubscriptions(); + } + + function onUnsubscribe(message) { + delete subscriptions[message.data.id]; + } + + function onRequest(message) { + var data = message.data; + if (!data.start || !data.end) { + throw new Error('missing start and end!'); + } + + var now = Date.now(); + var start = data.start; + var end = data.end > now ? now : data.end; + var amplitude = data.amplitude; + var period = data.period; + var offset = data.offset; + var dataRateInHz = data.dataRateInHz; + + var step = 1000 / dataRateInHz; + var nextStep = start - (start % step) + step; + + var data = []; + + for (; nextStep < end; nextStep += step) { + data.push({ + utc: nextStep, + yesterday: nextStep - 60*60*24*1000, + delta: 60*60*24*1000, + sin: sin(nextStep, period, amplitude, offset), + cos: cos(nextStep, period, amplitude, offset) + }); + } + self.postMessage({ + id: message.id, + data: data + }); + } + + function cos(timestamp, period, amplitude, offset) { + return amplitude * + Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset; + } + + function sin(timestamp, period, amplitude, offset) { + return amplitude * + Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset; + } + + function sendError(error, message) { + self.postMessage({ + error: error.name + ': ' + error.message, + message: message, + id: message.id + }); + } + + self.onmessage = function handleMessage(event) { + var message = event.data; + var handler = handlers[message.request]; + + if (!handler) { + sendError(new Error('unknown message type'), message); + } else { + try { + handler(message); + } catch (e) { + sendError(e, message); + } + } + }; + +}());