From 459d2be076d8ba6c411ab318bf8823a9303d3c3e Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 31 Oct 2017 14:09:37 -0400 Subject: [PATCH 1/4] FIX: Many fixes and clean up --- .gitignore | 4 + README.md | 2 +- changelog.md | 9 + examples/labstreaminglayer/index.js | 17 +- examples/python/handoff.py | 92 +-- examples/python/index.js | 26 +- examples/python/package.json | 6 +- openBCICyton.js | 5 +- openBCISimulator.js | 497 ++++++++++++++ test/openBCICyton-test.js | 1 + test/openBCISimulator-test.js | 974 ++++++++++++++++++++++++++++ 11 files changed, 1574 insertions(+), 59 deletions(-) create mode 100644 openBCISimulator.js create mode 100644 test/openBCISimulator-test.js diff --git a/.gitignore b/.gitignore index 54ef3d3..dc5d203 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ hardwareVoltageOutputAll.txt # Text editor temporary files .*.sw* # vi/vim + +# For python +examples/python/dist/* +examples/python/openbci_node_python.egg-info/* diff --git a/README.md b/README.md index 8eccfd7..6ab0fac 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Sample properties: * `auxData` (`Buffer` filled with either 2 bytes (if time synced) or 6 bytes (not time synced)) * `stopByte` (`Number` should be `0xCx` where x is 0-15 in hex) * `boardTime` (`Number` the raw board time) -* `timeStamp` (`Number` the `boardTime` plus the NTP calculated offset) +* `timestamp` (`Number` the `boardTime` plus the NTP calculated offset) The power of this module is in using the sample emitter, to be provided with samples to do with as you wish. diff --git a/changelog.md b/changelog.md index c8a29b1..9407e8e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,12 @@ +# 1.0.7 + +### Bug Fixes + +* Add simulator back into this repo +* Fixed bug where timestamp was not calculated with timeOffset, big fail. +* Fixed the `python` example +* Fixed the `labstreaminglayer` example + # 1.0.6 ### Bug Fixes diff --git a/examples/labstreaminglayer/index.js b/examples/labstreaminglayer/index.js index 4fb2517..69345a7 100644 --- a/examples/labstreaminglayer/index.js +++ b/examples/labstreaminglayer/index.js @@ -45,7 +45,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { } // Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties. - timeSyncPossible = ourBoard.usingVersionTwoFirmware(); + timeSyncPossible = ourBoard.usingAtLeastVersionTwoFirmware(); sendToPython({'numChans': numChans, 'sampleRate': ourBoard.sampleRate()}); if (timeSyncPossible) { @@ -82,9 +82,9 @@ const sampleFunc = sample => { }); } - if (sample.timeStamp) { // true after the first successful sync - if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours - console.log(`Bad time sync ${sample.timeStamp}`); + if (sample.timestamp) { // true after the first successful sync + if (sample.timestamp < 10 * 60 * 60 * 1000) { // Less than 10 hours + console.log(`Bad time sync ${sample.timestamp}`); } else { sendToPython({ action: 'process', @@ -127,7 +127,14 @@ function exitHandler (options, err) { if (err) console.log(err.stack); if (options.exit) { if (verbose) console.log('exit'); - ourBoard.disconnect().catch(console.log); + ourBoard.disconnect() + .then(() => { + process.exit(0); + }) + .catch((err) => { + console.log(err); + process.exit(0); + }); } } diff --git a/examples/python/handoff.py b/examples/python/handoff.py index 7cc2a85..2dae749 100644 --- a/examples/python/handoff.py +++ b/examples/python/handoff.py @@ -7,8 +7,8 @@ class Interface: def __init__(self, verbose=False): - context = zmq.Context() - self._socket = context.socket(zmq.PAIR) + self._context = zmq.Context() + self._socket = self._context.socket(zmq.PAIR) self._socket.connect("tcp://localhost:3004") self.verbose = verbose @@ -43,6 +43,13 @@ def recv(self): """ return self._socket.recv() + def close(self): + """ + Closes the zmq context + """ + self._backend.close() + self._context.term() + class RingBuffer(np.ndarray): """A multidimensional ring buffer.""" @@ -73,44 +80,49 @@ def main(argv): interface = Interface(verbose=verbose) # Signal buffer signal = RingBuffer(np.zeros((nb_chan + 1, 2500))) - - while True: - msg = interface.recv() - try: - dicty = json.loads(msg) - action = dicty.get('action') - command = dicty.get('command') - message = dicty.get('message') - - if command == 'sample': - if action == 'process': - # Do sample processing here - try: - if type(message) is not dict: - print "sample is not a dict", message - raise ValueError - # Get keys of sample - data = np.zeros(9) - - data[:-1] = message.get('channelData') - data[-1] = message.get('timeStamp') - - # Add data to end of ring buffer - signal.append(data) - - print message.get('sampleNumber') - except ValueError as e: - print e - elif command == 'status': - if action == 'active': - interface.send(json.dumps({ - 'action': 'alive', - 'command': 'status', - 'message': time.time() * 1000.0 - })) - - except BaseException as e: - print e + try: + while True: + msg = interface.recv() + print "woo" + try: + dicty = json.loads(msg) + action = dicty.get('action') + command = dicty.get('command') + message = dicty.get('message') + + if command == 'sample': + if action == 'process': + # Do sample processing here + try: + if type(message) is not dict: + print "sample is not a dict", message + raise ValueError + # Get keys of sample + data = np.zeros(9) + + data[:-1] = message.get('channelData') + data[-1] = message.get('timeStamp') + + # Add data to end of ring buffer + signal.append(data) + + print message.get('sampleNumber') + except ValueError as e: + print e + elif command == 'status': + if action == 'active': + interface.send(json.dumps({ + 'action': 'alive', + 'command': 'status', + 'message': time.time() * 1000.0 + })) + except KeyboardInterrupt: + print "W: interrupt received, stopping" + print("Python ZMQ Link Clean Up") + interface.close() + raise ValueError("Peace") + except BaseException as e: + print e if __name__ == '__main__': diff --git a/examples/python/index.js b/examples/python/index.js index 2ae7750..3cd799d 100644 --- a/examples/python/index.js +++ b/examples/python/index.js @@ -11,13 +11,13 @@ * then `npm start` */ const portPub = 'tcp://127.0.0.1:3004'; -const zmq = require('zmq-prebuilt'); +const zmq = require('zeromq'); const socket = zmq.socket('pair'); -const simulate = false; // Sends synthetic data +const simulate = true; // Sends synthetic data const debug = false; // Pretty print any bytes in and out... it's amazing... const verbose = true; // Adds verbosity to functions -const Cyton = require('openbci').Cyton; +const Cyton = require('../..'); let ourBoard = new Cyton({ simulate: simulate, // Uncomment to see how it works with simulator! simulatorFirmwareVersion: 'v2', @@ -41,7 +41,8 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { .then(() => { ourBoard.on('ready', () => { // Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties. - timeSyncPossible = ourBoard.usingVersionTwoFirmware(); + timeSyncPossible = ourBoard.usingAtLeastVersionTwoFirmware(); + console.log(`timeSyncPossible: ${timeSyncPossible}`); if (timeSyncPossible) { ourBoard.streamStart() @@ -63,6 +64,8 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { }); const sampleFunc = sample => { + console.log(JSON.stringify(sample)); + if (sample._count % resyncPeriod === 0) { ourBoard.syncClocksFull() .then(syncObj => { @@ -77,9 +80,9 @@ const sampleFunc = sample => { }); } - if (sample.timeStamp) { // true after the first successful sync - if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours - console.log(`Bad time sync ${sample.timeStamp}`); + if (sample.timestamp) { // true after the first successful sync + if (sample.timestamp < 10 * 60 * 60 * 1000) { // Less than 10 hours + console.log(`Bad time sync ${sample.timestamp}`); } else { sendToPython({ action: 'process', @@ -178,7 +181,14 @@ function exitHandler (options, err) { if (err) console.log(err.stack); if (options.exit) { if (verbose) console.log('exit'); - ourBoard.disconnect().catch(console.log); + ourBoard.disconnect() + .then(() => { + process.exit(0); + }) + .catch((err) => { + console.log(err); + process.exit(0); + }); } } diff --git a/examples/python/package.json b/examples/python/package.json index 4ebb27a..e15723e 100644 --- a/examples/python/package.json +++ b/examples/python/package.json @@ -16,14 +16,14 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^2.0.0", - "zmq-prebuilt": "^2.1.0" + "concurrently": "^3.1.0", + "zeromq": "^4.6.0" }, "devEngines": { "node": "<=6.x", "npm": ">=3.x" }, "devDependencies": { - "concurrently": "^3.1.0" + } } diff --git a/openBCICyton.js b/openBCICyton.js index 97b88ba..b65ad1a 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -6,7 +6,7 @@ const OpenBCIUtilities = require('openbci-utilities'); const obciUtils = OpenBCIUtilities.Utilities; const k = OpenBCIUtilities.Constants; const obciDebug = OpenBCIUtilities.Debug; -const OpenBCISimulator = OpenBCIUtilities.Simulator; +const OpenBCISimulator = require('./openBCISimulator'); const Sntp = require('sntp'); const bufferEqual = require('buffer-equal'); const math = require('mathjs'); @@ -1878,7 +1878,7 @@ Cyton.prototype._processBytes = function (data) { this.buffer = obciUtils.stripToEOTBuffer(data); } } else { - if (_.eq(this.getBoardType(), this.options.boardType) && this.options.verbose) { + if (!_.eq(this.getBoardType(), this.options.boardType) && this.options.verbose) { console.log(`Module detected ${this.getBoardType()} board type but you specified ${this.options.boardType}, use 'hardSet' to force the module to correct itself`); } this.curParsingMode = k.OBCIParsingNormal; @@ -2122,6 +2122,7 @@ Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArr } this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; + this._rawDataPacketToSample.timeOffset = this.sync.timeOffsetMaster; if (this.options.verbose) { console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); diff --git a/openBCISimulator.js b/openBCISimulator.js new file mode 100644 index 0000000..cac1aa3 --- /dev/null +++ b/openBCISimulator.js @@ -0,0 +1,497 @@ +'use strict'; +const EventEmitter = require('events').EventEmitter; +const util = require('util'); +const stream = require('stream'); + +const obciUtilities = require('openbci-utilities').Utilities; +const k = require('openbci-utilities').Constants; +const now = require('performance-now'); +const Buffer = require('safe-buffer').Buffer; + +const _options = { + accel: true, + alpha: true, + boardFailure: false, + daisy: false, + daisyCanBeAttached: true, + drift: 0, + firmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2, k.OBCIFirmwareV3], + fragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], + latencyTime: 16, + bufferSize: 4096, + lineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], + sampleRate: 250, + serialPortFailure: false, + verbose: false +}; + +function Simulator (portName, options) { + if (!(this instanceof Simulator)) { + return new Simulator(portName, options); + } + options = options || {}; + let opts = {}; + + stream.Stream.call(this); + + /** Configuring Options */ + let o; + for (o in _options) { + let userValue = options[o]; + delete options[o]; + + if (typeof _options[o] === 'object') { + // an array specifying a list of choices + // if the choice is not in the list, the first one is defaulted to + + if (_options[o].indexOf(userValue) !== -1) { + opts[o] = userValue; + } else { + opts[o] = _options[o][0]; + } + } else { + // anything else takes the user value if provided, otherwise is a default + + if (userValue !== undefined) { + opts[o] = userValue; + } else { + opts[o] = _options[o]; + } + } + } + + for (o in options) throw new Error('"' + o + '" is not a valid option'); + + this.options = opts; + + // Bools + this.connected = false; + this.sd = { + active: false, + startTime: 0 + }; + this.streaming = false; + this.synced = false; + this.sendSyncSetPacket = false; + // Buffers + this.outputBuffer = new Buffer(this.options.bufferSize); + this.outputBuffered = 0; + // Numbers + this.channelNumber = 1; + this.hostChannelNumber = this.channelNumber; + this.pollTime = 80; + this.sampleNumber = -1; // So the first sample is 0 + // Objects + this.sampleGenerator = obciUtilities.randomSample(k.OBCINumberOfChannelsDefault, this.options.sampleRate, this.options.alpha, this.options.lineNoise); + this.time = { + current: 0, + start: now(), + loop: null + }; + // Strings + this.portName = portName || k.OBCISimulatorPortName; + + // Call 'open' + if (this.options.verbose) console.log(`Port name: ${portName}`); + setTimeout(() => { + this.connected = true; + this.emit('open'); + }, 200); +} + +// This allows us to use the emitter class freely outside of the module +util.inherits(Simulator, EventEmitter); + +Simulator.prototype.flush = function (callback) { + this.outputBuffered = 0; + + clearTimeout(this.outputLoopHandle); + this.outputLoopHandle = null; + + if (callback) callback(); +}; + +Simulator.prototype.isOpen = function () { + return this.connected; +}; + +// output only size bytes of the output buffer +Simulator.prototype._partialDrain = function (size) { + if (!this.connected) throw new Error('not connected'); + + if (size > this.outputBuffered) size = this.outputBuffered; + + // buffer is copied because presently openBCICyton.js reuses it + let outBuffer = new Buffer(this.outputBuffer.slice(0, size)); + + this.outputBuffer.copy(this.outputBuffer, 0, size, this.outputBuffered); + this.outputBuffered -= size; + + this.emit('data', outBuffer); +}; + +// queue some data for output and send it out depending on options.fragmentation +Simulator.prototype._output = function (dataBuffer) { + // drain full buffers until there is no overflow + while (this.outputBuffered + dataBuffer.length > this.outputBuffer.length) { + let len = dataBuffer.copy(this.outputBuffer, this.outputBuffered); + dataBuffer = dataBuffer.slice(len); + this.outputBuffered += len; + + this._partialDrain(this.outputBuffered); + this.flush(); + } + + dataBuffer.copy(this.outputBuffer, this.outputBuffered); + this.outputBuffered += dataBuffer.length; + + if (!this.outputLoopHandle) { + let latencyTime = this.options.latencyTime; + if (this.options.fragmentation === k.OBCISimulatorFragmentationOneByOne || + this.options.fragmentation === k.OBCISimulatorFragmentationNone) { + // no need to wait for latencyTime + // note that this is the only difference between 'none' and 'fullBuffers' + latencyTime = 0; + } + let outputLoop = () => { + let size; + switch (this.options.fragmentation) { + case k.OBCISimulatorFragmentationRandom: + if (Math.random() < 0.5) { + // randomly picked to send out a fragment + size = Math.ceil(Math.random() * Math.max(this.outputBuffered, 62)); + break; + } // else, randomly picked to send a complete buffer in next block + /* falls through */ + case k.OBCISimulatorFragmentationFullBuffers: + case k.OBCISimulatorFragmentationNone: + case false: + size = this.outputBuffered; + break; + case k.OBCISimulatorFragmentationOneByOne: + size = 1; + break; + } + this._partialDrain(size); + if (this.outputBuffered) { + this.outputLoopHandle = setTimeout(outputLoop, latencyTime); + } else { + this.outputLoopHandle = null; + } + }; + if (latencyTime === 0) { + outputLoop(); + } else { + this.outputLoopHandle = setTimeout(outputLoop, latencyTime); + } + } +}; + +Simulator.prototype.write = function (data, callback) { + if (!this.connected) { + /* istanbul ignore else */ + if (callback) callback(Error('Not connected')); + else throw new Error('Not connected!'); + return; + } + + // TODO: this function assumes a type of Buffer for radio, and a type of String otherwise + // FIX THIS it makes it unusable outside the api code + switch (data[0]) { + case k.OBCIRadioKey: + this._processPrivateRadioMessage(data); + break; + case k.OBCIStreamStart: + if (!this.stream) this._startStream(); + this.streaming = true; + break; + case k.OBCIStreamStop: + if (this.stream) clearInterval(this.stream); // Stops the stream + this.streaming = false; + break; + case k.OBCIMiscSoftReset: + if (this.stream) clearInterval(this.stream); + this.streaming = false; + this._output(new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 ${this.options.daisy ? `On Daisy ADS1299 Device ID: 0xFFFFF\n` : ``} LIS3DH Device ID: 0x38422 ${this.options.firmwareVersion === k.OBCIFirmwareV2 ? `Firmware: v2.0.0\n` : ``}$$$`)); + break; + case k.OBCISDLogForHour1: + case k.OBCISDLogForHour2: + case k.OBCISDLogForHour4: + case k.OBCISDLogForHour12: + case k.OBCISDLogForHour24: + case k.OBCISDLogForMin5: + case k.OBCISDLogForMin15: + case k.OBCISDLogForMin30: + case k.OBCISDLogForSec14: + // If we are not streaming, then do verbose output + if (!this.streaming) { + this._output(new Buffer('Wiring is correct and a card is present.\nCorresponding SD file OBCI_69.TXT\n$$$')); + } + this.sd.active = true; + this.sd.startTime = now(); + break; + case k.OBCISDLogStop: + if (!this.streaming) { + if (this.SDLogActive) { + this._output(new Buffer(`Total Elapsed Time: ${now() - this.sd.startTime} ms`)); + this._output(new Buffer(`Max write time: ${Math.random() * 500} us`)); + this._output(new Buffer(`Min write time: ${Math.random() * 200} us`)); + this._output(new Buffer(`Overruns: 0`)); + this._printEOT(); + } else { + this._output(new Buffer('No open file to close\n')); + this._printEOT(); + } + } + this.SDLogActive = false; + break; + case k.OBCISyncTimeSet: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + this.synced = true; + setTimeout(() => { + this._output(new Buffer(k.OBCISyncTimeSent)); + this._syncUp(); + }, 10); + } + break; + case k.OBCIChannelMaxNumber8: + if (this.options.daisy) { + this.options.daisy = false; + this._output(new Buffer(k.OBCIChannelMaxNumber8SuccessDaisyRemoved)); + this._printEOT(); + } else { + this._printEOT(); + } + break; + case k.OBCIChannelMaxNumber16: + if (this.options.daisy) { + this._output(new Buffer(k.OBCIChannelMaxNumber16DaisyAlreadyAttached)); + this._printEOT(); + } else { + if (this.options.daisyCanBeAttached) { + this.options.daisy = true; + this._output(new Buffer(k.OBCIChannelMaxNumber16DaisyAttached)); + this._printEOT(); + } else { + this._output(new Buffer(k.OBCIChannelMaxNumber16NoDaisyAttached)); + this._printEOT(); + } + } + break; + case k.OBCIMiscQueryRegisterSettings: + let outputString = k.OBCIRegisterQueryCyton; + if (this.options.daisy) { + outputString += k.OBCIRegisterQueryCytonDaisy; + } + if (this.options.firmwareVersion === k.OBCIFirmwareV3) { + outputString += k.OBCIRegisterQueryAccelerometerFirmwareV3; + } else { + outputString += k.OBCIRegisterQueryAccelerometerFirmwareV1; + } + this._output(Buffer.from(outputString)); + this._printEOT(); + break; + default: + break; + } + + /** Handle Callback */ + if (callback) { + callback(null, 'Success!'); + } +}; + +Simulator.prototype.drain = function (callback) { + if (callback) callback(); +}; + +Simulator.prototype.close = function (callback) { + if (this.connected) { + this.flush(); + + if (this.stream) clearInterval(this.stream); + + this.connected = false; + this.emit('close'); + if (callback) callback(); + } else { + if (callback) callback(Error('Not connected')); + } +}; + +Simulator.prototype._startStream = function () { + let intervalInMS = 1000 / this.options.sampleRate; + + if (intervalInMS < 2) intervalInMS = 2; + + let getNewPacket = sampNumber => { + if (this.options.accel) { + if (this.synced) { + if (this.sendSyncSetPacket) { + this.sendSyncSetPacket = false; + return obciUtilities.convertSampleToPacketAccelTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0)); + } else { + return obciUtilities.convertSampleToPacketAccelTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0)); + } + } else { + return obciUtilities.convertSampleToPacketStandard(this.sampleGenerator(sampNumber)); + } + } else { + if (this.synced) { + if (this.sendSyncSetPacket) { + this.sendSyncSetPacket = false; + return obciUtilities.convertSampleToPacketRawAuxTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); + } else { + return obciUtilities.convertSampleToPacketRawAuxTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); + } + } else { + return obciUtilities.convertSampleToPacketRawAux(this.sampleGenerator(sampNumber), new Buffer([0, 0, 0, 0, 0, 0])); + } + } + }; + + this.stream = setInterval(() => { + this._output(getNewPacket(this.sampleNumber)); + this.sampleNumber++; + }, intervalInMS); +}; + +Simulator.prototype._syncUp = function () { + setTimeout(() => { + this.sendSyncSetPacket = true; + }, 12); // 3 packets later +}; + +Simulator.prototype._printEOT = function () { + this._output(new Buffer('$$$')); +}; + +Simulator.prototype._printFailure = function () { + this._output(new Buffer('Failure: ')); +}; + +Simulator.prototype._printSuccess = function () { + this._output(new Buffer('Success: ')); +}; + +Simulator.prototype._printValidatedCommsTimeout = function () { + this._printFailure(); + this._output(new Buffer('Communications timeout - Device failed to poll Host')); + this._printEOT(); +}; + +Simulator.prototype._processPrivateRadioMessage = function (dataBuffer) { + switch (dataBuffer[1]) { + case k.OBCIRadioCmdChannelGet: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (!this.options.boardFailure) { + this._printSuccess(); + this._output(new Buffer(`Host and Device on Channel Number ${this.channelNumber}`)); + this._output(new Buffer([this.channelNumber])); + this._printEOT(); + } else if (!this.serialPortFailure) { + this._printFailure(); + this._output(new Buffer(`Host on Channel Number ${this.channelNumber}`)); + this._output(new Buffer([this.channelNumber])); + this._printEOT(); + } + } + break; + case k.OBCIRadioCmdChannelSet: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (!this.options.boardFailure) { + if (dataBuffer[2] <= k.OBCIRadioChannelMax) { + this.channelNumber = dataBuffer[2]; + this.hostChannelNumber = this.channelNumber; + this._printSuccess(); + this._output(new Buffer(`Channel Number ${this.channelNumber}`)); + this._output(new Buffer([this.channelNumber])); + this._printEOT(); + } else { + this._printFailure(); + this._output(new Buffer('Verify channel number is less than 25')); + this._printEOT(); + } + } else if (!this.serialPortFailure) { + this._printValidatedCommsTimeout(); + } + } + break; + case k.OBCIRadioCmdChannelSetOverride: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (dataBuffer[2] <= k.OBCIRadioChannelMax) { + if (dataBuffer[2] === this.channelNumber) { + this.options.boardFailure = false; + } else { + this.options.boardFailure = true; + } + this.hostChannelNumber = dataBuffer[2]; + this._printSuccess(); + this._output(new Buffer(`Host override - Channel Number ${this.hostChannelNumber}`)); + this._output(new Buffer([this.hostChannelNumber])); + this._printEOT(); + } else { + this._printFailure(); + this._output(new Buffer('Verify channel number is less than 25')); + this._printEOT(); + } + } + break; + case k.OBCIRadioCmdPollTimeGet: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (!this.options.boardFailure) { + this._printSuccess(); + this._output(new Buffer(`Poll Time ${this.pollTime}`)); + this._output(new Buffer([this.pollTime])); + this._printEOT(); + } else { + this._printValidatedCommsTimeout(); + } + } + break; + case k.OBCIRadioCmdPollTimeSet: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (!this.options.boardFailure) { + this.pollTime = dataBuffer[2]; + this._printSuccess(); + this._output(new Buffer(`Poll Time ${this.pollTime}`)); + this._output(new Buffer([this.pollTime])); + this._printEOT(); + } else { + this._printValidatedCommsTimeout(); + } + } + break; + case k.OBCIRadioCmdBaudRateSetDefault: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + this._printSuccess(); + this._output(new Buffer('Switch your baud rate to 115200')); + this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this + } + break; + case k.OBCIRadioCmdBaudRateSetFast: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + this._printSuccess(); + this._output(new Buffer('Switch your baud rate to 230400')); + this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this + } + break; + case k.OBCIRadioCmdSystemStatus: + if (this.options.firmwareVersion === k.OBCIFirmwareV2) { + if (!this.options.boardFailure) { + this._printSuccess(); + this._output(new Buffer('System is Up')); + this._printEOT(); + } else { + this._printFailure(); + this._output(new Buffer('System is Down')); + this._printEOT(); + } + } + break; + default: + break; + } +}; + +module.exports = Simulator; diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index c3e4e6e..79215bf 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -1893,6 +1893,7 @@ describe('openbci-sdk', function () { ourBoard.once('synced', obj => { expect(obj.timeOffset, 'object timeOffset').to.equal(expectedTimeOffset); expect(ourBoard.sync.timeOffsetMaster, 'master time offset').to.equal(expectedTimeOffset); + expect(ourBoard._rawDataPacketToSample).to.have.property('timeOffset', expectedTimeOffset); done(); }); ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js new file mode 100644 index 0000000..0b57790 --- /dev/null +++ b/test/openBCISimulator-test.js @@ -0,0 +1,974 @@ +'use strict'; +/* global describe, it, after, afterEach, beforeEach */ +const bluebirdChecks = require('./bluebirdChecks'); +const bufferEqual = require('buffer-equal'); +const chai = require('chai'); +const chaiAsPromised = require(`chai-as-promised`); +const dirtyChai = require('dirty-chai'); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const OpenBCISimulator = require('../index').Simulator; +const openBCIUtilities = require('../openBCIUtilities'); +const k = require('../openBCIConstants'); +const Buffer = require('safe-buffer').Buffer; +const _ = require('lodash'); + +chai.use(chaiAsPromised); +chai.use(dirtyChai); + +describe('openBCISimulator', function () { + this.timeout(4000); + let portName = k.OBCISimulatorPortName; + afterEach(() => bluebirdChecks.noPendingPromises(200)); + describe('#constructor', function () { + let simulator; + afterEach(() => { + simulator = null; + }); + after(done => { + setTimeout(() => { + // Since there is a conditional timeout, it's important to wait to start the next test till this ends for sure + done(); + }, 200); // The same amount of time in the simulator + }); + it('constructs with the correct default options', function () { + simulator = new OpenBCISimulator(); + expect(simulator.options.accel).to.be.true(); + expect(simulator.options.alpha).to.be.true(); + expect(simulator.options.boardFailure).to.be.false(); + expect(simulator.options.daisy).to.be.false(); + expect(simulator.options.daisyCanBeAttached).to.be.true(); + expect(simulator.options.drift).to.equal(0); + expect(simulator.options.firmwareVersion).to.equal(k.OBCIFirmwareV1); + expect(simulator.options.lineNoise).to.equal(k.OBCISimulatorLineNoiseHz60); + expect(simulator.options.sampleRate).to.equal(k.OBCISampleRate250); + expect(simulator.options.serialPortFailure).to.be.false(); + expect(simulator.options.verbose).to.be.false(); + expect(simulator.options.fragmentation).to.equal(k.OBCISimulatorFragmentationNone); + expect(simulator.options.latencyTime).to.equal(16); + expect(simulator.options.bufferSize).to.equal(4096); + }); + it('should be able to get into daisy mode', function () { + simulator = new OpenBCISimulator(portName, { + daisy: true + }); + expect(simulator.options.daisy).to.be.true(); + }); + it('should set the correct sample rate in daisy mode, if no sampleRate is provided', function () { + simulator = new OpenBCISimulator(portName, { + daisy: true + }); + expect(simulator.options.sampleRate).to.equal(250); // produce samples at same rate + }); + it('should use provided sample rate even if daisy is true', function () { + simulator = new OpenBCISimulator(portName, { + daisy: true, + sampleRate: 20 + }); + expect(simulator.options.daisy).to.be.true(); + expect(simulator.options.sampleRate).to.equal(20); + }); + it('should be able to put into firmware version 2', function () { + simulator = new OpenBCISimulator(portName, { + firmwareVersion: 'v2' + }); + expect(simulator.options.firmwareVersion).to.equal(k.OBCIFirmwareV2); + }); + it('should be able to simulate board failure', function () { + simulator = new OpenBCISimulator(portName, { + boardFailure: true + }); + expect(simulator.options.boardFailure).to.be.true(); + }); + it('should be able to simulate serial port failure', function () { + simulator = new OpenBCISimulator(portName, { + serialPortFailure: true + }); + expect(simulator.options.serialPortFailure).to.be.true(); + }); + it('can turn 50Hz line noise on', function () { + simulator = new OpenBCISimulator(portName, { + lineNoise: '50Hz' + }); + (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseHz50); + }); + it('can turn no line noise on', function () { + simulator = new OpenBCISimulator(portName, { + lineNoise: 'none' + }); + (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseNone); + }); + it('should not inject alpha if desired', function () { + simulator = new OpenBCISimulator(portName, { + alpha: false + }); + expect(simulator.options.alpha).to.be.false(); + }); + it('should be able to not use the accel', function () { + simulator = new OpenBCISimulator(portName, { + accel: false + }); + expect(simulator.options.accel).to.be.false(); + }); + it('should be able to set positive drift', function () { + simulator = new OpenBCISimulator(portName, { + drift: 1 + }); + expect(simulator.options.drift).to.be.greaterThan(0); + }); + it('should be able to set negative drift', function () { + simulator = new OpenBCISimulator(portName, { + drift: -1 + }); + expect(simulator.options.drift).to.be.lessThan(0); + }); + it('should throw if passed an invalid option', function (done) { + try { + simulator = new OpenBCISimulator(portName, { + foo: 'bar' + }); + done('did not throw'); + } catch (e) { done(); } + }); + }); + describe('#write', function () { + it('should refuse to write when not connected', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); + try { + simulator.write('q'); + done('did not throw on disconnected write'); + } catch (e) { + simulator.write('q', err => { + if (err) { + done(); + } else { + done('did not provide error on disconnected write'); + } + }); + } + }); + }); + describe('#close', function () { + it('should provide an error closing when already closed', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); + simulator.close(err => { + if (err) { + done(); + } else { + done('closed successfully but had no time to open'); + } + }); + }); + }); + describe('query register settings', function () { + it('should send back the register query settings firmware version 1 cyton', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v1' + }); + simulator.once('data', function (data) { + console.log(data.toString()); + expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryAccelerometerFirmwareV1); + done(); + }); + + simulator.once('open', () => { + simulator.write('?'); + }); + }); + it('should send back the register query settings firmware version 3 cyton', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v3' + }); + simulator.once('data', function (data) { + console.log(data.toString()); + expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryAccelerometerFirmwareV3); + done(); + }); + + simulator.once('open', () => { + simulator.write('?'); + }); + }); + it('should send back the register query settings firmware version 1 cyton daisy', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v1', + daisy: true + }); + simulator.once('data', function (data) { + console.log(data.toString()); + expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryCytonDaisy + k.OBCIRegisterQueryAccelerometerFirmwareV1); + done(); + }); + + simulator.once('open', () => { + simulator.write('?'); + }); + }); + it('should send back the register query settings firmware version 3 cyton daisy', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v3', + daisy: true + }); + simulator.once('data', function (data) { + console.log(data.toString()); + expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryCytonDaisy + k.OBCIRegisterQueryAccelerometerFirmwareV3); + done(); + }); + + simulator.once('open', () => { + simulator.write('?'); + }); + }); + }); + describe(`_startStream`, function () { + it('should return a packet with sample data in it', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); + let sampleCounter = 0; + let sampleTestSize = 5; + + let newDataFunc = data => { + if (sampleCounter > sampleTestSize) { + simulator.write(k.OBCIStreamStop); + simulator.removeListener('data', newDataFunc); + const inputObj = { + rawDataPacket: data, + channelSettings: k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton), + scale: true + }; + let sample = openBCIUtilities.parsePacketStandardAccel(inputObj); + expect(sample.channelData).to.not.all.equal(0); + done(); + simulator = null; + } else { + sampleCounter++; + } + }; + simulator.on('data', newDataFunc); + simulator.once('open', () => simulator._startStream()); + }); + it('should return a sync set packet with accel', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); + let sampleCounter = 0; + let sampleTestSize = 5; + + let newDataFunc = data => { + if (sampleCounter === 0) { + // Ensure everything is switched on for this test + simulator.options.accel = true; + simulator.synced = true; + simulator.sendSyncSetPacket = true; + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)); + } else if (sampleCounter === 1) { + // Now this data should be the time sync up packet + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSyncSet)); + // Expect flag to be down + expect(simulator.sendSyncSetPacket).to.be.false(); + } else if (sampleCounter >= sampleTestSize) { + simulator.write(k.OBCIStreamStop); + simulator.removeListener('data', newDataFunc); + simulator = null; + done(); + } else { + // Now this data should be the time sync up packet + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced)); + } + sampleCounter++; + }; + + simulator.on('data', newDataFunc); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + it('should return a sync set packet with raw aux', function (done) { + let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + accel: false + }); + let sampleCounter = 0; + let sampleTestSize = 5; + + let newDataFunc = data => { + if (sampleCounter === 0) { + // Ensure everything is switched on for this test + simulator.synced = true; + simulator.sendSyncSetPacket = true; + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux)); + } else if (sampleCounter === 1) { + // Now this data should be the time sync up packet + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSyncSet)); + // Expect flag to be down + expect(simulator.sendSyncSetPacket).to.be.false(); + } else if (sampleCounter >= sampleTestSize) { + simulator.write(k.OBCIStreamStop); + simulator.removeListener('data', newDataFunc); + simulator = null; + done(); + } else { + // Now this data should be the time sync up packet + expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced)); + } + sampleCounter++; + }; + + simulator.on('data', newDataFunc); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + }); + describe(`firmwareVersion1`, function () { + let simulator; + beforeEach((done) => { + simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v1' + }); + simulator.once('open', done); + }); + afterEach(() => { + simulator = null; + }); + describe('reset', function () { + it('should not be v2', function (done) { + simulator.on('data', function (data) { + console.log(data.toString()); + expect(data.toString().match('v2')).to.equal(null); + done(); + }); + simulator.write(k.OBCIMiscSoftReset); + }); + }); + }); + describe(`firmwareVersion2`, function () { + let simulator; + beforeEach((done) => { + simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { + firmwareVersion: 'v2' + }); + simulator.once('open', done); + }); + afterEach(() => { + simulator.removeAllListeners('data'); + simulator = null; + }); + describe('set max channels', function () { + this.timeout(100); + it('should send nothing if no daisy attached', function (done) { + simulator.options.daisy = false; + simulator.on('data', function (data) { + expect(data.toString().match(k.OBCIParseEOT)).to.not.equal(null); + if (data.toString().match(k.OBCIParseEOT)) { + simulator.removeAllListeners('data'); + done(); + } + }); + simulator.write(k.OBCIChannelMaxNumber8); + }); + it('should send daisy removed if daisy attached', function (done) { + simulator.options.daisy = true; + simulator.on('data', function (data) { + expect(_.eq(data.toString(), 'daisy removed')).to.not.equal(null); + if (data.toString().match(k.OBCIParseEOT)) { + expect(simulator.options.daisy).to.equal(false); + simulator.removeAllListeners('data'); + done(); + } + }); + simulator.write(k.OBCIChannelMaxNumber8); + }); + it('should send just 16 if daisy already attached', function (done) { + simulator.options.daisy = true; + simulator.on('data', function (data) { + expect(data.toString().match(`16${k.OBCIParseEOT}`)).to.not.equal(null); + if (data.toString().match(k.OBCIParseEOT)) { + simulator.removeAllListeners('data'); + done(); + } + }); + simulator.write(k.OBCIChannelMaxNumber16); + }); + it('should send daisy attached if able to attach', function (done) { + simulator.options.daisy = false; + simulator.options.daisyCanBeAttached = true; + simulator.on('data', function (data) { + expect(data.toString().match(/daisy attached16/)).to.not.equal(null); + if (data.toString().match(k.OBCIParseEOT)) { + expect(simulator.options.daisy).to.equal(true); + simulator.removeAllListeners('data'); + done(); + } + }); + simulator.write(k.OBCIChannelMaxNumber16); + }); + it('should send daisy attached if not able to attach', function (done) { + simulator.options.daisy = false; + simulator.options.daisyCanBeAttached = false; + simulator.on('data', function (data) { + expect(data.toString().match(/no daisy to attach!/)).to.not.equal(null); + if (data.toString().match(k.OBCIParseEOT)) { + simulator.removeAllListeners('data'); + done(); + } + }); + simulator.write(k.OBCIChannelMaxNumber16); + }); + }); + describe('reset', function () { + it('should be v2', function (done) { + simulator.on('data', function (data) { + expect(data.toString().match(/v2/)).to.not.equal(null); + done(); + }); + simulator.write(k.OBCIMiscSoftReset); + }); + }); + describe('_processPrivateRadioMessage', function () { + describe('OBCIRadioCmdChannelGet', function () { + it('should emit success if firmware version 2', done => { + simulator.channelNumber = 0; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(0); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])); + }); + it('should emit failure if board failure and host channel number', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + simulator.channelNumber = 9; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + expect(buf[buf.length - 4]).to.equal(9); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])); + }); + }); + describe('OBCIRadioCmdChannelSet', function () { + it('should set the channel number if in bounds', done => { + let newChanNum = 20; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(newChanNum); + expect(simulator.channelNumber).to.equal(newChanNum); + expect(simulator.hostChannelNumber).to.equal(newChanNum); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, newChanNum])); + }); + it('should not set the channel number if out of bounds', done => { + let newChanNum = 26; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, newChanNum])); + }); + it('should emit failure if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, 7])); + }); + }); + describe('OBCIRadioCmdChannelSetOverride', function () { + it('should change just the hosts channel number and not the systems channel number and force a board comms failure', done => { + let systemChannelNumber = 0; + let newHostChannelNumber = 1; + simulator.channelNumber = systemChannelNumber; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(newHostChannelNumber); + expect(simulator.options.boardFailure).to.be.true(); + expect(simulator.channelNumber).to.equal(systemChannelNumber); + expect(simulator.hostChannelNumber).to.equal(newHostChannelNumber); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, newHostChannelNumber])); + }); + it('should change just the hosts channel number and not the systems channel number and fix a board failure', done => { + let systemChannelNumber = 0; + let oldHostChannelNumber = 1; + simulator.channelNumber = systemChannelNumber; + simulator.hostChannelNumber = oldHostChannelNumber; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(systemChannelNumber); + expect(simulator.options.boardFailure).to.be.false(); + expect(simulator.channelNumber).to.equal(systemChannelNumber); + expect(simulator.hostChannelNumber).to.equal(systemChannelNumber); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, systemChannelNumber])); + }); + it('should not set the channel number if out of bounds', done => { + let newChanNum = 26; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, newChanNum])); + }); + }); + describe('OBCIRadioCmdPollTimeGet', function () { + it('should emit success if firmware version 2 with poll time', done => { + let expectedPollTime = 80; + simulator.pollTime = expectedPollTime; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(expectedPollTime); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])); + }); + it('should emit failure if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])); + }); + }); + describe('OBCIRadioCmdPollTimeSet', function () { + it('should set the poll time if in bounds', done => { + let newPollTime = 20; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + expect(buf[buf.length - 4]).to.equal(newPollTime); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, newPollTime])); + }); + it('should emit failure if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, 7])); + }); + }); + describe('OBCIRadioCmdBaudRateSetDefault', function () { + it('should emit success if firmware version 2 with proper baud rate', done => { + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + let eotBuf = new Buffer('$$$'); + let newBaudRateBuf; + for (let i = buf.length; i > 3; i--) { + if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { + newBaudRateBuf = buf.slice(i - 9, i - 3); + break; + } + } + let newBaudRateNum = Number(newBaudRateBuf.toString()); + expect(newBaudRateNum).to.equal(k.OBCIRadioBaudRateDefault); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])); + }); + it('should emit success if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])); + }); + }); + describe('OBCIRadioCmdBaudRateSetFast', function () { + it('should emit success if firmware version 2 with proper baud rate', done => { + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + let eotBuf = new Buffer(`$$$`); + let newBaudRateBuf; + for (let i = buf.length; i > 3; i--) { + if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { + newBaudRateBuf = buf.slice(i - 9, i - 3); + break; + } + } + let newBaudRateNum = Number(newBaudRateBuf.toString()); + expect(newBaudRateNum).to.equal(k.OBCIRadioBaudRateFast); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])); + }); + it('should emit success if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])); + }); + }); + describe('OBCIRadioCmdSystemStatus', function () { + it('should emit success if firmware version 2', done => { + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])); + }); + it('should emit failure if board failure', done => { + // Turn board failure mode + simulator.options.boardFailure = true; + let buf = new Buffer(0); + let dataEmit = data => { + buf = Buffer.concat([buf, data]); + if (openBCIUtilities.doesBufferHaveEOT(buf)) { + expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); + expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); + simulator.removeListener('data', dataEmit); + done(); + } + }; + + simulator.on('data', dataEmit); + + simulator._processPrivateRadioMessage(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])); + }); + }); + }); + }); + describe('fragmentation', function () { + let simulator; + afterEach(done => { + simulator.removeAllListeners(); + simulator.write(k.OBCIStreamStop); + simulator.close(done); + }); + it('Should accumulate packets if set to FullBuffers', function (done) { + let bufferSize = 64; + simulator = new OpenBCISimulator(portName, { + fragmentation: k.OBCISimulatorFragmentationFullBuffers, + bufferSize: bufferSize, + latencyTime: 1000 + }); + simulator.once('data', function (buffer) { + expect(buffer.length).to.equal(bufferSize); + done(); + }); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + it('Should emit partial packets after latencyTime', function (done) { + let bufferSize = 4096; + simulator = new OpenBCISimulator(portName, { + fragmentation: k.OBCISimulatorFragmentationFullBuffers, + bufferSize: 4096, + latencyTime: 0 + }); + simulator.once('data', function (buffer) { + expect(buffer.length).to.be.lessThan(bufferSize); + done(); + }); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + it('Should emit single bytes if set to OneByOne', function (done) { + simulator = new OpenBCISimulator(portName, { + fragmentation: k.OBCISimulatorFragmentationOneByOne + }); + let counter = 0; + simulator.on('data', function (buffer) { + expect(buffer.length).to.equal(1); + ++counter; + + if (counter === 5) done(); + }); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + it('should properly split packets, retaining valid packets', function (done) { + simulator = new OpenBCISimulator(portName, { + fragmentation: k.OBCISimulatorFragmentationRandom + }); + let buffer = new Buffer(0); + let counter = 0; + simulator.on('data', function (data) { + buffer = Buffer.concat([buffer, data], buffer.length + data.length); + if (buffer.length >= 33) { + const inputObj = { + rawDataPacket: buffer.slice(0, 33), + channelSettings: k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton), + scale: true + }; + openBCIUtilities.parsePacketStandardAccel(inputObj); + buffer = buffer.slice(33); + ++counter; + if (counter === 5) done(); + } + }); + simulator.once('open', () => simulator.write(k.OBCIStreamStart)); + }); + }); + describe(`boardFailure`, function () {}); + + describe(`#sync`, function () { + this.timeout(2000); + let simulator; + beforeEach(function (done) { + simulator = new OpenBCISimulator(portName, { + firmwareVersion: 'v2' + }); + simulator.once('open', () => { + done(); + }); + }); + afterEach(function () { + simulator = null; + }); + it(`should emit the time sync sent command`, function (done) { + simulator.once('data', data => { + expect(openBCIUtilities.isTimeSyncSetConfirmationInBuffer(data)).to.be.true(); + done(); + }); + simulator.write(k.OBCISyncTimeSet, (err, msg) => { + if (err) { + done(err); + } + }); + }); + it(`should set synced to true`, function (done) { + simulator.synced = false; + let newData = data => { + expect(simulator.synced).to.be.true(); + simulator.removeListener('data', newData); + done(); + }; + + simulator.on('data', data => { + newData(data); + }); + + simulator.write(k.OBCISyncTimeSet, (err, msg) => { + if (err) { + done(err); + } + }); + }); + it(`should emit a time sync set packet followed by a time synced accel packet after sync up call`, function (done) { + simulator.synced = false; + let emitCounter = 0; + let maxPacketsBetweenSetPacket = 5; + let newData = data => { + if (emitCounter === 0) { // the time sync packet is emitted here + // Make a call to start streaming + simulator.write(k.OBCIStreamStart, err => { + if (err) done(err); + }); + } else if (emitCounter < maxPacketsBetweenSetPacket) { + if (openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketAccelTimeSyncSet) { + simulator.removeListener('data', newData); + simulator.write(k.OBCIStreamStop, err => { + if (err) done(err); + done(); + }); + } else { + expect(openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketAccelTimeSynced); + } + } else { + done(`Failed to get set packet in time`); + } + emitCounter++; + }; + + simulator.on('data', newData); + + simulator.write(k.OBCISyncTimeSet, (err, msg) => { + if (err) { + done(err); + } + }); + }); + it(`should emit a time sync set raw aux, then time synced raw aux packet after sync up call`, function (done) { + simulator.synced = false; + simulator.options.accel = false; + let emitCounter = 0; + let maxPacketsBetweenSetPacket = 5; + let newData = data => { + if (emitCounter === 0) { // the time sync packet is emitted here + // Make a call to start streaming + simulator.write(k.OBCIStreamStart, err => { + if (err) done(err); + }); + } else if (emitCounter < maxPacketsBetweenSetPacket) { + if (openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketRawAuxTimeSyncSet) { + simulator.removeListener('data', newData); + simulator.write(k.OBCIStreamStop, err => { + if (err) done(err); + done(); + }); + } else { + expect(openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketRawAuxTimeSynced); + } + } else { + done(`Failed to get set packet in time`); + } + + emitCounter++; + }; + + simulator.on('data', newData); + + simulator.write(k.OBCISyncTimeSet, (err, msg) => { + if (err) { + done(err); + } + }); + }); + }); +}); From e93fe2b39da478046092849514ab38c176db2ec0 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 31 Oct 2017 14:10:28 -0400 Subject: [PATCH 2/4] DOCS: Bump version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ca4496..ea301f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci-cyton", - "version": "1.0.6", + "version": "1.0.7", "description": "The official Node.js SDK for the OpenBCI Cyton with Dongle.", "main": "openBCICyton.js", "scripts": { From ef90608413c669b2153b133e98585d529dccf413 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 31 Oct 2017 14:19:03 -0400 Subject: [PATCH 3/4] TESTS: Fix import for moving file back into this repo --- test/openBCISimulator-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js index 0b57790..846bde9 100644 --- a/test/openBCISimulator-test.js +++ b/test/openBCISimulator-test.js @@ -7,9 +7,9 @@ const chaiAsPromised = require(`chai-as-promised`); const dirtyChai = require('dirty-chai'); const expect = chai.expect; const should = chai.should(); // eslint-disable-line no-unused-vars -const OpenBCISimulator = require('../index').Simulator; -const openBCIUtilities = require('../openBCIUtilities'); -const k = require('../openBCIConstants'); +const OpenBCISimulator = require('../openBCISimulator'); +const openBCIUtilities = require('openbci-utilities').Utilities; +const k = require('openbci-utilities').Constants; const Buffer = require('safe-buffer').Buffer; const _ = require('lodash'); From 0b8f6ae1a176fbdb958f20070e38e73545a1358c Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 31 Oct 2017 14:25:33 -0400 Subject: [PATCH 4/4] ADD: performance now to dependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ea301f0..9fb8ad3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "lodash": "^4.17.4", "mathjs": "^3.14.2", "openbci-utilities": "^0.2.7", + "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", "sntp": "^2.0.2"