From 6b15f045f52cd8d65c7a408872c59f8e90f8415f Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 2 Nov 2016 17:32:56 -0400 Subject: [PATCH 01/42] Enh: Drop factory Add: index.js Remove: Node 4 and 5 support --- .travis.yml | 9 +- README.md | 590 ++-- changelog.md | 21 +- index.js | 2 + openBCIBoard.js | 2407 ----------------- openBCIConstants.js | 12 + openBCICyton.js | 2351 ++++++++++++++++ openBCISample.js | 8 +- openBCISimulator.js | 2 +- openBCIUtils.js | 35 + package.json | 9 +- test/OpenBCIConstants-test.js | 9 +- test/OpenBCISample-test.js | 72 +- ...test.js => openBCICyton-Impedance-test.js} | 18 +- ...nBCIBoard-test.js => openBCICyton-test.js} | 270 +- test/openBCISimulator-test.js | 18 +- 16 files changed, 2921 insertions(+), 2912 deletions(-) create mode 100644 index.js delete mode 100644 openBCIBoard.js create mode 100644 openBCICyton.js create mode 100644 openBCIUtils.js rename test/{openBCIBoard-Impedance-test.js => openBCICyton-Impedance-test.js} (98%) rename test/{openBCIBoard-test.js => openBCICyton-test.js} (94%) diff --git a/.travis.yml b/.travis.yml index 8179862..ed57ce7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,5 @@ language: node_js node_js: - - "4.0" - - "4.1" - - "4.2" - - "4.3" - - "4.4" - - "4.5" - - "4.6" - "5.11.0" - "6.0" - "6.1" @@ -17,6 +10,8 @@ node_js: - "6.6" - "6.7" - "6.8" + - "7.0" + - "7.1" install: - npm install --all script: diff --git a/README.md b/README.md index 76335ee..4fe771d 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,23 @@ A Node.js module for OpenBCI ~ written with love by [Push The World!](http://www.pushtheworldllc.com) -We are proud to support all functionality of the OpenBCI 8 and 16 Channel boards and are actively developing and maintaining this module. +We are proud to support all functionality of the Cyton (8 and 16 Channel boards). For the Ganglion (4 channel) please visit [OpenBCI_NodeJS_Ganglion](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion). Push The World is actively developing and maintaining this module. The purpose of this module is to **get connected** and **start streaming** as fast as possible. ### Table of Contents: --- -1. [Installation](#install) -2. [TL;DR](#tldr) -3. [About](#About) -4. [General Overview](#general-overview) -5. [SDK Reference Guide](#sdk-reference-guide) - 1. [Constructor](#constructor) - 2. [Methods](#method) - 3. [Events](#event) - 4. [Constants](#constants) +1. [TL;DR](#tldr) +2. [Installation](#install) +3. [Cyton (32bit Board)](#cyton) + 1. [About](#about) + 2. [General Overview](#general-overview) + 3. [SDK Reference Guide](#sdk-reference-guide) + * [Constructor](#constructor) + * [Methods](#method) + * [Events](#event) + * [Constants](#constants) 6. [Interfacing With Other Tools](#interfacing-with-other-tools) 7. [Developing](#developing) 8. [Testing](#developing-testing) @@ -33,47 +34,53 @@ The purpose of this module is to **get connected** and **start streaming** as fa 10. [License](#license) 11. [Roadmap](#roadmap) -### Installation: -``` -npm install openbci -``` ### TL;DR: Get connected and start streaming right now -```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +#### Cyton (8 and 16 channel boards) +```ecmascript 6 +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` - .then(function() { - ourBoard.on('ready',function() { - ourBoard.streamStart(); - ourBoard.on('sample',function(sample) { - /** Work with sample */ - for (var i = 0; i < ourBoard.numberOfChannels(); i++) { - console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts."); - // prints to the console - // "Channel 1: 0.00001987 Volts." - // "Channel 2: 0.00002255 Volts." - // ... - // "Channel 8: -0.00001875 Volts." - } - }); + .then(() => { + ourBoard.on('ready',() => { + ourBoard.streamStart(); + ourBoard.on('sample',(sample) => { + /** Work with sample */ + for (let i = 0; i < ourBoard.numberOfChannels(); i++) { + console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts."); + // prints to the console + // "Channel 1: 0.00001987 Volts." + // "Channel 2: 0.00002255 Volts." + // ... + // "Channel 8: -0.00001875 Volts." + } }); -}) + }); +}); ``` + +### Installation: -### About: +#### macOs +``` +npm install openbci +``` + +# Cyton + +## About: Want to know if the module really works? Check out some projects and organizations using it: * [_OpenEXP_](https://github.com/openexp/OpenEXP): an open-source desktop app for running experiments and collecting behavioral and physiological data. -* [_Thinker_](http://www.pushtheworldllc.com/#!thinker/uc1fn): a project building the world's first brainwave-word database. +* [_Thinker_](http://www.pushtheworldllc.com/): a project building the world's first brainwave-word database. * [_NeuroJS_](https://github.com/NeuroJS): a community dedicated to Neuroscience research using JavaScript, they have several great examples. Still not satisfied it works?? Check out this [detailed report](http://s132342840.onlinehome.us/pushtheworld/files/voltageVerificationTestPlanAndResults.pdf) that scientifically validates the output voltages of this module. -How are you still doubting and not using this already? Fine, go look at some of the [700 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it! +How are you still doubting and not using this already? Fine, go look at some of the [800 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it! -### General Overview: +## General Overview: Initialization -------------- @@ -81,43 +88,48 @@ Initialization Initializing the board: ```js -var OpenBCIBoard = require('openbci'); -var ourBoard = new OpenBCIBoard.OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ``` For initializing with options, such as verbose print outs: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ - verbose: true +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + verbose: true }); ``` Or if you don't have a board and want to use synthetic data: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ - simulate: true +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + simulate: true }); ``` Another useful way to start the simulator: ```js -var openBCIBoard = require('openbci'); -var k = openBCIBoard.OpenBCIConstants; -var ourBoard = openBCIBoard.OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const k = require('openbci').Constants; +const ourBoard = new Cyton(); ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true - .then(function(boardSerial) { - ourBoard.on('ready',function() { + .then((boardSerial) => { + ourBoard.on('ready',() => { /** Start streaming, reading registers, what ever your heart desires */ }); - }).catch(function(err) { + }).catch((err) => { /** Handle connection errors */ }); ``` - +or if you are using ES6: +```js +import { Cyton, Constants } from 'openbci'; +const ourBoard = new Cyton(); +ourBoard.connect(Constants.OBCISimulatorPortName); +``` 'ready' event ------------ @@ -126,8 +138,8 @@ You MUST wait for the 'ready' event to be emitted before streaming/talking with so installing the 'sample' listener and writing before the ready event might result in... nothing at all. ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { /** Start streaming, reading registers, what ever your heart desires */ @@ -159,8 +171,8 @@ To get a ['sample'](#event-sample) event, you need to: 3. In callback for ['ready'](#event-ready) emitter, call [`streamStart()`](#method-stream-start) 4. Install the ['sample'](#event-sample) event emitter ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function() { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -174,7 +186,8 @@ ourBoard.connect(portName).then(function() { ``` Close the connection with [`.streamStop()`](#method-stream-stop) and disconnect with [`.disconnect()`](#method-disconnect) ```js -var ourBoard = new require('openbci').OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.streamStop().then(ourBoard.disconnect()); ``` @@ -188,60 +201,61 @@ Keep your resync interval above 50ms. While it's important to resync every coupl Using local computer time: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard, - ourBoard = new OpenBCIBoard({ - verbose:true - }); +const Cyton = require('openbci').Cyton; +const k = require('openbci').Constants; +const ourBoard = new Cyton({ + verbose:true +}); const resyncPeriodMin = 5; // re sync every five minutes const secondsInMinute = 60; -var sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! -var timeSyncPossible = false; +let sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! +let timeSyncPossible = false; // Call to connect ourBoard.connect(portName).then(() => { - ourBoard.on('ready',() => { - // Get the sample rate after 'ready' - sampleRate = ourBoard.sampleRate(); - // 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(); - - ourBoard.streamStart() - .then(() => { - /** Start streaming command sent to board. */ - }) - .catch(err => { - console.log(`stream start: ${err}`); - }) - }); - - // PTW recommends sample driven - ourBoard.on('sample',sample => { - // Resynchronize every every 5 minutes - if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) { - ourBoard.syncClocksFull() - .then(syncObj => { - // Sync was successful - if (syncObj.valid) { - // Log the object to check it out! - console.log(`syncObj`,syncObj); - - // Sync was not successful - } else { - // Retry it - console.log(`Was not able to sync, please retry?`); - } - }); - } - - if (sample.timeStamp) { // true after the first sync - console.log(`NTP Time Stamp ${sample.timeStamp}`); - } - - }); + ourBoard.on('ready',() => { + // Get the sample rate after 'ready' + sampleRate = ourBoard.sampleRate(); + // 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(); + + ourBoard.streamStart() + .then(() => { + /** Start streaming command sent to board. */ + }) + .catch(err => { + console.log(`stream start: ${err}`); + }); + }); + + // PTW recommends sample driven + ourBoard.on('sample',sample => { + // Resynchronize every every 5 minutes + if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) { + ourBoard.syncClocksFull() + .then(syncObj => { + // Sync was successful + if (syncObj.valid) { + // Log the object to check it out! + console.log(`syncObj`,syncObj); + + // Sync was not successful + } else { + // Retry it + console.log(`Was not able to sync, please retry?`); + } + }); + } + + if (sample.timeStamp) { // true after the first sync + console.log(`NTP Time Stamp ${sample.timeStamp}`); + } + + }); }) .catch(err => { - console.log(`connect: ${err}`); + console.log(`connect: ${err}`); }); ``` @@ -252,8 +266,8 @@ You must have the OpenBCI board connected to the PC before trying to automatical If a port is not automatically found, then call [`.listPorts()`](#method-list-ports) to get a list of all serial ports this would be a good place to present a drop down picker list to the user, so they may manually select the serial port name. ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.autoFindOpenBCIBoard().then(portName => { if(portName) { /** @@ -294,8 +308,8 @@ Where there are the same number of elements as channels and each element can be Without further ado, here is an example: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -310,7 +324,7 @@ ourBoard.connect(portName).then(function(boardSerial) { ``` But wait! What is this `impedanceArray`? An Array of Objects, for each object: -```js +``` [{ channel: 1, P: { @@ -351,8 +365,8 @@ To run an impedance test on all inputs, one channel at a time: For example: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.streamStart(); ourBoard.on('impedanceArray', impedanceArray => { @@ -364,13 +378,13 @@ ourBoard.connect(portName).then(function(boardSerial) { See Reference Guide for a complete list of impedance tests. -# SDK Reference Guide: +## SDK Reference Guide: --------------- -## Constructor: +### Constructor: -### OpenBCIBoard (options) +#### Cyton (options) -Create new instance of an OpenBCI board. +Create new instance of an Cyton board. **_options (optional)_** @@ -378,10 +392,8 @@ Board optional configurations. * `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if firmware on board has been previously configured. * `boardType` {String} - Specifies type of OpenBCI board (3 possible boards) - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel board with Daisy Module - 16 Channels - * `ganglion` - 4 Channel board - (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 08/2016) + * `default` - 8 Channel Cyton board (Default) + * `daisy` - 8 Channel Cyton board with Daisy Module - 16 Channels * `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) * `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on the board is not polling the RFduino on the dongle. (Default `false`) * `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. This is useful if you want to test how your application reacts to a user requesting 16 channels but there is no daisy module actually attached, or vice versa, where there is a daisy module attached and the user only wants to use 8 channels. (Default `false`) @@ -411,9 +423,9 @@ Board optional configurations. **Note, we have added support for either all lowercase OR camel case for the options, use whichever style you prefer.** -## Methods: +### Methods: -### .autoFindOpenBCIBoard() +#### .autoFindOpenBCIBoard() Automatically find an OpenBCI board. @@ -421,7 +433,7 @@ Automatically find an OpenBCI board. **_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux or `OpenBCISimulator` if `this.options.simulate === true`. -### .channelOff(channelNumber) +#### .channelOff(channelNumber) Turn off a specified channel @@ -431,7 +443,7 @@ A number (1-16) specifying which channel you want to turn off. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .channelOn(channelNumber) +#### .channelOn(channelNumber) Turn on a specified channel @@ -441,7 +453,7 @@ A number (1-16) specifying which channel you want to turn on. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) +#### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) Send a channel setting command to the board. @@ -481,7 +493,7 @@ ourBoard.channelSet(2,false,24,'normal',true,true,false); // sends ['x','2','0','6','0','1','1','0','X'] to the command queue ``` -### .connect(portName) +#### .connect(portName) The essential precursor method to be called initially to establish a serial connection to the OpenBCI board. @@ -491,29 +503,17 @@ The system path of the OpenBCI board serial port to open. For example, `/dev/tty **_Returns_** a promise, fulfilled by a successful serial connection to the board. -### .debugSession() +#### .debugSession() Calls all [`.printPacketsBad()`](#method-print-packets-bad), [`.printPacketsRead()`](#method-print-packets-read), [`.printBytesIn()`](#method-print-bytes-in) -### .disconnect() +#### .disconnect() Closes the serial port opened by [`.connect()`](#method-connect). Waits for stop streaming command to be sent if currently streaming. **_Returns_** a promise, fulfilled by a successful close of the serial port object, rejected otherwise. -### .getSettingsForChannel(channelNumber) - -Gets the specified channelSettings register data from printRegisterSettings call. - -**_channelNumber_** - -A number specifying which channel you want to get data on. Only 1-8 at this time. - -**Note, at this time this does not work for the daisy board** - -**_Returns_** a promise, fulfilled if the command was sent to the board and the `.processBytes()` function is ready to reach for the specified channel. - -### .impedanceTestAllChannels() +#### .impedanceTestAllChannels() To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a little while to actually run (<8 seconds)! @@ -523,7 +523,7 @@ Don't forget to install the ['impedanceArray'](#event-impedance-array) emitter t **_Returns_** a promise upon completion of test. -### .impedanceTestChannels(arrayOfCommands) +#### .impedanceTestChannels(arrayOfCommands) **_arrayOfCommands_** @@ -540,7 +540,7 @@ Don't forget to install the `impedanceArray` emitter to receive the impendances! **_Returns_** a promise upon completion of test. -### .impedanceTestChannel(channelNumber) +#### .impedanceTestChannel(channelNumber) Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. @@ -552,8 +552,8 @@ A Number, specifies which channel you want to test. **Example** ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -568,21 +568,21 @@ ourBoard.connect(portName).then(function(boardSerial) { }); ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: 2394.45, - text: 'good' + "channel": 1, + "P": { + "raw": 2394.45, + "text": "good" }, - N: { - raw: 7694.45, - text: 'ok' + "N": { + "raw": 7694.45, + "text": "ok" } } ``` -### .impedanceTestChannelInputP(channelNumber) +#### .impedanceTestChannelInputP(channelNumber) Run impedance test on a single channel, applying the test signal only to P input. @@ -594,10 +594,10 @@ A Number, specifies which channel you want to test. **Example** ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); +ourBoard.connect(portName).then(() => { + ourBoard.on('ready', () => { ourBoard.streamStart(); ourBoard.impedanceTestChannelInputP(1) .then(impedanceObject => { @@ -610,21 +610,21 @@ ourBoard.connect(portName).then(function(boardSerial) { }); ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: 2394.45, - text: 'good' + "channel": 1, + "P": { + "raw": 2394.45, + "text": "good" }, - N: { - raw: -1, - text: 'init' + "N": { + "raw": -1, + "text": "init" } } ``` -### .impedanceTestChannelInputN(channelNumber) +#### .impedanceTestChannelInputN(channelNumber) Run impedance test on a single channel, applying the test signal only to N input. @@ -635,11 +635,11 @@ A Number, specifies which channel you want to test. **_Returns_** a promise that resolves a single channel impedance object. **Example** -```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { +```ecmascript 6 +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); +ourBoard.connect(portName).then(() => { + ourBoard.on('ready',() => { ourBoard.streamStart(); ourBoard.impedanceTestChannelInputN(1) .then(impedanceObject => { @@ -647,54 +647,53 @@ ourBoard.connect(portName).then(function(boardSerial) { }) .catch(err => console.log(err)); }); -}).catch(function(err) { - /** Handle connection errors */ -}); +}).catch(err => console.log(err)); + ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: -1, - text: 'init' + "channel": 1, + "P": { + "raw": -1, + "text": "init" }, - N: { - raw: 7694.45, - text: 'ok' + "N": { + "raw": 7694.45, + "text": "ok" } } ``` -### .impedanceTestContinuousStart() +#### .impedanceTestContinuousStart() Sends command to turn on impedances for all channels and continuously calculate their impedances. **_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer -### .impedanceTestContinuousStop() +#### .impedanceTestContinuousStop() Sends command to turn off impedances for all channels and stop continuously calculate their impedances. **_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer -### .isConnected() +#### .isConnected() Checks if the driver is connected to a board. **_Returns_** a boolean, true if connected -### .isStreaming() +#### .isStreaming() Checks if the board is currently sending samples. **_Returns_** a boolean, true if streaming -### .listPorts() +#### .listPorts() List available ports so the user can choose a device when not automatically found. **_Returns_** a promise, fulfilled with a list of available serial ports. -### .numberOfChannels() +#### .numberOfChannels() Get the current number of channels available to use. (i.e. 8 or 16). @@ -702,25 +701,25 @@ Get the current number of channels available to use. (i.e. 8 or 16). **_Returns_** a number, the total number of available channels. -### .printBytesIn() +#### .printBytesIn() Prints the total number of bytes that were read in this session to the console. -### .printPacketsBad() +#### .printPacketsBad() Prints the total number of packets that were not able to be read in this session to the console. -### .printPacketsRead() +#### .printPacketsRead() Prints the total number of packets that were read in this session to the console. -### .printRegisterSettings() +#### .printRegisterSettings() Prints all register settings for the ADS1299 and the LIS3DH on the OpenBCI board. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .radioBaudRateSet(speed) +#### .radioBaudRateSet(speed) Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial data is sent from the Host to the Serial driver. The rate can either be set to default or fast. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the new baud rate after closing the current serial port and reopening one. @@ -732,7 +731,7 @@ Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, **_Returns_** {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. -### .radioChannelGet() +#### .radioChannelGet() Used to query the OpenBCI system for it's radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve an Object. See `returns` below. @@ -740,7 +739,7 @@ Used to query the OpenBCI system for it's radio channel number. The function wil **_Returns_** {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in the condition that there system is experiencing board communications failure. -### .radioChannelSet(channelNumber) +#### .radioChannelSet(channelNumber) Used to set the system radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -752,7 +751,7 @@ Used to set the system radio channel number. The function will reject if not con **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioChannelSetHostOverride(channelNumber) +#### .radioChannelSetHostOverride(channelNumber) Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if your dongle and board are not on the right channel and bring down your radio system if you take your dongle and board are not on the same channel. Use with caution! The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -764,7 +763,7 @@ Used to set the ONLY the radio dongle Host channel number. This will fix your ra **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioPollTimeGet() +#### .radioPollTimeGet() Used to query the OpenBCI system for it's device's poll time. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the poll time when fulfilled. It's important to note that if the board is not on, this function will always be rejected with a failure message. @@ -772,7 +771,7 @@ Used to query the OpenBCI system for it's device's poll time. The function will **_Returns_** {Promise} - Resolves with the new poll time, rejects with err. -### .radioPollTimeSet(pollTime) +#### .radioPollTimeSet(pollTime) Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this sets the interval at which the Device polls the Host for new information. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -784,7 +783,7 @@ Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioSystemStatusGet() +#### .radioSystemStatusGet() Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are in fact ready to start trying to connect and such. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the same channel and powered, then this will resolve true. @@ -792,7 +791,7 @@ Used to ask the Host if it's radio system is up. This is useful to quickly deter **_Returns_** {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. -### .sampleRate() +#### .sampleRate() Get the current sample rate. @@ -800,7 +799,7 @@ Get the current sample rate. **_Returns_** a number, the current sample rate. -### .sdStart(recordingDuration) +#### .sdStart(recordingDuration) Start logging to the SD card. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. @@ -822,13 +821,13 @@ The duration you want to log SD information for. Opens a new SD file to write in **_Returns_** resolves if the command was added to the write queue. -### .sdStop() +#### .sdStop() Stop logging to the SD card and close any open file. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. The success message contains a lot of useful information about what happened when writing to the SD card. **_Returns_** resolves if the command was added to the write queue. -### .simulatorEnable() +#### .simulatorEnable() To enter simulate mode. Must call [`.connect()`](#method-connect) after. @@ -836,7 +835,7 @@ To enter simulate mode. Must call [`.connect()`](#method-connect) after. **_Returns_** a promise, fulfilled if able to enter simulate mode, reject if not. -### .simulatorDisable() +#### .simulatorDisable() To leave simulate mode. @@ -844,27 +843,27 @@ To leave simulate mode. **_Returns_** a promise, fulfilled if able to stop simulate mode, reject if not. -### .sntp +#### .sntp Extends the popular SNTP package on [npmjs](https://www.npmjs.com/package/sntp) -### .sntpGetOffset() +#### .sntpGetOffset() Stateful method for querying the current offset only when the last one is too old. (defaults to daily) **_Returns_** a promise with the time offset -### .sntpStart() +#### .sntpStart() This starts the SNTP server and gets it to remain in sync with the SNTP server. **_Returns_** a promise if the module was able to sync with NTP server. -### .sntpStop() +#### .sntpStop() Stops the SNTP from updating -### .softReset() +#### .softReset() Sends a soft reset command to the board. @@ -872,7 +871,7 @@ Sends a soft reset command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .streamStart() +#### .streamStart() Sends a start streaming command to the board. @@ -880,7 +879,7 @@ Sends a start streaming command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. -### .streamStop() +#### .streamStop() Sends a stop streaming command to the board. @@ -888,7 +887,7 @@ Sends a stop streaming command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. -### .syncClocks() +#### .syncClocks() Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using version +2 firmware. @@ -896,25 +895,25 @@ Send the command to tell the board to start the syncing protocol. Must be connec **_Returns_** {Promise} resolves if the command was sent to the write queue, rejects if unable. -### .syncClocksFull() +#### .syncClocksFull() Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using v2 firmware. Uses the `synced` event to ensure multiple syncs don't overlap. **Note, this functionality requires OpenBCI Firmware Version 2.0** **_Returns_** {Promise} resolves if `synced` event is emitted, rejects if not connected or using firmware v2. Resolves with a synced object: -```javascript +```js { - boardTime: 0, // The time contained in the time sync set packet. - correctedTransmissionTime: false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation. - timeSyncSent: 0, // The time the `<` was sent to the Dongle. - timeSyncSentConfirmation: 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle. - timeSyncSetPacket: 0, // The time the set packet was received from the Board. - timeRoundTrip: 0, // Simply timeSyncSetPacket - timeSyncSent. - timeTransmission: 0, // Estimated time it took for time sync set packet to be sent from Board to Driver. - timeOffset: 0, // The map (or translation) from boardTime to module time. - timeOffsetMaster: 0, // The map (or translation) from boardTime to module time averaged over time syncs. - valid: false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below. + "boardTime": 0, // The time contained in the time sync set packet. + "correctedTransmissionTime": false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation. + "timeSyncSent": 0, // The time the `<` was sent to the Dongle. + "timeSyncSentConfirmation": 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle. + "timeSyncSetPacket": 0, // The time the set packet was received from the Board. + "timeRoundTrip": 0, // Simply timeSyncSetPacket - timeSyncSent. + "timeTransmission": 0, // Estimated time it took for time sync set packet to be sent from Board to Driver. + "timeOffset": 0, // The map (or translation) from boardTime to module time. + "timeOffsetMaster": 0, // The map (or translation) from boardTime to module time averaged over time syncs. + "valid": false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below. } ``` @@ -923,73 +922,71 @@ Send the command to tell the board to start the syncing protocol. Must be connec Syncing multiple times to base the offset of the average of the four syncs. ```javascript -var OpenBCIBoard = require('openbci').OpenBCIBoard, - ourBoard = new OpenBCIBoard({ - verbose:true - }); - -var portName = /* INSERT PORT NAME HERE */; -var samples = []; // Array to store time synced samples into -var timeSyncActivated = false; +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + verbose:true +}); +let portName = /* INSERT PORT NAME HERE */; +let samples = []; // Array to store time synced samples into +let timeSyncActivated = false; ourBoard.connect(portName) - .then(() => { - ourBoard.on('ready',() => { - ourBoard.streamStart() - .then(() => { - /** Could also call `.syncClocksFull()` here */ - }) - .catch(err => { - console.log(`Error starting stream ${err}`); - }) - }); - ourBoard.on('sample',sample => { - /** If we are not synced, then do that! */ - if (timeSyncActivated === false) { - timeSyncActivated = true; - ourBoard.syncClocksFull() - .then(syncObj => { - if (syncObj.valid) { - console.log('1st sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('2nd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('3rd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('4th sync done'); - - } - /* Do awesome time syncing stuff */ - }) - .catch(err => { - console.log(`sync err ${err}`); - }); + .then(() => { + ourBoard.on('ready',() => { + ourBoard.streamStart() + .then(() => { + /** Could also call `.syncClocksFull()` here */ + }) + .catch(err => { + console.log(`Error starting stream ${err}`); + }) + }); + ourBoard.on('sample',sample => { + /** If we are not synced, then do that! */ + if (timeSyncActivated === false) { + timeSyncActivated = true; + ourBoard.syncClocksFull() + .then(syncObj => { + if (syncObj.valid) { + console.log('1st sync done'); } - if (startLoggingSamples && sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) { - /** If you only want to log samples with time stamps */ - samples.push(sample); + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('2nd sync done'); } - }); - }) + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('3rd sync done'); + } + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('4th sync done'); + } + /* Do awesome time syncing stuff */ + }) + .catch(err => { + console.log(`sync err ${err}`); + }); + } + if (sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) { + /** If you only want to log samples with time stamps */ + samples.push(sample); + } + }); +}) .catch(err => { - console.log(`connect ${err}`); + console.log(`connect ${err}`); }); ``` -### .testSignal(signal) +#### .testSignal(signal) Apply the internal test signal to all channels. @@ -1007,13 +1004,13 @@ A String indicating which test signal to apply **_Returns_** a promise, if the commands were sent to write buffer. -### .time() +#### .time() Uses `._sntpNow()` time when sntpTimeSync specified `true` in options, or else Date.now() for time. **_Returns_** time since UNIX epoch in ms. -### .usingVersionTwoFirmware() +#### .usingVersionTwoFirmware() Convenience method to determine if you can use firmware v2.x.x capabilities. @@ -1021,7 +1018,7 @@ Convenience method to determine if you can use firmware v2.x.x capabilities. **_Returns_** a boolean, true if using firmware version 2 or greater. -### .write(dataToWrite) +#### .write(dataToWrite) Send commands to the board. Due to the OpenBCI board firmware 1.0, a 10ms spacing **must** be observed between every command sent to the board. This method handles the timing and spacing between characters by adding characters to a global write queue and pulling from it every 10ms. If you are using firmware version +2.0 then you no spacing will be used. @@ -1053,50 +1050,49 @@ ourBoard.write('c'); ourBoard.write('o'); ``` -## Events: +### Events: -### .on('close', callback) +#### .on('close', callback) Emitted when the serial connection to the board is closed. -### .on('droppedPacket', callback) +#### .on('droppedPacket', callback) Emitted when a packet (or packets) are dropped. Returns an array. -### .on('error', callback) +#### .on('error', callback) Emitted when there is an on the serial port. -### .on('impedanceArray', callback) +#### .on('impedanceArray', callback) Emitted when there is a new impedanceArray available. Returns an array. -### .on('query', callback) +#### .on('query', callback) Emitted resulting in a call to [`.getChannelSettings()`](#method-get-settings-for-channel) with the channelSettingsObject -### .on('rawDataPacket', callback) +#### .on('rawDataPacket', callback) Emitted when there is a new raw data packet available. -### .on('ready', callback) +#### .on('ready', callback) Emitted when the board is in a ready to start streaming state. -### .on('sample', callback) +#### .on('sample', callback) Emitted when there is a new sample available. -### .on('synced', callback) +#### .on('synced', callback) Emitted when there is a new sample available. -## Constants: +### Constants: To use the constants file simply: ```js -var openBCIBoard = require('openbci'); -var k = openBCIBoard.OpenBCIConstants; +const k = require('openbci').Constants; console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console. ``` @@ -1181,7 +1177,7 @@ MIT ## Roadmap: -1. Ganglion integration (2.x) +1. Ganglion integration (3.x) 2. Compatible with node streams (3.x) 3. Remove factory paradigm from main file (3.x) 5. ES6/ES7 total adoption (3.x) diff --git a/changelog.md b/changelog.md index 757d6a8..1ca28fd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,22 @@ +# 2.0.0 + +### New Features +* `index.js` file allows for ES6 destructing + +### Breaking Changes +* Change name of `OpenBCIBoard` to `Cyton` to follow new naming convention. + Simply change: + ```ecmascript 6 + const OpenBCIBoard = require('openbci').OpenBCIBoard; + const ourBoard = new OpenBCIBoard(); + ``` + ```ecmascript 6 + const Cyton = require('openbci').Cyton; + const ourBoard = new Cyton(); + ``` +* Major change to how board is initialized with removal of `factory` paradigm. +* Drop support for Node 4 and 5 due to lack of EMACS 6 + # 1.4.1 ### Bug Fixes @@ -226,7 +245,7 @@ The second major release for the OpenBCI Node.js SDK brings major changes, impro ### Bug Fixes * updates to README.me and comments to change ntp to sntp, because the two are similar, but not the same and we do not want to be misleading -* Extended [Stnp](https://www.npmjs.com/package/sntp) to main openBCIBoard.js +* Extended [Stnp](https://www.npmjs.com/package/sntp) to main openBCICyton.js * Add `.sntpNow()` function to get ntp time. # 0.3.1 diff --git a/index.js b/index.js new file mode 100644 index 0000000..c6cbbaa --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +module.exports.Cyton = require('./openBCICyton'); +module.exports.Constants = require('./openBCIConstants'); diff --git a/openBCIBoard.js b/openBCIBoard.js deleted file mode 100644 index cb01d5d..0000000 --- a/openBCIBoard.js +++ /dev/null @@ -1,2407 +0,0 @@ -'use strict'; -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var stream = require('stream'); -var SerialPort = require('serialport'); -var openBCISample = require('./openBCISample'); -var k = openBCISample.k; -var openBCISimulator = require('./openBCISimulator'); -var Sntp = require('sntp'); -var bufferEqual = require('buffer-equal'); -var math = require('mathjs'); - -/** -* @description SDK for OpenBCI Board {@link www.openbci.com} -* @module 'openbci-sdk' -*/ -function OpenBCIFactory () { - var factory = this; - - var _options = { - boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], - baudRate: 115200, - simulate: false, - simulatorBoardFailure: false, - simulatorDaisyModuleAttached: false, - simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], - simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], - simulatorLatencyTime: 16, - simulatorBufferSize: 4096, - simulatorHasAccelerometer: true, - simulatorInternalClockDrift: 0, - simulatorInjectAlpha: true, - simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], - simulatorSampleRate: 250, - simulatorSerialPortFailure: false, - sntpTimeSync: false, - sntpTimeSyncHost: 'pool.ntp.org', - sntpTimeSyncPort: 123, - verbose: false, - debug: false - }; - - /** - * @description The initialization method to call first, before any other method. - * @param options (optional) - Board optional configurations. - * - `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if - * firmware on board has been previously configured. - * - * - `boardType` {String} - Specifies type of OpenBCI board. - * 3 Possible Boards: - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. - * `ganglion` - 4 Channel board - * (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) - * - * - `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting - * `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) - * - * - `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on - * the board is not polling the RFduino on the dongle. (Default `false`) - * - * - `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. - * This is useful if you want to test how your application reacts to a user requesting 16 channels - * but there is no daisy module actually attached, or vice versa, where there is a daisy module - * attached and the user only wants to use 8 channels. (Default `false`) - * - * - `simulatorFirmwareVersion` {String} - Allows simulator to be started with firmware version 2 features - * 2 Possible Options: - * `v1` - Firmware Version 1 (Default) - * `v2` - Firmware Version 2 - * - * - `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which - * occurs commonly in real devices. It is recommended to test code with this enabled. - * 4 Possible Options: - * `none` - do not fragment packets; output complete chunks immediately when produced (Default) - * `random` - output random small chunks of data interspersed with full buffers - * `fullBuffers` - allow buffers to fill up until the latency timer has expired - * `oneByOne` - output each byte separately - * - * - `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers, - if `simulatorFragmentation` is specified. (Default `16`) - * - * - `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is - * specified. (Default `4096`) - * - * - `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) - * - * - `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) - * - * - `simulatorInjectLineNoise` {String} - Injects line noise on channels. - * 3 Possible Options: - * `60Hz` - 60Hz line noise (Default) [America] - * `50Hz` - 50Hz line noise [Europe] - * `none` - Do not inject line noise. - * - * - `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if - * `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that - * setting and this sample rate will be used. (Default is `250`) - * - * - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely - * due to a OpenBCI dongle not being plugged in. - * - * - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source - * of truth instead of local computer time. If you are running experiements on your local - * computer, keep this `false`. (Default `false`) - * - * - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). - * - * - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`) - * - * - `verbose` {Boolean} - Print out useful debugging events. (Default `false`) - * - * - `debug` {Boolean} - Print out a raw dump of bytes sent and received. (Default `false`) - * - * @constructor - * @author AJ Keller (@pushtheworldllc) - */ - function OpenBCIBoard (options) { - options = (typeof options !== 'function') && options || {}; - var opts = {}; - - stream.Stream.call(this); - - /** Configuring Options */ - var o; - for (o in _options) { - var userOption = (o in options) ? o : o.toLowerCase(); - var userValue = options[userOption]; - delete options[userOption]; - - 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'); - - // Set to global options object - this.options = opts; - - /** Properties (keep alphabetical) */ - // Arrays - this.accelArray = [0, 0, 0]; // X, Y, Z - this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); - this.writeOutArray = []; - // Booleans - this._streaming = false; - // Buffers - this.buffer = null; - this.masterBuffer = masterBufferMaker(); - // Objects - this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType)); - this.impedanceTest = { - active: false, - isTestingPInput: false, - isTestingNInput: false, - onChannel: 0, - sampleNumber: 0, - continuousMode: false, - impedanceForChannel: 0 - }; - this.info = { - boardType: this.options.boardType, - sampleRate: k.OBCISampleRate125, - firmware: k.OBCIFirmwareV1, - numberOfChannels: k.OBCINumberOfChannelsDefault, - missedPackets: 0 - }; - if (this.options.boardType === k.OBCIBoardDefault) { - this.info.sampleRate = k.OBCISampleRate250; - } - - this._lowerChannelsSampleObject = null; - this.serial = null; - this.sync = { - curSyncObj: null, - eventEmitter: null, - objArray: [], - sntpActive: false, - timeOffsetMaster: 0, - timeOffsetAvg: 0, - timeOffsetArray: [] - }; - this.writer = null; - // Numbers - this.badPackets = 0; - this.curParsingMode = k.OBCIParsingReset; - this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); - this.previousSampleNumber = -1; - this.sampleCount = 0; - this.timeOfPacketArrival = 0; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - // Strings - - // NTP - if (this.options.sntpTimeSync) { - // establishing ntp connection - this.sntpStart() - .catch(ignored => { - // try again once after a delay - return new Promise((resolve, reject) => { - setTimeout(resolve, 500); - }).then(() => this.sntpStart()); - }) - .then(() => { - if (this.options.verbose) console.log('SNTP: connected'); - }) - .catch(err => { - if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); - this.emit('error', err); - }); - } - - // TODO: Add connect immediately functionality, suggest this to be the default... - } - - // This allows us to use the emitter class freely outside of the module - util.inherits(OpenBCIBoard, stream.Stream); - - /** - * @description The essential precursor method to be called initially to establish a - * serial connection to the OpenBCI board. - * @param portName - a string that contains the port name of the OpenBCIBoard. - * @returns {Promise} if the board was able to connect. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.connect = function (portName) { - return new Promise((resolve, reject) => { - if (this.isConnected()) return reject('already connected!'); - - /* istanbul ignore else */ - if (this.options.simulate || portName === k.OBCISimulatorPortName) { - this.options.simulate = true; - // If we are simulating, set portName to fake name - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('using faux board ' + portName); - this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { - accel: this.options.simulatorHasAccelerometer, - alpha: this.options.simulatorInjectAlpha, - boardFailure: this.options.simulatorBoardFailure, - daisy: this.options.simulatorDaisyModuleAttached, - drift: this.options.simulatorInternalClockDrift, - firmwareVersion: this.options.simulatorFirmwareVersion, - fragmentation: this.options.simulatorFragmentation, - latencyTime: this.options.simulatorLatencyTime, - bufferSize: this.options.simulatorBufferSize, - lineNoise: this.options.simulatorInjectLineNoise, - sampleRate: this.options.simulatorSampleRate, - serialPortFailure: this.options.simulatorSerialPortFailure, - verbose: this.options.verbose - }); - } else { - this.portName = portName; - if (this.options.verbose) console.log('using real board ' + portName); - this.serial = new SerialPort(portName, { - baudRate: this.options.baudRate - }, (err) => { - if (err) reject(err); - }); - } - - if (this.options.verbose) console.log('Serial port connected'); - - this.serial.on('data', data => { - this._processBytes(data); - }); - this.serial.once('open', () => { - if (this.options.verbose) console.log('Serial port open'); - new Promise(resolve => { - // TODO: document why this 300 ms delay is needed - setTimeout(resolve, this.options.simulate ? 50 : 300); - }).then(() => { - if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); - return this.write(k.OBCIStreamStop); - }).then(() => { - return new Promise(resolve => this.serial.flush(resolve)); - }).then(() => { - // TODO: document why this 250 ms delay is needed - return new Promise(resolve => setTimeout(resolve, 250)); - }).then(() => { - if (this.options.verbose) console.log('Sending soft reset'); - // TODO: this promise chain resolves early because - // A. some legacy code (in tests) sets the ready handler after this resolves - // and - // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented - // which is C. not implemented yet except in a manner such that replies occur in the write handler, - // resulting in the EOT arriving before this resolves - // Fix one or more of the above 3 situations, then move resolve() to the next block. - resolve(); - return this.softReset(); - }).then(() => { - if (this.options.verbose) console.log("Waiting for '$$$'"); - }); - }); - this.serial.once('close', () => { - if (this.options.verbose) console.log('Serial Port Closed'); - // 'close' is emitted in _disconnected() - this._disconnected('port closed'); - }); - this.serial.once('error', (err) => { - if (this.options.verbose) console.log('Serial Port Error'); - this.emit('error', err); - this._disconnected(err); - }); - }); - }; - - /** - * @description Called once when for any reason the serial port is no longer open. - * @private - */ - OpenBCIBoard.prototype._disconnected = function (err) { - this._streaming = false; - - clearTimeout(this.writer); - this.writer = null; - - this.serial.removeAllListeners(); - this.serial = null; - - this.emit('close'); - - while (this.writeOutArray.length > 0) { - var command = this.writeOutArray.pop(); - if (command.reject) command.reject(err); - } - }; - - /** - * @description Closes the serial port. Waits for stop streaming command to - * be sent if currently streaming. - * @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.disconnect = function () { - return Promise.resolve() - .then(() => { - if (this.isStreaming()) { - if (this.options.verbose) console.log('stop streaming'); - return this.streamStop(); - } - }) - .then(() => { - if (!this.isConnected()) { - return Promise.reject('no board connected'); - } else { - return new Promise((resolve, reject) => { - // serial emitting 'close' will call _disconnected - this.serial.close(() => { - resolve(); - }); - }); - } - }); - }; - - /** - * @description Checks if the driver is connected to a board. - * @returns {boolean} - True if connected. - */ - OpenBCIBoard.prototype.isConnected = function () { - if (!this.serial) return false; - return this.serial.isOpen(); - }; - - /** - * @description Checks if the board is currently sending samples. - * @returns {boolean} - True if streaming. - */ - OpenBCIBoard.prototype.isStreaming = function () { - return this._streaming; - }; - - /** - * @description Sends a start streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board will start streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.streamStart = function () { - return new Promise((resolve, reject) => { - if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); - this._streaming = true; - this._reset_ABANDONED(); // framework is incomplete but looks useful - this.write(k.OBCIStreamStart).then(resolve, reject); - }); - }; - - /** - * @description Sends a stop streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board stopped streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.streamStop = function () { - return new Promise((resolve, reject) => { - if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); - this._streaming = false; - this.write(k.OBCIStreamStop).then(resolve, reject); - }); - }; - - /** - * @description To start simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.simulatorEnable = function () { - return new Promise((resolve, reject) => { - if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? - if (this.isConnected()) { - this.disconnect() // disconnect first - .then(() => { - this.options.simulate = true; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = true; - resolve(); - } - }); - }; - - /** - * @description To stop simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.simulatorDisable = function () { - return new Promise((resolve, reject) => { - if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? - if (this.isConnected()) { - this.disconnect() - .then(() => { - this.options.simulate = false; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = false; - resolve(); - } - }); - }; - - /** - * @description To be able to easily write to the board but ensure that we never send commands - * with less than a 10ms spacing between sends in early version boards. This uses - * an array and shifts off the entries until there are none left. - * @param dataToWrite - Either a single character or an Array of characters - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.write = function (dataToWrite) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) { - reject('not connected'); - } else { - if (Array.isArray(dataToWrite)) { // Got an input array - var len = dataToWrite.length; - for (var i = 0; i < len; i++) { - this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); - } - this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; - } else { - this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); - } - - if (!this.writer) { // there is no writer started - var writerFunction = () => { - if (this.writeOutArray.length === 0) { - this.writer = null; - return; - } - - var command = this.writeOutArray.shift(); - var promise = this._writeAndDrain(command.cmd); - - promise.then(() => { - this.writer = setTimeout(writerFunction, this.writeOutDelay); - }, () => { - // write failed but more commands may be pending that need a result - writerFunction(); - }); - - if (command.reject) { - promise.catch(err => { - if (this.options.verbose) console.log('write failure: ' + err); - command.reject(err); - }); - } - if (command.resolve) promise.then(command.resolve); - }; - this.writer = setTimeout(writerFunction, this.writeOutDelay); - } - } - }); - }; - - /** - * @description Should be used to send data to the board - * @param data {Buffer} - The data to write out - * @returns {Promise} if signal was able to be sent - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._writeAndDrain = function (data) { - this._debugBytes('>>>', data); - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Serial port not open'); - this.serial.write(data, (error) => { - if (error) { - console.log('Error [writeAndDrain]: ' + error); - reject(error); - } else { - this.serial.drain(function () { - resolve(); - }); - } - }); - }); - }; - - /** - * @description Automatically find an OpenBCI board. - * Note: This method is used for convenience and should be used when trying to - * connect to a board. If you find a case (i.e. a platform (linux, - * windows...) that this does not work, please open an issue and - * we will add support! - * @returns {Promise} - Fulfilled with portName, rejected when can't find the board. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.autoFindOpenBCIBoard = function () { - var serialPatterns = [ - { // mac - comName: /usbserial-D/ - }, - { // linux - comName: /^\/dev\/ttyUSB/, - manufacturer: /^FTDI$/, - serialNumber: /^FTDI_FT231X_USB_UART/, - vendorId: /^0x0403$/, - productId: /^0x6015$/ - } - ]; - return new Promise((resolve, reject) => { - /* istanbul ignore else */ - if (this.options.simulate) { - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('auto found sim board'); - resolve(k.OBCISimulatorPortName); - } else { - SerialPort.list((err, ports) => { - if (err) { - if (this.options.verbose) console.log('serial port err'); - reject(err); - } - // This is one big if statement - if (ports.some(port => { - return serialPatterns.some(patterns => { - for (var attribute in patterns) { - if (!String(port[attribute]).match(patterns[attribute])) { - return false; - } - } - this.portName = port.comName; - return true; - }); - })) { - if (this.options.verbose) console.log('auto found board'); - resolve(this.portName); - } else { - if (this.options.verbose) console.log('could not find board'); - reject('Could not auto find board'); - } - }); - } - }); - }; - - /** - * @description Convenience method to determine if you can use firmware v2.x.x - * capabilities. - * @returns {boolean} - True if using firmware version 2 or greater. Should - * be called after a `.softReset()` because we can parse the output of that - * to determine if we are using firmware version 2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.usingVersionTwoFirmware = function () { - if (this.options.simulate) { - return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; - } else { - return this.info.firmware === k.OBCIFirmwareV2; - } - }; - - /** - * @description Used to set the system radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelSet = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); - }); - }; - - /** - * @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if - * your dongle and board are not on the right channel and bring down your radio system if you take your - * dongle and board are not on the same channel. Use with caution! The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelSetHostOverride = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(`${data.toString()}`); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); - }); - }; - - /** - * @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * an Object. See `returns` below. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in - * the condition that there system is experiencing board communications failure. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelGet = function () { - // The function to run on timeout - var badCommsTimeout; - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 500); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { - resolve({ - channelNumber: data[data.length - 4], - data: data - }); - } else { - reject(`Error [radioChannelGet]: ${data.toString()}`); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); - }); - }; - - /** - * @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * the poll time when fulfilled. It's important to note that if the board is not on, this function will always - * be rejected with a failure message. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves with the poll time, rejects with an error message. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioPollTimeGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - var pollTime = data[data.length - 4]; - resolve(pollTime); - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); - }); - }; - - /** - * @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the - * Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this - * sets the interval at which the Device polls the Host for new information. Further the function should reject - * if currently streaming. Lastly and more important, if the board is not running the new firmware then this - * functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this - * function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 - * @since 1.0.0 - * @returns {Promise} - Resolves with new poll time, rejects with error message. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioPollTimeSet = function (pollTime) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); - if (!k.isNumber(pollTime)) return reject('Must input type Number'); - if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); - if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); // Ditch the eot $$$ - } else { - reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); - }); - }; - - /** - * @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the - * Host and the Board is the Device. Only the Device can initiate a communication between the two entities. - * There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then - * all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial - * data is sent from the Host to the Serial driver. The rate can either be set to default or fast. - * Further the function should reject if currently streaming. Lastly and more important, if the board is not - * running the new firmware then this functionality does not exist and thus this method will reject. - * If the board is using firmware 2+ then this function should resolve the new baud rate after closing the - * current serial port and reopening one. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) - * @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioBaudRateSet = function (speed) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (!k.isString(speed)) return reject('Must input type String'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - var eotBuf = new Buffer('$$$'); - var newBaudRateBuf; - for (var i = data.length; i > 3; i--) { - if (bufferEqual(data.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = data.slice(i - 9, i - 3); - break; - } - } - var newBaudRateNum = Number(newBaudRateBuf.toString()); - if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { - return reject('Error parse mismatch, restart your system!'); - } - if (!this.isConnected()) { - reject('Lost connection to device during baud set'); - } else if (openBCISample.isSuccessInBuffer(data)) { - // Change the sample rate here - if (this.options.simulate === false) { - this.serial.update({baudRate: newBaudRateNum}, err => { - if (err) return reject(err); - else resolve(newBaudRateNum); - }); - } else { - resolve(newBaudRateNum); - } - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - if (speed === k.OBCIRadioBaudRateFastStr) { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); - } else { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); - } - }); - }; - - /** - * @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are - * in fact ready to start trying to connect and such. The function will reject if not connected to the serial - * port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the - * same channel and powered, then this will resolve true. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioSystemStatusGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (this.options.verbose) console.log(data.toString()); - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(true); - } else { - resolve(false); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); - }); - }; - - /** - * @description List available ports so the user can choose a device when not - * automatically found. - * Note: This method is used for convenience essentially just wrapping up - * serial port. - * @author Andy Heusser (@andyh616) - * @returns {Promise} - On fulfill will contain an array of Serial ports to use. - */ - OpenBCIBoard.prototype.listPorts = function () { - return new Promise((resolve, reject) => { - SerialPort.list((err, ports) => { - if (err) reject(err); - else { - ports.push({ - comName: k.OBCISimulatorPortName, - manufacturer: '', - serialNumber: '', - pnpId: '', - locationId: '', - vendorId: '', - productId: '' - }); - resolve(ports); - } - }); - }); - }; - - /** - * @description Sends a soft reset command to the board - * @returns {Promise} - * Note: The softReset command MUST be sent to the board before you can start - * streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.softReset = function () { - this.curParsingMode = k.OBCIParsingReset; - return this.write(k.OBCIMiscSoftReset); - }; - - /** - * @description To get the specified channelSettings register data from printRegisterSettings call - * @param channelNumber - a number - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - // TODO: REDO THIS FUNCTION - OpenBCIBoard.prototype.getSettingsForChannel = function (channelNumber) { - return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { - // this.searchingBuf = newSearchingBuffer - return this.printRegisterSettings(); - }); - }; - - /** - * @description To print out the register settings to the console - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.printRegisterSettings = function () { - return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { - this.curParsingMode = k.OBCIParsingChannelSettings; - }); - }; - - /** - * @description Send a command to the board to turn a specified channel off - * @param channelNumber - * @returns {Promise.} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelOff = function (channelNumber) { - return k.commandChannelOff(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); - }; - - /** - * @description Send a command to the board to turn a specified channel on - * @param channelNumber - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelOn = function (channelNumber) { - return k.commandChannelOn(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); - }; - - /** - * @description To send a channel setting command to the board - * @param channelNumber - Number (1-16) - * @param powerDown - Bool (true -> OFF, false -> ON (default)) - * turns the channel on or off - * @param gain - Number (1,2,4,6,8,12,24(default)) - * sets the gain for the channel - * @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) - * selects the ADC channel input source - * @param bias - Bool (true -> Include in bias (default), false -> remove from bias) - * selects to include the channel input in bias generation - * @param srb2 - Bool (true -> Connect this input to SRB2 (default), - * false -> Disconnect this input from SRB2) - * Select to connect (true) this channel's P input to the SRB2 pin. This closes - * a switch between P input and SRB2 for the given channel, and allows the - * P input to also remain connected to the ADC. - * @param srb1 - Bool (true -> connect all N inputs to SRB1, - * false -> Disconnect all N inputs from SRB1 (default)) - * Select to connect (true) all channels' N inputs to SRB1. This effects all pins, - * and disconnects all N inputs from the ADC. - * @returns {Promise} resolves if sent, rejects on bad input or no board - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { - var arrayOfCommands = []; - return new Promise((resolve, reject) => { - k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) - .then((val) => { - arrayOfCommands = val.commandArray; - this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; - return this.write(arrayOfCommands); - }).then(resolve, reject); - }); - }; - - /** - * @description Apply the internal test signal to all channels - * @param signal - A string indicating which test signal to apply - * - `dc` - * - Connect to DC signal - * - `ground` - * - Connect to internal GND (VDD - VSS) - * - `pulse1xFast` - * - Connect to test signal 1x Amplitude, fast pulse - * - `pulse1xSlow` - * - Connect to test signal 1x Amplitude, slow pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, fast pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, slow pulse - * - `none` - * - Reset to default - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.testSignal = function (signal) { - return new Promise((resolve, reject) => { - k.getTestSignalCommand(signal) - .then(command => { - return this.write(command); - }) - .then(() => resolve()) - .catch(err => reject(err)); - }); - }; - - /** - * @description - Sends command to turn on impedances for all channels and continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestContinuousStart = function () { - return new Promise((resolve, reject) => { - if (this.impedanceTest.active) return reject('Error: test already active'); - if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); - - this.impedanceTest.active = true; - this.impedanceTest.continuousMode = true; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, true)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); - }; - - /** - * @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestContinuousStop = function () { - return new Promise((resolve, reject) => { - if (!this.impedanceTest.active) return reject('Error: no test active'); - if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); - - this.impedanceTest.active = false; - this.impedanceTest.continuousMode = false; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, false)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); - }; - - /** - * @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a - * little while to actually run (<8 seconds)! - * @returns {Promise} - Resovles when complete testing all the channels. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestAllChannels = function () { - var upperLimit = k.OBCINumberOfChannelsDefault; - - /* istanbul ignore if */ - if (this.options.daisy) { - upperLimit = k.OBCINumberOfChannelsDaisy; - } - - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > upperLimit) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - this.impedanceTestChannel(channelNumber) - .then(() => { - resolve(completeChannelImpedanceTest(channelNumber + 1)); - /* istanbul ignore next */ - }).catch(err => reject(err)); - } - }); - }; - - return completeChannelImpedanceTest(1); - }; - - /** - * @description To test specific input configurations of channels! - * @param arrayOfChannels - The array of configurations where: - * 'p' or 'P' is only test P input - * 'n' or 'N' is only test N input - * 'b' or 'B' is test both inputs (takes 66% longer to run) - * '-' to ignore channel - * EXAMPLE: - * For 8 channel board: ['-','N','n','p','P','-','b','b'] - * (Note: it doesn't matter if capitalized or not) - * @returns {Promise} - Fulfilled with a loaded impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannels = function (arrayOfChannels) { - if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - // Check proper length of array - if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > arrayOfChannels.length) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - - var testCommand = arrayOfChannels[channelNumber - 1]; - - if (testCommand === 'p' || testCommand === 'P') { - this.impedanceTestChannelInputP(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'n' || testCommand === 'N') { - this.impedanceTestChannelInputN(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'b' || testCommand === 'B') { - this.impedanceTestChannel(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else { // skip ('-') condition - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - } - } - }); - }; - return completeChannelImpedanceTest(1); - }; - - /** - * @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannel = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. - }) - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /** - * @description Run impedance test on a single channel, applying the test signal only to P input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannelInputP = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /** - * @description Run impedance test on a single channel, applying the test signal to N input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannelInputN = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /* istanbul ignore next */ - /** - * @description To apply the impedance test signal to an input for any given channel - * @param channelNumber - Number - The channel you want to test. - * @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. - * @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. - * @returns {Promise} - With Number value of channel number - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected'); - - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tSending command to apply test signal to P input.'); - } else if (!pInput && nInput) { - console.log('\tSending command to apply test signal to N input.'); - } else if (pInput && nInput) { - console.log('\tSending command to apply test signal to P and N inputs.'); - } else { - console.log('\tSending command to stop applying test signal to both P and N inputs.'); - } - } - - if (!pInput && !nInput) { - this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort - } else { - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong - } - if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); - // Get impedance settings to send the board - k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { - return this.write(commandsArray); - }).then(() => { - /** - * If either pInput or nInput are true then we should start calculating impedance. Setting - * this.impedanceTest.active to true here allows us to route every sample for an impedance - * calculation instead of the normal sample output. - */ - if (pInput || nInput) this.impedanceTest.active = true; - resolve(channelNumber); - }, (err) => { - reject(err); - }); - }); - }; - - /** - * @description Calculates the impedance for a specified channel for a set time - * @param channelNumber - A Number, the channel number you want to test. - * @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. - * @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tCalculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tCalculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tCalculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - this.impedanceTest.onChannel = channelNumber; - this.impedanceTest.sampleNumber = 0; // Reset the sample number - this.impedanceTest.isTestingPInput = pInput; - this.impedanceTest.isTestingNInput = nInput; - // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) - // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) - setTimeout(() => { // Calculate for 250ms - this.impedanceTest.onChannel = 0; - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tDone calculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tDone calculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tDone calculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; - if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; - resolve(channelNumber); - }, 400); - }); - }; - - /** - * @description Calculates average and gets textual value of impedance for a specified channel - * @param channelNumber - A Number, the channel number you want to finalize. - * @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. - * @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tFinalizing impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tFinalizing impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tFinalizing impedance for P and N input.'); - } else { - console.log('\tNot Finalizing impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - - if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); - if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); - - setTimeout(() => { - resolve(channelNumber); - }, 50); // Introduce a delay to allow for extra time in case of back to back tests - }); - }; - - /** - * @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request - * response from the board. - * @param recordingDuration {String} - The duration you want to log SD information for. Limited to: - * '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' - * @returns {Promise} - Resolves when the command has been written. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sdStart = function (recordingDuration) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - k.sdSettingForString(recordingDuration) - .then(command => { - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(command).then(resolve, reject); - }) - .catch(err => reject(err)); - }); - }; - - /** - * @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted - * with request response from the board. - * @returns {Promise} - Resolves when written - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sdStop = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(k.OBCISDLogStop).then(resolve, reject); - }); - }; - - /** - * @description Get the the current sample rate is. - * @returns {Number} The sample rate - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sampleRate = function () { - if (this.options.simulate) { - return this.options.simulatorSampleRate; - } else { - if (this.info) { - return this.info.sampleRate; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCISampleRate125; - case k.OBCIBoardDefault: - default: - return k.OBCISampleRate250; - } - } - } - }; - - /** - * @description This function is used as a convenience method to determine how many - * channels the current board is using. - * @returns {Number} A number - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.numberOfChannels = function () { - if (this.info) { - return this.info.numberOfChannels; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCINumberOfChannelsDaisy; - case k.OBCIBoardDefault: - default: - return k.OBCINumberOfChannelsDefault; - } - } - }; - - /** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.syncClocks = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); - }); - }; - - /** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs - * don't overlap. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.1.0 - * @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.syncClocksFull = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - var timeout = setTimeout(() => { - return reject('syncClocksFull timeout after 500ms with no sync'); - }, 500); // Should not take more than 1s to sync up - this.sync.eventEmitter = syncObj => { - clearTimeout(timeout); - return resolve(syncObj); - }; - this.once('synced', this.sync.eventEmitter); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet) - .catch(err => { - clearTimeout(timeout); - return reject(err); - }); - }); - }; - - /** - * @description Output passed bytes on the console as a hexdump, if enabled - * @param prefix - label to show to the left of bytes - * @param data - bytes to output, a buffer or string - * @private - */ - OpenBCIBoard.prototype._debugBytes = function (prefix, data) { - if (!this.options.debug) return; - - if (typeof data === 'string') data = new Buffer(data); - - console.log('Debug bytes:'); - - for (var j = 0; j < data.length;) { - var hexPart = ''; - var ascPart = ''; - for (var end = Math.min(data.length, j + 16); j < end; ++j) { - var byt = data[j]; - - var hex = ('0' + byt.toString(16)).slice(-2); - hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in - hexPart += hex; - - var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; - ascPart += asc; - } - - // pad to fixed width for alignment - hexPart = (hexPart + ' ').substring(0, 3 * 17); - - console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); - } - }; - - /** - * @description Consider the '_processBytes' method to be the work horse of this - * entire framework. This method gets called any time there is new - * data coming in on the serial port. If you are familiar with the - * 'serialport' package, then every time data is emitted, this function - * gets sent the input data. The data comes in very fragmented, sometimes - * we get half of a packet, and sometimes we get 3 and 3/4 packets, so - * we will need to store what we don't read for next time. - * @param data - a buffer of unknown size - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processBytes = function (data) { - this._debugBytes(this.curParsingMode + '<<', data); - - // Concat old buffer - var oldDataBuffer = null; - if (this.buffer) { - oldDataBuffer = this.buffer; - data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); - } - - switch (this.curParsingMode) { - case k.OBCIParsingEOT: - if (openBCISample.doesBufferHaveEOT(data)) { - this.curParsingMode = k.OBCIParsingNormal; - this.emit('eot', data); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingReset: - // Does the buffer have an EOT in it? - if (openBCISample.doesBufferHaveEOT(data)) { - this._processParseBufferForReset(data); - this.curParsingMode = k.OBCIParsingNormal; - this.emit('ready'); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingTimeSyncSent: - // If there is only one match - if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { - if (this.options.verbose) console.log(`Found Time Sync Sent`); - this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); - this.curParsingMode = k.OBCIParsingNormal; - } - this.buffer = this._processDataBuffer(data); - break; - case k.OBCIParsingNormal: - default: - this.buffer = this._processDataBuffer(data); - break; - } - - if (this.buffer && oldDataBuffer) { - if (bufferEqual(this.buffer, oldDataBuffer)) { - this.buffer = null; - } - } - }; - - /** - * @description Used to extract samples out of a buffer of unknown length - * @param dataBuffer {Buffer} - A buffer to parse for samples - * @returns {Buffer} - Any data that was not pulled out of the buffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processDataBuffer = function (dataBuffer) { - if (!dataBuffer) return null; - var bytesToParse = dataBuffer.length; - // Exit if we have a buffer with less data than a packet - if (bytesToParse < k.OBCIPacketSize) return dataBuffer; - - var parsePosition = 0; - // Begin parseing - while (parsePosition <= bytesToParse - k.OBCIPacketSize) { - // Is the current byte a head byte that looks like 0xA0 - if (dataBuffer[parsePosition] === k.OBCIByteStart) { - // Now that we know the first is a head byte, let's see if the last one is a - // tail byte 0xCx where x is the set of numbers from 0-F (hex) - if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { - /** We just qualified a raw packet */ - // This could be a time set packet! - this.timeOfPacketArrival = this.time(); - // Grab the raw packet, make a copy of it. - var rawPacket; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } else { - rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } - - // Emit that buffer - this.emit('rawDataPacket', rawPacket); - // Submit the packet for processing - this._processQualifiedPacket(rawPacket); - // Overwrite the dataBuffer with a new buffer - var tempBuf; - if (parsePosition > 0) { - tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); - } else { - tempBuf = dataBuffer.slice(k.OBCIPacketSize); - } - if (tempBuf.length === 0) { - dataBuffer = null; - } else { - if (k.getVersionNumber(process.version) >= 6) { - dataBuffer = Buffer.from(tempBuf); - } else { - dataBuffer = new Buffer(tempBuf); - } - } - // Move the parse position up one packet - parsePosition = -1; - bytesToParse -= k.OBCIPacketSize; - } - } - parsePosition++; - } - - return dataBuffer; - }; - - /** - * @description Alters the global info object by parseing an incoming soft reset key - * @param dataBuffer {Buffer} - The soft reset data buffer - * @returns {Buffer} - If there is data left in the buffer, just it will be returned. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processParseBufferForReset = function (dataBuffer) { - if (openBCISample.countADSPresent(dataBuffer) === 2) { - this.info.boardType = k.OBCIBoardDaisy; - this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; - this.info.sampleRate = k.OBCISampleRate125; - } else { - this.info.boardType = k.OBCIBoardDefault; - this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; - this.info.sampleRate = k.OBCISampleRate250; - } - - if (openBCISample.findV2Firmware(dataBuffer)) { - this.info.firmware = k.OBCIFirmwareV2; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - } else { - this.info.firmware = k.OBCIFirmwareV1; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - } - }; - - /** - * @description Used to route qualified packets to their proper parsers - * @param rawDataPacketBuffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { - if (!rawDataPacketBuffer) return; - if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; - var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); - if (missedPacketArray) { - this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); - } - this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; - var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); - switch (packetType) { - case k.OBCIStreamPacketStandardAccel: - this._processPacketStandardAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketStandardRawAux: - this._processPacketStandardRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketUserDefinedType: - // Do nothing for User Defined Packets - break; - case k.OBCIStreamPacketAccelTimeSyncSet: - // Don't waste any time! - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketAccelTimeSynced: - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSyncSet: - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSynced: - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - default: - // Don't do anything if the packet is not defined - break; - } - }; - - /** - * @description A method used to compute impedances. - * @param sampleObject - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processImpedanceTest = function (sampleObject) { - var impedanceArray; - if (this.impedanceTest.continuousMode) { - // console.log('running in continuous mode...') - // openBCISample.debugPrettyPrint(sampleObject) - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.emit('impedanceArray', impedanceArray); - } - } else if (this.impedanceTest.onChannel !== 0) { - // Only calculate impedance for one channel - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; - } - } - }; - - /** - * @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketStandardAccel = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketStandardRawAux = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @param timeOfPacketArrival {Number} - The time the packet arrived. - * @private - * @returns {Object} - A time sync object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { - /** - * Helper function for creating a bad sync object - * @param err {object} - Can be any object - * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} - */ - var getBadObject = (err) => { - var badObject = openBCISample.newSyncObject(); - badObject.timeOffsetMaster = this.sync.timeOffsetMaster; - // Create an error - badObject.error = err; - return badObject; - }; - - var syncObj = {}; - - if (this.sync.curSyncObj === null) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); - // Missed comma - } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); - } else { - try { - this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; - if (this.options.verbose) console.log('Got time set packet from the board'); - let boardTime = openBCISample.getFromTimePacketTime(rawPacket); - this.sync.curSyncObj.boardTime = boardTime; - // if (this.options.verbose) { - // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) - // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) - // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) - // } - - // Calculate the time between sending the `<` to getting the set packet, call this the round trip length - this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; - if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); - - // If the sync sent conf and set packet arrive in different serial flushes - // ------------------------------------------ - // | | timeTransmission | < GOOD :) - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - // - // Assume it's good... - this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); - - // If the conf and the set packet arrive in the same serial flush we have big problem! - // ------------------------------------------ - // | | | < BAD :( - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { - // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point - this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); - if (this.options.verbose) console.log(`Had to correct transmission time`); - this.sync.curSyncObj.correctedTransmissionTime = true; - } - - // Calculate the offset #finally - this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; - if (this.options.verbose) { - console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); - console.log(`Board time: ${boardTime}`); - } - - // Add to array - if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { - // Shift the oldest one out of the array - this.sync.timeOffsetArray.shift(); - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } else { - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } - - // Calculate the master time offset that we use averaging to compute - if (this.sync.timeOffsetArray.length > 1) { - var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); - this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); - } else { - this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; - } - - this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; - - if (this.options.verbose) { - console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); - } - - // Set the valid object to true - this.sync.curSyncObj.valid = true; - - // Make a byte by byte copy of event - syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); - - // Save obj to the global array - this.sync.objArray.push(syncObj); - } catch (err) { - // Log if verbose. - if (this.options.verbose) console.log(err.message); - syncObj = getBadObject(err); - } - } - // Fix the curParsingMode back to normal - this.curParsingMode = k.OBCIParsingNormal; - // Emit synced object - this.emit(k.OBCIEmitterSynced, syncObj); - // Set global to null - this.sync.curSyncObj = null; - // Return the object - return syncObj; - }; - - /** - * @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets - * an accelerometer value. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncedAccel = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that - * shall be emitted as a raw buffer and not scaled. - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are - * being tested. - * @param sampleObject {Object} - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._finalizeNewSample = function (sampleObject) { - sampleObject._count = this.sampleCount++; - if (this.impedanceTest.active) { - this._processImpedanceTest(sampleObject); - } else { - // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper - // channels (9-16) come in packets with even sample numbers - if (this.info.boardType === k.OBCIBoardDaisy) { - // Send the sample for downstream sample compaction - this._finalizeNewSampleForDaisy(sampleObject); - } else { - this.emit(k.OBCIEmitterSample, sampleObject); - } - } - }; - - /** - * @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber - * sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a - * sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be - * emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the - * missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. - * @param sampleObject {Object} - The sample object to finalize - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._finalizeNewSampleForDaisy = function (sampleObject) { - if (openBCISample.isOdd(sampleObject.sampleNumber)) { - // Check for the skipped packet condition - if (this._lowerChannelsSampleObject) { - // The last packet was odd... missed the even packet - this.info.missedPackets++; - } - this._lowerChannelsSampleObject = sampleObject; - } else { - // Make sure there is an odd packet waiting to get merged with this packet - if (this._lowerChannelsSampleObject) { - // Merge these two samples - var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); - // Set the _lowerChannelsSampleObject object to null - this._lowerChannelsSampleObject = null; - // Emite the new merged sample - this.emit('sample', mergedSample); - } else { - // Missed the odd packet, i.e. two evens in a row - this.info.missedPackets++; - } - } - }; - - /** - * @description Reset the master buffer and reset the number of bad packets. - * @author AJ Keller (@pushtheworldllc) - */ - // TODO: nothing is using these constructs, but they look like good constructs. See contents of masterBufferMaker() - OpenBCIBoard.prototype._reset_ABANDONED = function () { - this.masterBuffer = masterBufferMaker(); - this.badPackets = 0; - }; - - /** - * @description Stateful method for querying the current offset only when the last - * one is too old. (defaults to daily) - * @returns {Promise} A promise with the time offset - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sntpGetOffset = function () { - this.options.sntpTimeSync = true; - - if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); - - return new Promise((resolve, reject) => { - Sntp.offset({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, function (err, offset) { - if (err) reject(err); - else resolve(offset); - }); - }); - }; - - /** - * @description Allows users to utilize all features of sntp if they want to... - */ - OpenBCIBoard.prototype.sntp = Sntp; - - /** - * @description This gets the time plus offset - * @private - */ - OpenBCIBoard.prototype._sntpNow = Sntp.now; - - /** - * @description This starts the SNTP server and gets it to remain in sync with the SNTP server - * @returns {Promise} - A promise if the module was able to sync with ntp server. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sntpStart = function (options) { - this.options.sntpTimeSync = true; - - // Sntp.start doesn't actually report errors (2016-10-29) - // so functionality is first detected via sntpGetOffset - return this.sntpGetOffset().then(() => { - return new Promise((resolve, reject) => { - Sntp.start({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, () => { - this.sync.sntpActive = true; - this.emit('sntpTimeLock'); - resolve(); - }); - }); - }); - }; - - /** - * @description Stops the sntp from updating. - */ - OpenBCIBoard.prototype.sntpStop = function () { - Sntp.stop(); - this.options.sntpTimeSync = false; - this.sync.sntpActive = false; - }; - - /** - * @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time - * @returns {Number} - The time - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.time = function () { - if (this.options.sntpTimeSync) { - return this._sntpNow(); - } else { - return Date.now(); - } - }; - - /** - * @description This prints the total number of packets that were not able to be read - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printPacketsBad = function () { - if (this.badPackets > 1) { - console.log('Dropped a total of ' + this.badPackets + ' packets.'); - } else if (this.badPackets === 1) { - console.log('Dropped a total of 1 packet.'); - } else { - console.log('No packets dropped.'); - } - }; - - /** - * @description This prints the total bytes in - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printBytesIn = function () { - if (this.bytesIn > 1) { - console.log('Read in ' + this.bytesIn + ' bytes.'); - } else if (this.bytesIn === 1) { - console.log('Read one 1 packet in.'); - } else { - console.log('Read no packets.'); - } - }; - - /** - * @description This prints the total number of packets that have been read - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printPacketsRead = function () { - if (this.masterBuffer.packetsRead > 1) { - console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); - } else if (this.masterBuffer.packetsIn === 1) { - console.log('Read 1 packet.'); - } else { - console.log('No packets read.'); - } - }; - - /** - * @description Nice convenience method to print some session details - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.debugSession = function () { - this.printBytesIn(); - this.printPacketsRead(); - this.printPacketsBad(); - }; - - /** - * @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) - * @param channelSettingsObj - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.debugPrintChannelSettings = function (channelSettingsObj) { - console.log('-- Channel Settings Object --'); - var powerState = 'OFF'; - if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { - powerState = 'ON'; - } - console.log('---- POWER STATE: ' + powerState); - console.log('-- END --'); - }; - - /** - * @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. - * @param channelSettingsObject - * @returns {boolean} - */ - OpenBCIBoard.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { - return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; - }; - - // TODO: checkConnection (py: check_connection) - // TODO: reconnect (py: reconnect) - // TODO: testAuto - // TODO: getNbAUXChannels - // TODO: printIncomingText (py: print_incomming_text) - // TODO: warn - - factory.OpenBCIBoard = OpenBCIBoard; - factory.OpenBCIConstants = k; - factory.OpenBCISample = openBCISample; -} - -util.inherits(OpenBCIFactory, EventEmitter); - -module.exports = new OpenBCIFactory(); - -/** -* @description To parse a given channel given output from a print registers query -* @param rawChannelBuffer -* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -// function getChannelSettingsObj (rawChannelBuffer) { -// return new Promise(function (resolve, reject) { -// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { -// reject('Undefined or null channel buffer'); -// } -// -// var channelSettingsObject = { -// CHANNEL: '0', -// POWER_DOWN: '0', -// GAIN_SET: '0', -// INPUT_TYPE_SET: '0', -// BIAS_SET: '0', -// SRB2_SET: '0', -// SRB1_SET: '0' -// }; -// -// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits -// var sizeOfData = rawChannelBuffer.byteLength; -// -// var objIndex = 0; -// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data -// switch (objIndex) { -// case 0: -// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); -// break; -// default: -// break; -// } -// -// objIndex++; -// } -// resolve(channelSettingsObject); -// }); -// } - -function masterBufferMaker () { - var masterBuf = new Buffer(k.OBCIMasterBufferSize); - masterBuf.fill(0); - return { // Buffer used to store bytes in and read packets from - buffer: masterBuf, - positionRead: 0, - positionWrite: 0, - packetsIn: 0, - packetsRead: 0, - looseBytes: 0 - }; -} diff --git a/openBCIConstants.js b/openBCIConstants.js index 8b22505..990792b 100644 --- a/openBCIConstants.js +++ b/openBCIConstants.js @@ -203,6 +203,7 @@ const obciSimulatorFragmentationNone = 'none'; /** Possible Sample Rates */ const obciSampleRate125 = 125; +const obciSampleRate200 = 200; const obciSampleRate250 = 250; /** Max sample number */ @@ -232,9 +233,13 @@ const obciByteStart = 0xA0; const obciByteStop = 0xC0; /** Errors */ +const errorNobleAlreadyScanning = 'Scan already under way'; +const errorNobleNotAlreadyScanning = 'No scan started'; +const errorNobleNotInPoweredOnState = 'Please turn blue tooth on.'; const errorInvalidByteLength = 'Invalid Packet Byte Length'; const errorInvalidByteStart = 'Invalid Start Byte'; const errorInvalidByteStop = 'Invalid Stop Byte'; +const errorInvalidType = 'Invalid Type'; const errorTimeSyncIsNull = "'this.sync.curSyncObj' must not be null"; const errorTimeSyncNoComma = 'Missed the time sync sent confirmation. Try sync again'; const errorUndefinedOrNullInput = 'Undefined or Null Input'; @@ -337,6 +342,7 @@ const obciEmitterClose = 'close'; const obciEmitterDroppedPacket = 'droppedPacket'; const obciEmitterError = 'error'; const obciEmitterImpedanceArray = 'impedanceArray'; +const obciEmitterMessage = 'message'; const obciEmitterQuery = 'query'; const obciEmitterRawDataPacket = 'rawDataPacket'; const obciEmitterReady = 'ready'; @@ -749,6 +755,7 @@ module.exports = { }, /** Possible Sample Rates */ OBCISampleRate125: obciSampleRate125, + OBCISampleRate200: obciSampleRate200, OBCISampleRate250: obciSampleRate250, /** Max sample number */ OBCISampleNumberMax: obciSampleNumberMax, @@ -758,9 +765,13 @@ module.exports = { OBCIByteStart: obciByteStart, OBCIByteStop: obciByteStop, /** Errors */ + OBCIErrorNobleAlreadyScanning: errorNobleAlreadyScanning, + OBCIErrorNobleNotAlreadyScanning: errorNobleNotAlreadyScanning, + OBCIErrorNobleNotInPoweredOnState: errorNobleNotInPoweredOnState, OBCIErrorInvalidByteLength: errorInvalidByteLength, OBCIErrorInvalidByteStart: errorInvalidByteStart, OBCIErrorInvalidByteStop: errorInvalidByteStop, + OBCIErrorInvalidType: errorInvalidType, OBCIErrorTimeSyncIsNull: errorTimeSyncIsNull, OBCIErrorTimeSyncNoComma: errorTimeSyncNoComma, OBCIErrorUndefinedOrNullInput: errorUndefinedOrNullInput, @@ -896,6 +907,7 @@ module.exports = { OBCIEmitterDroppedPacket: obciEmitterDroppedPacket, OBCIEmitterError: obciEmitterError, OBCIEmitterImpedanceArray: obciEmitterImpedanceArray, + OBCIEmitterMessage: obciEmitterMessage, OBCIEmitterQuery: obciEmitterQuery, OBCIEmitterRawDataPacket: obciEmitterRawDataPacket, OBCIEmitterReady: obciEmitterReady, diff --git a/openBCICyton.js b/openBCICyton.js new file mode 100644 index 0000000..33ac7bf --- /dev/null +++ b/openBCICyton.js @@ -0,0 +1,2351 @@ +'use strict'; +const bufferEqual = require('buffer-equal'); +const EventEmitter = require('events').EventEmitter; +const math = require('mathjs'); +const util = require('util'); +const SerialPort = require('serialport'); +const openBCISample = require('./openBCISample'); +const k = openBCISample.k; +const openBCISimulator = require('./openBCISimulator'); +const Sntp = require('sntp'); +const openBCIUtils = require('./openBCIUtils'); +/** +* @description SDK for OpenBCI Board {@link www.openbci.com} +* @module 'openbci-sdk' +*/ + +const _options = { + boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], + baudRate: 115200, + simulate: false, + simulatorBoardFailure: false, + simulatorDaisyModuleAttached: false, + simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], + simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], + simulatorLatencyTime: 16, + simulatorBufferSize: 4096, + simulatorHasAccelerometer: true, + simulatorInternalClockDrift: 0, + simulatorInjectAlpha: true, + simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], + simulatorSampleRate: 250, + simulatorSerialPortFailure: false, + sntpTimeSync: false, + sntpTimeSyncHost: 'pool.ntp.org', + sntpTimeSyncPort: 123, + verbose: false, + debug: false +}; + +/** +* @description The initialization method to call first, before any other method. +* @param options (optional) - Board optional configurations. +* - `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if +* firmware on board has been previously configured. +* +* - `boardType` {String} - Specifies type of OpenBCI board. +* 3 Possible Boards: +* `default` - 8 Channel OpenBCI board (Default) +* `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. +* `ganglion` - 4 Channel board +* (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) +* +* - `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting +* `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) +* +* - `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on +* the board is not polling the RFduino on the dongle. (Default `false`) +* +* - `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. +* This is useful if you want to test how your application reacts to a user requesting 16 channels +* but there is no daisy module actually attached, or vice versa, where there is a daisy module +* attached and the user only wants to use 8 channels. (Default `false`) +* +* - `simulatorFirmwareVersion` {String} - Allows simulator to be started with firmware version 2 features +* 2 Possible Options: +* `v1` - Firmware Version 1 (Default) +* `v2` - Firmware Version 2 +* +* - `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which +* occurs commonly in real devices. It is recommended to test code with this enabled. +* 4 Possible Options: +* `none` - do not fragment packets; output complete chunks immediately when produced (Default) +* `random` - output random small chunks of data interspersed with full buffers +* `fullBuffers` - allow buffers to fill up until the latency timer has expired +* `oneByOne` - output each byte separately +* +* - `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers, + if `simulatorFragmentation` is specified. (Default `16`) +* +* - `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is +* specified. (Default `4096`) +* +* - `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) +* +* - `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) +* +* - `simulatorInjectLineNoise` {String} - Injects line noise on channels. +* 3 Possible Options: +* `60Hz` - 60Hz line noise (Default) [America] +* `50Hz` - 50Hz line noise [Europe] +* `none` - Do not inject line noise. +* +* - `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if +* `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that +* setting and this sample rate will be used. (Default is `250`) +* +* - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely +* due to a OpenBCI dongle not being plugged in. +* +* - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source +* of truth instead of local computer time. If you are running experiements on your local +* computer, keep this `false`. (Default `false`) +* +* - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). +* +* - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`) +* +* - `verbose` {Boolean} - Print out useful debugging events. (Default `false`) +* +* - `debug` {Boolean} - Print out a raw dump of bytes sent and received. (Default `false`) +* +* @constructor +* @author AJ Keller (@pushtheworldllc) +*/ +function Cyton (options) { + options = (typeof options !== 'function') && options || {}; + var opts = {}; + + /** Configuring Options */ + var o; + for (o in _options) { + var userOption = (o in options) ? o : o.toLowerCase(); + var userValue = options[userOption]; + delete options[userOption]; + + 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'); + + // Set to global options object + this.options = opts; + + /** Properties (keep alphabetical) */ + // Arrays + this.accelArray = [0, 0, 0]; // X, Y, Z + this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); + this.writeOutArray = []; + // Booleans + this._streaming = false; + // Buffers + this.buffer = null; + this.masterBuffer = masterBufferMaker(); + // Objects + this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType)); + this.impedanceTest = { + active: false, + isTestingPInput: false, + isTestingNInput: false, + onChannel: 0, + sampleNumber: 0, + continuousMode: false, + impedanceForChannel: 0 + }; + this.info = { + boardType: this.options.boardType, + sampleRate: k.OBCISampleRate125, + firmware: k.OBCIFirmwareV1, + numberOfChannels: k.OBCINumberOfChannelsDefault, + missedPackets: 0 + }; + if (this.options.boardType === k.OBCIBoardDefault) { + this.info.sampleRate = k.OBCISampleRate250; + } + + this._lowerChannelsSampleObject = null; + this.serial = null; + this.sync = { + curSyncObj: null, + eventEmitter: null, + objArray: [], + sntpActive: false, + timeOffsetMaster: 0, + timeOffsetAvg: 0, + timeOffsetArray: [] + }; + this.writer = null; + // Numbers + this.badPackets = 0; + this.curParsingMode = k.OBCIParsingReset; + this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); + this.previousSampleNumber = -1; + this.sampleCount = 0; + this.timeOfPacketArrival = 0; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; + // Strings + + // NTP + if (this.options.sntpTimeSync) { + // establishing ntp connection + this.sntpStart() + .catch(ignored => { + // try again once after a delay + return new Promise((resolve, reject) => { + setTimeout(resolve, 500); + }).then(() => this.sntpStart()); + }) + .then(() => { + if (this.options.verbose) console.log('SNTP: connected'); + }) + .catch(err => { + if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); + this.emit('error', err); + }); + } +} + +/** +* @description The essential precursor method to be called initially to establish a +* serial connection to the OpenBCI board. +* @param portName - a string that contains the port name of the Cyton. +* @returns {Promise} if the board was able to connect. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.connect = function (portName) { + return new Promise((resolve, reject) => { + if (this.isConnected()) return reject('already connected!'); + + /* istanbul ignore else */ + if (this.options.simulate || portName === k.OBCISimulatorPortName) { + this.options.simulate = true; + // If we are simulating, set portName to fake name + this.portName = k.OBCISimulatorPortName; + if (this.options.verbose) console.log('using faux board ' + portName); + this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { + accel: this.options.simulatorHasAccelerometer, + alpha: this.options.simulatorInjectAlpha, + boardFailure: this.options.simulatorBoardFailure, + daisy: this.options.simulatorDaisyModuleAttached, + drift: this.options.simulatorInternalClockDrift, + firmwareVersion: this.options.simulatorFirmwareVersion, + fragmentation: this.options.simulatorFragmentation, + latencyTime: this.options.simulatorLatencyTime, + bufferSize: this.options.simulatorBufferSize, + lineNoise: this.options.simulatorInjectLineNoise, + sampleRate: this.options.simulatorSampleRate, + serialPortFailure: this.options.simulatorSerialPortFailure, + verbose: this.options.verbose + }); + } else { + this.portName = portName; + if (this.options.verbose) console.log('using real board ' + portName); + this.serial = new SerialPort(portName, { + baudRate: this.options.baudRate + }, (err) => { + if (err) reject(err); + }); + } + + if (this.options.verbose) console.log('Serial port connected'); + + this.serial.on('data', data => { + this._processBytes(data); + }); + this.serial.once('open', () => { + if (this.options.verbose) console.log('Serial port open'); + new Promise(resolve => { + // TODO: document why this 300 ms delay is needed + setTimeout(resolve, this.options.simulate ? 50 : 300); + }).then(() => { + if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); + return this.write(k.OBCIStreamStop); + }).then(() => { + return new Promise(resolve => this.serial.flush(resolve)); + }).then(() => { + // TODO: document why this 250 ms delay is needed + return new Promise(resolve => setTimeout(resolve, 250)); + }).then(() => { + if (this.options.verbose) console.log('Sending soft reset'); + // TODO: this promise chain resolves early because + // A. some legacy code (in tests) sets the ready handler after this resolves + // and + // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented + // which is C. not implemented yet except in a manner such that replies occur in the write handler, + // resulting in the EOT arriving before this resolves + // Fix one or more of the above 3 situations, then move resolve() to the next block. + resolve(); + return this.softReset(); + }).then(() => { + if (this.options.verbose) console.log("Waiting for '$$$'"); + }); + }); + this.serial.once('close', () => { + if (this.options.verbose) console.log('Serial Port Closed'); + // 'close' is emitted in _disconnected() + this._disconnected('port closed'); + }); + this.serial.once('error', (err) => { + if (this.options.verbose) console.log('Serial Port Error'); + this.emit('error', err); + this._disconnected(err); + }); + }); +}; + +/** +* @description Called once when for any reason the serial port is no longer open. +* @private +*/ +Cyton.prototype._disconnected = function (err) { + this._streaming = false; + + clearTimeout(this.writer); + this.writer = null; + + this.serial.removeAllListeners(); + this.serial = null; + + this.emit('close'); + + while (this.writeOutArray.length > 0) { + var command = this.writeOutArray.pop(); + if (command.reject) command.reject(err); + } +}; + +/** +* @description Closes the serial port. Waits for stop streaming command to +* be sent if currently streaming. +* @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.disconnect = function () { + return Promise.resolve() + .then(() => { + if (this.isStreaming()) { + if (this.options.verbose) console.log('stop streaming'); + return this.streamStop(); + } + }) + .then(() => { + if (!this.isConnected()) { + return Promise.reject('no board connected'); + } else { + return new Promise((resolve, reject) => { + // serial emitting 'close' will call _disconnected + this.serial.close(() => { + resolve(); + }); + }); + } + }); +}; + +/** +* @description Checks if the driver is connected to a board. +* @returns {boolean} - True if connected. +*/ +Cyton.prototype.isConnected = function () { + if (!this.serial) return false; + return this.serial.isOpen(); +}; + +/** +* @description Checks if the board is currently sending samples. +* @returns {boolean} - True if streaming. +*/ +Cyton.prototype.isStreaming = function () { + return this._streaming; +}; + +/** +* @description Sends a start streaming command to the board. +* @returns {Promise} indicating if the signal was able to be sent. +* Note: You must have successfully connected to an OpenBCI board using the connect +* method. Just because the signal was able to be sent to the board, does not +* mean the board will start streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.streamStart = function () { + return new Promise((resolve, reject) => { + if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); + this._streaming = true; + this._reset_ABANDONED(); // framework is incomplete but looks useful + this.write(k.OBCIStreamStart).then(resolve, reject); + }); +}; + +/** +* @description Sends a stop streaming command to the board. +* @returns {Promise} indicating if the signal was able to be sent. +* Note: You must have successfully connected to an OpenBCI board using the connect +* method. Just because the signal was able to be sent to the board, does not +* mean the board stopped streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.streamStop = function () { + return new Promise((resolve, reject) => { + if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); + this._streaming = false; + this.write(k.OBCIStreamStop).then(resolve, reject); + }); +}; + +/** +* @description To start simulating an open bci board +* Note: Must be called after the constructor +* @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.simulatorEnable = function () { + return new Promise((resolve, reject) => { + if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? + if (this.isConnected()) { + this.disconnect() // disconnect first + .then(() => { + this.options.simulate = true; + resolve(); + }) + .catch(err => reject(err)); + } else { + this.options.simulate = true; + resolve(); + } + }); +}; + +/** +* @description To stop simulating an open bci board +* Note: Must be called after the constructor +* @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.simulatorDisable = function () { + return new Promise((resolve, reject) => { + if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? + if (this.isConnected()) { + this.disconnect() + .then(() => { + this.options.simulate = false; + resolve(); + }) + .catch(err => reject(err)); + } else { + this.options.simulate = false; + resolve(); + } + }); +}; + +/** +* @description To be able to easily write to the board but ensure that we never send commands +* with less than a 10ms spacing between sends in early version boards. This uses +* an array and shifts off the entries until there are none left. +* @param dataToWrite - Either a single character or an Array of characters +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.write = function (dataToWrite) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) { + reject('not connected'); + } else { + if (Array.isArray(dataToWrite)) { // Got an input array + var len = dataToWrite.length; + for (var i = 0; i < len; i++) { + this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); + } + this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; + } else { + this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); + } + + if (!this.writer) { // there is no writer started + var writerFunction = () => { + if (this.writeOutArray.length === 0) { + this.writer = null; + return; + } + + var command = this.writeOutArray.shift(); + var promise = this._writeAndDrain(command.cmd); + + promise.then(() => { + this.writer = setTimeout(writerFunction, this.writeOutDelay); + }, () => { + // write failed but more commands may be pending that need a result + writerFunction(); + }); + + if (command.reject) { + promise.catch(err => { + if (this.options.verbose) console.log('write failure: ' + err); + command.reject(err); + }); + } + if (command.resolve) promise.then(command.resolve); + }; + this.writer = setTimeout(writerFunction, this.writeOutDelay); + } + } + }); +}; + +/** +* @description Should be used to send data to the board +* @param data {Buffer} - The data to write out +* @returns {Promise} if signal was able to be sent +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._writeAndDrain = function (data) { + if (this.options.debug) openBCIUtils.debugBytes('>>>', data); + + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Serial port not open'); + this.serial.write(data, (error) => { + if (error) { + console.log('Error [writeAndDrain]: ' + error); + reject(error); + } else { + this.serial.drain(function () { + resolve(); + }); + } + }); + }); +}; + +/** +* @description Automatically find an OpenBCI board. +* Note: This method is used for convenience and should be used when trying to +* connect to a board. If you find a case (i.e. a platform (linux, +* windows...) that this does not work, please open an issue and +* we will add support! +* @returns {Promise} - Fulfilled with portName, rejected when can't find the board. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.autoFindOpenBCIBoard = function () { + var serialPatterns = [ + { // mac + comName: /usbserial-D/ + }, + { // linux + comName: /^\/dev\/ttyUSB/, + manufacturer: /^FTDI$/, + serialNumber: /^FTDI_FT231X_USB_UART/, + vendorId: /^0x0403$/, + productId: /^0x6015$/ + } + ]; + return new Promise((resolve, reject) => { + /* istanbul ignore else */ + if (this.options.simulate) { + this.portName = k.OBCISimulatorPortName; + if (this.options.verbose) console.log('auto found sim board'); + resolve(k.OBCISimulatorPortName); + } else { + SerialPort.list((err, ports) => { + if (err) { + if (this.options.verbose) console.log('serial port err'); + reject(err); + } + // This is one big if statement + if (ports.some(port => { + return serialPatterns.some(patterns => { + for (var attribute in patterns) { + if (!String(port[attribute]).match(patterns[attribute])) { + return false; + } + } + this.portName = port.comName; + return true; + }); + })) { + if (this.options.verbose) console.log('auto found board'); + resolve(this.portName); + } else { + if (this.options.verbose) console.log('could not find board'); + reject('Could not auto find board'); + } + }); + } + }); +}; + +/** +* @description Convenience method to determine if you can use firmware v2.x.x +* capabilities. +* @returns {boolean} - True if using firmware version 2 or greater. Should +* be called after a `.softReset()` because we can parse the output of that +* to determine if we are using firmware version 2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.usingVersionTwoFirmware = function () { + if (this.options.simulate) { + return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; + } else { + return this.info.firmware === k.OBCIFirmwareV2; + } +}; + +/** +* @description Used to set the system radio channel number. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. +* @since 1.0.0 +* @returns {Promise} - Resolves with the new channel number, rejects with err. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelSet = function (channelNumber) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); + if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); + if (!k.isNumber(channelNumber)) return reject('Must input type Number'); + if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); + if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); + } else { + reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); + }); +}; + +/** +* @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if +* your dongle and board are not on the right channel and bring down your radio system if you take your +* dongle and board are not on the same channel. Use with caution! The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. +* @since 1.0.0 +* @returns {Promise} - Resolves with the new channel number, rejects with err. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelSetHostOverride = function (channelNumber) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); + if (!k.isNumber(channelNumber)) return reject('Must input type Number'); + if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); + if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(`${data.toString()}`); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); + } else { + reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); + }); +}; + +/** +* @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve +* an Object. See `returns` below. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in +* the condition that there system is experiencing board communications failure. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelGet = function () { + // The function to run on timeout + var badCommsTimeout; + + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 500); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + if (openBCISample.isSuccessInBuffer(data)) { + resolve({ + channelNumber: data[data.length - 4], + data: data + }); + } else { + reject(`Error [radioChannelGet]: ${data.toString()}`); + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); + }); +}; + +/** +* @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve +* the poll time when fulfilled. It's important to note that if the board is not on, this function will always +* be rejected with a failure message. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves with the poll time, rejects with an error message. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioPollTimeGet = function () { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + var pollTime = data[data.length - 4]; + resolve(pollTime); + } else { + reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); + }); +}; + +/** +* @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the +* Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this +* sets the interval at which the Device polls the Host for new information. Further the function should reject +* if currently streaming. Lastly and more important, if the board is not running the new firmware then this +* functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this +* function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 +* @since 1.0.0 +* @returns {Promise} - Resolves with new poll time, rejects with error message. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioPollTimeSet = function (pollTime) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); + if (!k.isNumber(pollTime)) return reject('Must input type Number'); + if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); + if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); // Ditch the eot $$$ + } else { + reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); + }); +}; + +/** +* @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the +* Host and the Board is the Device. Only the Device can initiate a communication between the two entities. +* There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then +* all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial +* data is sent from the Host to the Serial driver. The rate can either be set to default or fast. +* Further the function should reject if currently streaming. Lastly and more important, if the board is not +* running the new firmware then this functionality does not exist and thus this method will reject. +* If the board is using firmware 2+ then this function should resolve the new baud rate after closing the +* current serial port and reopening one. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) +* @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioBaudRateSet = function (speed) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (!k.isString(speed)) return reject('Must input type String'); + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + var eotBuf = new Buffer('$$$'); + var newBaudRateBuf; + for (var i = data.length; i > 3; i--) { + if (bufferEqual(data.slice(i - 3, i), eotBuf)) { + newBaudRateBuf = data.slice(i - 9, i - 3); + break; + } + } + var newBaudRateNum = Number(newBaudRateBuf.toString()); + if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { + return reject('Error parse mismatch, restart your system!'); + } + if (!this.isConnected()) { + reject('Lost connection to device during baud set'); + } else if (openBCISample.isSuccessInBuffer(data)) { + // Change the sample rate here + if (this.options.simulate === false) { + this.serial.update({baudRate: newBaudRateNum}, err => { + if (err) return reject(err); + else resolve(newBaudRateNum); + }); + } else { + resolve(newBaudRateNum); + } + } else { + reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + if (speed === k.OBCIRadioBaudRateFastStr) { + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); + } else { + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); + } + }); +}; + +/** +* @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are +* in fact ready to start trying to connect and such. The function will reject if not connected to the serial +* port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the +* same channel and powered, then this will resolve true. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioSystemStatusGet = function () { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (this.options.verbose) console.log(data.toString()); + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(true); + } else { + resolve(false); + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); + }); +}; + +/** +* @description List available ports so the user can choose a device when not +* automatically found. +* Note: This method is used for convenience essentially just wrapping up +* serial port. +* @author Andy Heusser (@andyh616) +* @returns {Promise} - On fulfill will contain an array of Serial ports to use. +*/ +Cyton.prototype.listPorts = function () { + return new Promise((resolve, reject) => { + SerialPort.list((err, ports) => { + if (err) reject(err); + else { + ports.push({ + comName: k.OBCISimulatorPortName, + manufacturer: '', + serialNumber: '', + pnpId: '', + locationId: '', + vendorId: '', + productId: '' + }); + resolve(ports); + } + }); + }); +}; + +/** +* @description Sends a soft reset command to the board +* @returns {Promise} +* Note: The softReset command MUST be sent to the board before you can start +* streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.softReset = function () { + this.curParsingMode = k.OBCIParsingReset; + return this.write(k.OBCIMiscSoftReset); +}; + +// /** +// * @description To get the specified channelSettings register data from printRegisterSettings call +// * @param channelNumber - a number +// * @returns {Promise.|*} +// * @author AJ Keller (@pushtheworldllc) +// */ +// // TODO: REDO THIS FUNCTION +// Cyton.prototype.getSettingsForChannel = function (channelNumber) { +// return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { +// // this.searchingBuf = newSearchingBuffer +// return this.printRegisterSettings(); +// }); +// }; + +/** +* @description To print out the register settings to the console +* @returns {Promise.|*} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.printRegisterSettings = function () { + return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { + this.curParsingMode = k.OBCIParsingEOT; + }); +}; + +/** +* @description Send a command to the board to turn a specified channel off +* @param channelNumber +* @returns {Promise.} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelOff = function (channelNumber) { + return k.commandChannelOff(channelNumber).then((charCommand) => { + // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) + return this.write(charCommand); + }); +}; + +/** +* @description Send a command to the board to turn a specified channel on +* @param channelNumber +* @returns {Promise.|*} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelOn = function (channelNumber) { + return k.commandChannelOn(channelNumber).then((charCommand) => { + // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) + return this.write(charCommand); + }); +}; + +/** +* @description To send a channel setting command to the board +* @param channelNumber - Number (1-16) +* @param powerDown - Bool (true -> OFF, false -> ON (default)) +* turns the channel on or off +* @param gain - Number (1,2,4,6,8,12,24(default)) +* sets the gain for the channel +* @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) +* selects the ADC channel input source +* @param bias - Bool (true -> Include in bias (default), false -> remove from bias) +* selects to include the channel input in bias generation +* @param srb2 - Bool (true -> Connect this input to SRB2 (default), +* false -> Disconnect this input from SRB2) +* Select to connect (true) this channel's P input to the SRB2 pin. This closes +* a switch between P input and SRB2 for the given channel, and allows the +* P input to also remain connected to the ADC. +* @param srb1 - Bool (true -> connect all N inputs to SRB1, +* false -> Disconnect all N inputs from SRB1 (default)) +* Select to connect (true) all channels' N inputs to SRB1. This effects all pins, +* and disconnects all N inputs from the ADC. +* @returns {Promise} resolves if sent, rejects on bad input or no board +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { + var arrayOfCommands = []; + return new Promise((resolve, reject) => { + k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) + .then((val) => { + arrayOfCommands = val.commandArray; + this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; + return this.write(arrayOfCommands); + }).then(resolve, reject); + }); +}; + +/** +* @description Apply the internal test signal to all channels +* @param signal - A string indicating which test signal to apply +* - `dc` +* - Connect to DC signal +* - `ground` +* - Connect to internal GND (VDD - VSS) +* - `pulse1xFast` +* - Connect to test signal 1x Amplitude, fast pulse +* - `pulse1xSlow` +* - Connect to test signal 1x Amplitude, slow pulse +* - `pulse2xFast` +* - Connect to test signal 2x Amplitude, fast pulse +* - `pulse2xFast` +* - Connect to test signal 2x Amplitude, slow pulse +* - `none` +* - Reset to default +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.testSignal = function (signal) { + return new Promise((resolve, reject) => { + k.getTestSignalCommand(signal) + .then(command => { + return this.write(command); + }) + .then(() => resolve()) + .catch(err => reject(err)); + }); +}; + +/** +* @description - Sends command to turn on impedances for all channels and continuously calculate their impedances +* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestContinuousStart = function () { + return new Promise((resolve, reject) => { + if (this.impedanceTest.active) return reject('Error: test already active'); + if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); + + this.impedanceTest.active = true; + this.impedanceTest.continuousMode = true; + + var chain = Promise.resolve(); + for (var i = 0; i < this.numberOfChannels(); i++) { + chain = chain + .then(() => k.getImpedanceSetter(i + 1, false, true)) + .then((commandsArray) => this.write(commandsArray)); + } + chain.then(resolve, reject); + }); +}; + +/** +* @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances +* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestContinuousStop = function () { + return new Promise((resolve, reject) => { + if (!this.impedanceTest.active) return reject('Error: no test active'); + if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); + + this.impedanceTest.active = false; + this.impedanceTest.continuousMode = false; + + var chain = Promise.resolve(); + for (var i = 0; i < this.numberOfChannels(); i++) { + chain = chain + .then(() => k.getImpedanceSetter(i + 1, false, false)) + .then((commandsArray) => this.write(commandsArray)); + } + chain.then(resolve, reject); + }); +}; + +/** +* @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a +* little while to actually run (<8 seconds)! +* @returns {Promise} - Resovles when complete testing all the channels. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestAllChannels = function () { + var upperLimit = k.OBCINumberOfChannelsDefault; + + /* istanbul ignore if */ + if (this.options.daisy) { + upperLimit = k.OBCINumberOfChannelsDaisy; + } + + if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + + // Recursive function call + var completeChannelImpedanceTest = (channelNumber) => { + return new Promise((resolve, reject) => { + if (channelNumber > upperLimit) { // Base case! + this.emit('impedanceArray', this.impedanceArray); + this.impedanceTest.onChannel = 0; + resolve(); + } else { + if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); + this.impedanceTestChannel(channelNumber) + .then(() => { + resolve(completeChannelImpedanceTest(channelNumber + 1)); + /* istanbul ignore next */ + }).catch(err => reject(err)); + } + }); + }; + + return completeChannelImpedanceTest(1); +}; + +/** +* @description To test specific input configurations of channels! +* @param arrayOfChannels - The array of configurations where: +* 'p' or 'P' is only test P input +* 'n' or 'N' is only test N input +* 'b' or 'B' is test both inputs (takes 66% longer to run) +* '-' to ignore channel +* EXAMPLE: +* For 8 channel board: ['-','N','n','p','P','-','b','b'] +* (Note: it doesn't matter if capitalized or not) +* @returns {Promise} - Fulfilled with a loaded impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannels = function (arrayOfChannels) { + if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); + if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + // Check proper length of array + if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); + + // Recursive function call + var completeChannelImpedanceTest = (channelNumber) => { + return new Promise((resolve, reject) => { + if (channelNumber > arrayOfChannels.length) { // Base case! + this.emit('impedanceArray', this.impedanceArray); + this.impedanceTest.onChannel = 0; + resolve(); + } else { + if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); + + var testCommand = arrayOfChannels[channelNumber - 1]; + + if (testCommand === 'p' || testCommand === 'P') { + this.impedanceTestChannelInputP(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else if (testCommand === 'n' || testCommand === 'N') { + this.impedanceTestChannelInputN(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else if (testCommand === 'b' || testCommand === 'B') { + this.impedanceTestChannel(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else { // skip ('-') condition + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + } + } + }); + }; + return completeChannelImpedanceTest(1); +}; + +/** +* @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannel = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. + }) + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/** +* @description Run impedance test on a single channel, applying the test signal only to P input. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannelInputP = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/** +* @description Run impedance test on a single channel, applying the test signal to N input. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannelInputN = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/* istanbul ignore next */ +/** +* @description To apply the impedance test signal to an input for any given channel +* @param channelNumber - Number - The channel you want to test. +* @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. +* @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. +* @returns {Promise} - With Number value of channel number +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected'); + + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tSending command to apply test signal to P input.'); + } else if (!pInput && nInput) { + console.log('\tSending command to apply test signal to N input.'); + } else if (pInput && nInput) { + console.log('\tSending command to apply test signal to P and N inputs.'); + } else { + console.log('\tSending command to stop applying test signal to both P and N inputs.'); + } + } + + if (!pInput && !nInput) { + this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort + } else { + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong + } + if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); + // Get impedance settings to send the board + k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { + return this.write(commandsArray); + }).then(() => { + /** + * If either pInput or nInput are true then we should start calculating impedance. Setting + * this.impedanceTest.active to true here allows us to route every sample for an impedance + * calculation instead of the normal sample output. + */ + if (pInput || nInput) this.impedanceTest.active = true; + resolve(channelNumber); + }, (err) => { + reject(err); + }); + }); +}; + +/** +* @description Calculates the impedance for a specified channel for a set time +* @param channelNumber - A Number, the channel number you want to test. +* @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. +* @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. +* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tCalculating impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tCalculating impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tCalculating impedance for P and N input.'); + } else { + console.log('\tNot calculating impedance for either P and N input.'); + } + } + return new Promise((resolve, reject) => { + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); + if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); + if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + this.impedanceTest.onChannel = channelNumber; + this.impedanceTest.sampleNumber = 0; // Reset the sample number + this.impedanceTest.isTestingPInput = pInput; + this.impedanceTest.isTestingNInput = nInput; + // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) + // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) + setTimeout(() => { // Calculate for 250ms + this.impedanceTest.onChannel = 0; + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tDone calculating impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tDone calculating impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tDone calculating impedance for P and N input.'); + } else { + console.log('\tNot calculating impedance for either P and N input.'); + } + } + if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; + if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; + resolve(channelNumber); + }, 400); + }); +}; + +/** +* @description Calculates average and gets textual value of impedance for a specified channel +* @param channelNumber - A Number, the channel number you want to finalize. +* @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. +* @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. +* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tFinalizing impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tFinalizing impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tFinalizing impedance for P and N input.'); + } else { + console.log('\tNot Finalizing impedance for either P and N input.'); + } + } + return new Promise((resolve, reject) => { + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); + if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); + if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + + if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); + if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); + + setTimeout(() => { + resolve(channelNumber); + }, 50); // Introduce a delay to allow for extra time in case of back to back tests + }); +}; + +/** +* @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request +* response from the board. +* @param recordingDuration {String} - The duration you want to log SD information for. Limited to: +* '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' +* @returns {Promise} - Resolves when the command has been written. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sdStart = function (recordingDuration) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + k.sdSettingForString(recordingDuration) + .then(command => { + // If we are not streaming, then expect a confirmation message back from the board + if (!this.isStreaming()) { + this.curParsingMode = k.OBCIParsingEOT; + } + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + this.write(command).then(resolve, reject); + }) + .catch(err => reject(err)); + }); +}; + +/** +* @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted +* with request response from the board. +* @returns {Promise} - Resolves when written +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sdStop = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + // If we are not streaming, then expect a confirmation message back from the board + if (!this.isStreaming()) { + this.curParsingMode = k.OBCIParsingEOT; + } + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + this.write(k.OBCISDLogStop).then(resolve, reject); + }); +}; + +/** +* @description Get the the current sample rate is. +* @returns {Number} The sample rate +* Note: This is dependent on if you configured the board correctly on setup options +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sampleRate = function () { + if (this.options.simulate) { + return this.options.simulatorSampleRate; + } else { + if (this.info) { + return this.info.sampleRate; + } else { + switch (this.boardType) { + case k.OBCIBoardDaisy: + return k.OBCISampleRate125; + case k.OBCIBoardDefault: + default: + return k.OBCISampleRate250; + } + } + } +}; + +/** +* @description This function is used as a convenience method to determine how many +* channels the current board is using. +* @returns {Number} A number +* Note: This is dependent on if you configured the board correctly on setup options +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.numberOfChannels = function () { + if (this.info) { + return this.info.numberOfChannels; + } else { + switch (this.boardType) { + case k.OBCIBoardDaisy: + return k.OBCINumberOfChannelsDaisy; + case k.OBCIBoardDefault: + default: + return k.OBCINumberOfChannelsDefault; + } + } +}; + +/** +* @description Send the command to tell the board to start the syncing protocol. Must be connected, +* streaming and using at least version 2.0.0 firmware. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.syncClocks = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); + if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj.timeSyncSent = this.time(); + this.curParsingMode = k.OBCIParsingTimeSyncSent; + this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); + }); +}; + +/** +* @description Send the command to tell the board to start the syncing protocol. Must be connected, +* streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs +* don't overlap. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.1.0 +* @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.syncClocksFull = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); + if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + var timeout = setTimeout(() => { + return reject('syncClocksFull timeout after 500ms with no sync'); + }, 500); // Should not take more than 1s to sync up + this.sync.eventEmitter = syncObj => { + clearTimeout(timeout); + return resolve(syncObj); + }; + this.once('synced', this.sync.eventEmitter); + this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj.timeSyncSent = this.time(); + this.curParsingMode = k.OBCIParsingTimeSyncSent; + this._writeAndDrain(k.OBCISyncTimeSet) + .catch(err => { + clearTimeout(timeout); + return reject(err); + }); + }); +}; + +/** +* @description Consider the '_processBytes' method to be the work horse of this +* entire framework. This method gets called any time there is new +* data coming in on the serial port. If you are familiar with the +* 'serialport' package, then every time data is emitted, this function +* gets sent the input data. The data comes in very fragmented, sometimes +* we get half of a packet, and sometimes we get 3 and 3/4 packets, so +* we will need to store what we don't read for next time. +* @param data - a buffer of unknown size +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processBytes = function (data) { + if (this.options.debug) openBCIUtils.debugBytes(this.curParsingMode + '<<', data); + + // Concat old buffer + var oldDataBuffer = null; + if (this.buffer) { + oldDataBuffer = this.buffer; + data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); + } + + switch (this.curParsingMode) { + case k.OBCIParsingEOT: + if (openBCISample.doesBufferHaveEOT(data)) { + this.curParsingMode = k.OBCIParsingNormal; + this.emit('eot', data); + this.buffer = openBCISample.stripToEOTBuffer(data); + } else { + this.buffer = data; + } + break; + case k.OBCIParsingReset: + // Does the buffer have an EOT in it? + if (openBCISample.doesBufferHaveEOT(data)) { + this._processParseBufferForReset(data); + this.curParsingMode = k.OBCIParsingNormal; + this.emit('ready'); + this.buffer = openBCISample.stripToEOTBuffer(data); + } else { + this.buffer = data; + } + break; + case k.OBCIParsingTimeSyncSent: + // If there is only one match + if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { + if (this.options.verbose) console.log(`Found Time Sync Sent`); + this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); + this.curParsingMode = k.OBCIParsingNormal; + } + this.buffer = this._processDataBuffer(data); + break; + case k.OBCIParsingNormal: + default: + this.buffer = this._processDataBuffer(data); + break; + } + + if (this.buffer && oldDataBuffer) { + if (bufferEqual(this.buffer, oldDataBuffer)) { + this.buffer = null; + } + } +}; + +/** +* @description Used to extract samples out of a buffer of unknown length +* @param dataBuffer {Buffer} - A buffer to parse for samples +* @returns {Buffer} - Any data that was not pulled out of the buffer +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processDataBuffer = function (dataBuffer) { + if (!dataBuffer) return null; + var bytesToParse = dataBuffer.length; + // Exit if we have a buffer with less data than a packet + if (bytesToParse < k.OBCIPacketSize) return dataBuffer; + + var parsePosition = 0; + // Begin parseing + while (parsePosition <= bytesToParse - k.OBCIPacketSize) { + // Is the current byte a head byte that looks like 0xA0 + if (dataBuffer[parsePosition] === k.OBCIByteStart) { + // Now that we know the first is a head byte, let's see if the last one is a + // tail byte 0xCx where x is the set of numbers from 0-F (hex) + if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { + /** We just qualified a raw packet */ + // This could be a time set packet! + this.timeOfPacketArrival = this.time(); + // Grab the raw packet, make a copy of it. + var rawPacket; + if (k.getVersionNumber(process.version) >= 6) { + // From introduced in node version 6.x.x + rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); + } else { + rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); + } + + // Emit that buffer + this.emit('rawDataPacket', rawPacket); + // Submit the packet for processing + this._processQualifiedPacket(rawPacket); + // Overwrite the dataBuffer with a new buffer + var tempBuf; + if (parsePosition > 0) { + tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); + } else { + tempBuf = dataBuffer.slice(k.OBCIPacketSize); + } + if (tempBuf.length === 0) { + dataBuffer = null; + } else { + if (k.getVersionNumber(process.version) >= 6) { + dataBuffer = Buffer.from(tempBuf); + } else { + dataBuffer = new Buffer(tempBuf); + } + } + // Move the parse position up one packet + parsePosition = -1; + bytesToParse -= k.OBCIPacketSize; + } + } + parsePosition++; + } + + return dataBuffer; +}; + +/** + * @description Alters the global info object by parseing an incoming soft reset key + * @param dataBuffer {Buffer} - The soft reset data buffer + * @returns {Buffer} - If there is data left in the buffer, just it will be returned. + * @private + * @author AJ Keller (@pushtheworldllc) + */ +Cyton.prototype._processParseBufferForReset = function (dataBuffer) { + if (openBCISample.countADSPresent(dataBuffer) === 2) { + this.info.boardType = k.OBCIBoardDaisy; + this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; + this.info.sampleRate = k.OBCISampleRate125; + } else { + this.info.boardType = k.OBCIBoardDefault; + this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; + this.info.sampleRate = k.OBCISampleRate250; + } + + if (openBCISample.findV2Firmware(dataBuffer)) { + this.info.firmware = k.OBCIFirmwareV2; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + } else { + this.info.firmware = k.OBCIFirmwareV1; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; + } +}; + +/** +* @description Used to route qualified packets to their proper parsers +* @param rawDataPacketBuffer +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { + if (!rawDataPacketBuffer) return; + if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; + var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); + if (missedPacketArray) { + this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); + } + this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; + var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); + switch (packetType) { + case k.OBCIStreamPacketStandardAccel: + this._processPacketStandardAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketStandardRawAux: + this._processPacketStandardRawAux(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketUserDefinedType: + // Do nothing for User Defined Packets + break; + case k.OBCIStreamPacketAccelTimeSyncSet: + // Don't waste any time! + this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); + this._processPacketTimeSyncedAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketAccelTimeSynced: + this._processPacketTimeSyncedAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketRawAuxTimeSyncSet: + this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); + this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketRawAuxTimeSynced: + this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); + break; + default: + // Don't do anything if the packet is not defined + break; + } +}; + +/** +* @description A method used to compute impedances. +* @param sampleObject - A sample object that follows the normal standards. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processImpedanceTest = function (sampleObject) { + var impedanceArray; + if (this.impedanceTest.continuousMode) { + // console.log('running in continuous mode...') + // openBCISample.debugPrettyPrint(sampleObject) + impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); + if (impedanceArray) { + this.emit('impedanceArray', impedanceArray); + } + } else if (this.impedanceTest.onChannel !== 0) { + // Only calculate impedance for one channel + impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); + if (impedanceArray) { + this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; + } + } +}; + +/** +* @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketStandardAccel = function (rawPacket) { + try { + let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketStandardRawAux = function (rawPacket) { + try { + let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp +* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket +* @param timeOfPacketArrival {Number} - The time the packet arrived. +* @private +* @returns {Object} - A time sync object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { + /** + * Helper function for creating a bad sync object + * @param err {object} - Can be any object + * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} + */ + var getBadObject = (err) => { + var badObject = openBCISample.newSyncObject(); + badObject.timeOffsetMaster = this.sync.timeOffsetMaster; + // Create an error + badObject.error = err; + return badObject; + }; + + var syncObj = {}; + + if (this.sync.curSyncObj === null) { + if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); + // Set the output to bad object + syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); + // Missed comma + } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { + if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); + // Set the output to bad object + syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); + } else { + try { + this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; + if (this.options.verbose) console.log('Got time set packet from the board'); + let boardTime = openBCISample.getFromTimePacketTime(rawPacket); + this.sync.curSyncObj.boardTime = boardTime; + // if (this.options.verbose) { + // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) + // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) + // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) + // } + + // Calculate the time between sending the `<` to getting the set packet, call this the round trip length + this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; + if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); + + // If the sync sent conf and set packet arrive in different serial flushes + // ------------------------------------------ + // | | timeTransmission | < GOOD :) + // ------------------------------------------ + // ^ ^ ^ + // s s s + // e e e + // n n t packet + // t t confirmation + // + // Assume it's good... + this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); + + // If the conf and the set packet arrive in the same serial flush we have big problem! + // ------------------------------------------ + // | | | < BAD :( + // ------------------------------------------ + // ^ ^ ^ + // s s s + // e e e + // n n t packet + // t t confirmation + if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { + // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point + this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); + if (this.options.verbose) console.log(`Had to correct transmission time`); + this.sync.curSyncObj.correctedTransmissionTime = true; + } + + // Calculate the offset #finally + this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; + if (this.options.verbose) { + console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); + console.log(`Board time: ${boardTime}`); + } + + // Add to array + if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { + // Shift the oldest one out of the array + this.sync.timeOffsetArray.shift(); + // Push the new value into the array + this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); + } else { + // Push the new value into the array + this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); + } + + // Calculate the master time offset that we use averaging to compute + if (this.sync.timeOffsetArray.length > 1) { + var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); + this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); + } else { + this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; + } + + this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; + + if (this.options.verbose) { + console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); + } + + // Set the valid object to true + this.sync.curSyncObj.valid = true; + + // Make a byte by byte copy of event + syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); + + // Save obj to the global array + this.sync.objArray.push(syncObj); + } catch (err) { + // Log if verbose. + if (this.options.verbose) console.log(err.message); + syncObj = getBadObject(err); + } + } + // Fix the curParsingMode back to normal + this.curParsingMode = k.OBCIParsingNormal; + // Emit synced object + this.emit(k.OBCIEmitterSynced, syncObj); + // Set global to null + this.sync.curSyncObj = null; + // Return the object + return syncObj; +}; + +/** +* @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets +* an accelerometer value. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { + // if (this.sync.active === false) console.log('Need to sync with board...') + try { + let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that +* shall be emitted as a raw buffer and not scaled. +* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { + // if (this.sync.active === false) console.log('Need to sync with board...') + try { + let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are +* being tested. +* @param sampleObject {Object} - A sample object that follows the normal standards. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._finalizeNewSample = function (sampleObject) { + sampleObject._count = this.sampleCount++; + if (this.impedanceTest.active) { + this._processImpedanceTest(sampleObject); + } else { + // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper + // channels (9-16) come in packets with even sample numbers + if (this.info.boardType === k.OBCIBoardDaisy) { + // Send the sample for downstream sample compaction + this._finalizeNewSampleForDaisy(sampleObject); + } else { + this.emit(k.OBCIEmitterSample, sampleObject); + } + } +}; + +/** +* @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber +* sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a +* sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be +* emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the +* missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. +* @param sampleObject {Object} - The sample object to finalize +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { + if (openBCISample.isOdd(sampleObject.sampleNumber)) { + // Check for the skipped packet condition + if (this._lowerChannelsSampleObject) { + // The last packet was odd... missed the even packet + this.info.missedPackets++; + } + this._lowerChannelsSampleObject = sampleObject; + } else { + // Make sure there is an odd packet waiting to get merged with this packet + if (this._lowerChannelsSampleObject) { + // Merge these two samples + var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); + // Set the _lowerChannelsSampleObject object to null + this._lowerChannelsSampleObject = null; + // Emite the new merged sample + this.emit('sample', mergedSample); + } else { + // Missed the odd packet, i.e. two evens in a row + this.info.missedPackets++; + } + } +}; + +/** +* @description Reset the master buffer and reset the number of bad packets. +* @author AJ Keller (@pushtheworldllc) +*/ +// TODO: nothing is using these constructs, but they look like good constructs. See contents of masterBufferMaker() +Cyton.prototype._reset_ABANDONED = function () { + this.masterBuffer = masterBufferMaker(); + this.badPackets = 0; +}; + +/** +* @description Stateful method for querying the current offset only when the last +* one is too old. (defaults to daily) +* @returns {Promise} A promise with the time offset +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sntpGetOffset = function () { + this.options.sntpTimeSync = true; + + if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); + + return new Promise((resolve, reject) => { + Sntp.offset({ + host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org + port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) + clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes + timeout: 500 // Assume packet has been lost after 500 milliseconds + }, function (err, offset) { + if (err) reject(err); + else resolve(offset); + }); + }); +}; + +/** +* @description Allows users to utilize all features of sntp if they want to... +*/ +Cyton.prototype.sntp = Sntp; + +/** +* @description This gets the time plus offset +* @private +*/ +Cyton.prototype._sntpNow = Sntp.now; + +/** +* @description This starts the SNTP server and gets it to remain in sync with the SNTP server +* @returns {Promise} - A promise if the module was able to sync with ntp server. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sntpStart = function (options) { + this.options.sntpTimeSync = true; + + // Sntp.start doesn't actually report errors (2016-10-29) + // so functionality is first detected via sntpGetOffset + return this.sntpGetOffset().then(() => { + return new Promise((resolve, reject) => { + Sntp.start({ + host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org + port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) + clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes + timeout: 500 // Assume packet has been lost after 500 milliseconds + }, () => { + this.sync.sntpActive = true; + this.emit('sntpTimeLock'); + resolve(); + }); + }); + }); +}; + +/** +* @description Stops the sntp from updating. +*/ +Cyton.prototype.sntpStop = function () { + Sntp.stop(); + this.options.sntpTimeSync = false; + this.sync.sntpActive = false; +}; + +/** +* @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time +* @returns {Number} - The time +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.time = function () { + if (this.options.sntpTimeSync) { + return this._sntpNow(); + } else { + return Date.now(); + } +}; + +/** +* @description This prints the total number of packets that were not able to be read +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printPacketsBad = function () { + if (this.badPackets > 1) { + console.log('Dropped a total of ' + this.badPackets + ' packets.'); + } else if (this.badPackets === 1) { + console.log('Dropped a total of 1 packet.'); + } else { + console.log('No packets dropped.'); + } +}; + +/** +* @description This prints the total bytes in +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printBytesIn = function () { + if (this.bytesIn > 1) { + console.log('Read in ' + this.bytesIn + ' bytes.'); + } else if (this.bytesIn === 1) { + console.log('Read one 1 packet in.'); + } else { + console.log('Read no packets.'); + } +}; + +/** +* @description This prints the total number of packets that have been read +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printPacketsRead = function () { + if (this.masterBuffer.packetsRead > 1) { + console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); + } else if (this.masterBuffer.packetsIn === 1) { + console.log('Read 1 packet.'); + } else { + console.log('No packets read.'); + } +}; + +/** +* @description Nice convenience method to print some session details +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.debugSession = function () { + this.printBytesIn(); + this.printPacketsRead(); + this.printPacketsBad(); +}; + +/** +* @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) +* @param channelSettingsObj +*/ +/* istanbul ignore next */ +Cyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { + console.log('-- Channel Settings Object --'); + var powerState = 'OFF'; + if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { + powerState = 'ON'; + } + console.log('---- POWER STATE: ' + powerState); + console.log('-- END --'); +}; + +/** +* @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. +* @param channelSettingsObject +* @returns {boolean} +*/ +Cyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { + return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; +}; + +util.inherits(Cyton, EventEmitter); + +module.exports = Cyton; + +/** +* @description To parse a given channel given output from a print registers query +* @param rawChannelBuffer +* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +// function getChannelSettingsObj (rawChannelBuffer) { +// return new Promise(function (resolve, reject) { +// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { +// reject('Undefined or null channel buffer'); +// } +// +// var channelSettingsObject = { +// CHANNEL: '0', +// POWER_DOWN: '0', +// GAIN_SET: '0', +// INPUT_TYPE_SET: '0', +// BIAS_SET: '0', +// SRB2_SET: '0', +// SRB1_SET: '0' +// }; +// +// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits +// var sizeOfData = rawChannelBuffer.byteLength; +// +// var objIndex = 0; +// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data +// switch (objIndex) { +// case 0: +// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); +// break; +// default: +// break; +// } +// +// objIndex++; +// } +// resolve(channelSettingsObject); +// }); +// } + +function masterBufferMaker () { + var masterBuf = new Buffer(k.OBCIMasterBufferSize); + masterBuf.fill(0); + return { // Buffer used to store bytes in and read packets from + buffer: masterBuf, + positionRead: 0, + positionWrite: 0, + packetsIn: 0, + packetsRead: 0, + looseBytes: 0 + }; +} diff --git a/openBCISample.js b/openBCISample.js index b84c2a9..0877c14 100644 --- a/openBCISample.js +++ b/openBCISample.js @@ -1,7 +1,7 @@ 'use strict'; -var gaussian = require('gaussian'); -var k = require('./openBCIConstants'); -var StreamSearch = require('streamsearch'); +const gaussian = require('gaussian'); +const k = require('./openBCIConstants'); +const StreamSearch = require('streamsearch'); /** Constants for interpreting the EEG data */ // Reference voltage for ADC in ADS1299. @@ -274,7 +274,7 @@ var sampleModule = { return (prefix << 16) | (twoByteBuffer[0] << 8) | twoByteBuffer[1]; }, - interpret24bitAsInt32: threeByteBuffer => { + interpret24bitAsInt32: (threeByteBuffer) => { var prefix = 0; if (threeByteBuffer[0] > 127) { diff --git a/openBCISimulator.js b/openBCISimulator.js index bf9a244..1795ea1 100644 --- a/openBCISimulator.js +++ b/openBCISimulator.js @@ -120,7 +120,7 @@ function OpenBCISimulatorFactory () { if (size > this.outputBuffered) size = this.outputBuffered; - // buffer is copied because presently openBCIBoard.js reuses it + // buffer is copied because presently openBCICyton.js reuses it var outBuffer = new Buffer(this.outputBuffer.slice(0, size)); this.outputBuffer.copy(this.outputBuffer, 0, size, this.outputBuffered); diff --git a/openBCIUtils.js b/openBCIUtils.js new file mode 100644 index 0000000..a9024d5 --- /dev/null +++ b/openBCIUtils.js @@ -0,0 +1,35 @@ +module.exports = { + debugBytes +}; + +/** + * @description Output passed bytes on the console as a hexdump, if enabled + * @param prefix - label to show to the left of bytes + * @param data - bytes to output, a buffer or string + * @private + */ +function debugBytes (prefix, data) { + if (typeof data === 'string') data = new Buffer(data); + + console.log('Debug bytes:'); + + for (var j = 0; j < data.length;) { + var hexPart = ''; + var ascPart = ''; + for (var end = Math.min(data.length, j + 16); j < end; ++j) { + var byt = data[j]; + + var hex = ('0' + byt.toString(16)).slice(-2); + hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in + hexPart += hex; + + var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; + ascPart += asc; + } + + // pad to fixed width for alignment + hexPart = (hexPart + ' ').substring(0, 3 * 17); + + console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); + } +} diff --git a/package.json b/package.json index d0fad46..43455aa 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "openbci", - "version": "1.4.1", + "version": "2.0.0", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", - "main": "openBCIBoard", + "main": "index.js", "scripts": { "start": "node index", "test": "semistandard | snazzy && mocha test", @@ -16,10 +16,12 @@ "license": "MIT", "dependencies": { "buffer-equal": "^1.0.0", + "clone": "^2.0.0", "gaussian": "^1.0.0", + "lodash": "^4.16.6", "mathjs": "^3.3.0", "performance-now": "^0.2.0", - "serialport": "4.0.1", + "serialport": "4.0.6", "sntp": "^2.0.0", "streamsearch": "^0.1.2" }, @@ -53,6 +55,7 @@ "semistandard": { "globals": [ "describe", + "xdescribe", "context", "before", "beforeEach", diff --git a/test/OpenBCIConstants-test.js b/test/OpenBCIConstants-test.js index cae1ffa..0f01603 100644 --- a/test/OpenBCIConstants-test.js +++ b/test/OpenBCIConstants-test.js @@ -764,6 +764,9 @@ describe('OpenBCIConstants', function () { it('should be 125', function () { assert.equal(125, k.OBCISampleRate125); }); + it('should be 250', function () { + assert.equal(200, k.OBCISampleRate200); + }); it('should be 250', function () { assert.equal(250, k.OBCISampleRate250); }); @@ -1446,6 +1449,9 @@ describe('OpenBCIConstants', function () { it('Event Emitter Impedance Array', function () { assert.equal('impedanceArray', k.OBCIEmitterImpedanceArray); }); + it('Event Emitter Message', function () { + assert.equal('message', k.OBCIEmitterMessage); + }); it('Event Emitter Query', function () { assert.equal('query', k.OBCIEmitterQuery); }); @@ -1459,7 +1465,4 @@ describe('OpenBCIConstants', function () { assert.equal('sample', k.OBCIEmitterSample); }); }); - describe('Errors', function () { - - }); }); diff --git a/test/OpenBCISample-test.js b/test/OpenBCISample-test.js index 8ba9974..f856d46 100644 --- a/test/OpenBCISample-test.js +++ b/test/OpenBCISample-test.js @@ -2,28 +2,28 @@ * Created by ajk on 12/15/15. */ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var openBCISample = require('../openBCISample'); -var chai = require('chai'); -var expect = chai.expect; -var assert = chai.assert; -var should = chai.should(); // eslint-disable-line no-unused-vars - -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); +const bluebirdChecks = require('./bluebirdChecks'); +const openBCISample = require('../openBCISample'); +const chai = require('chai'); +const expect = chai.expect; +const assert = chai.assert; +const should = chai.should(); // eslint-disable-line no-unused-vars + +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); chai.use(chaiAsPromised); chai.use(sinonChai); -var bufferEqual = require('buffer-equal'); +const bufferEqual = require('buffer-equal'); -var k = openBCISample.k; +const k = require('../openBCIConstants'); const defaultChannelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); -var sampleBuf = openBCISample.samplePacket(); +const sampleBuf = openBCISample.samplePacket(); -var accelArray; +let accelArray; -var channelScaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); +const channelScaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); describe('openBCISample', function () { beforeEach(function () { @@ -600,28 +600,6 @@ describe('openBCISample', function () { expect(sample.timeStamp).to.equal(time + timeOffset); }); }); - describe('#interpret24bitAsInt32', function () { - it('converts a small positive number', function () { - var buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a large positive number', function () { - var buf1 = new Buffer([0x02, 0xC0, 0x01]); // 0x02C001 === 180225 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 180225); - }); - it('converts a small negative number', function () { - var buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-1, 1); - }); - it('converts a large negative number', function () { - var buf1 = new Buffer([0x81, 0xA1, 0x01]); // 0x81A101 === -8281855 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-8281855, 1); - }); - }); describe('#interpret16bitAsInt32', function () { it('converts a small positive number', function () { var buf1 = new Buffer([0x06, 0x90]); // 0x0690 === 1680 @@ -644,6 +622,28 @@ describe('openBCISample', function () { assert.equal(num, -32351); }); }); + describe('#interpret24bitAsInt32', function () { + it('converts a small positive number', function () { + var buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + var num = openBCISample.interpret24bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a large positive number', function () { + var buf1 = new Buffer([0x02, 0xC0, 0x01]); // 0x02C001 === 180225 + var num = openBCISample.interpret24bitAsInt32(buf1); + assert.equal(num, 180225); + }); + it('converts a small negative number', function () { + var buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 + var num = openBCISample.interpret24bitAsInt32(buf1); + num.should.be.approximately(-1, 1); + }); + it('converts a large negative number', function () { + var buf1 = new Buffer([0x81, 0xA1, 0x01]); // 0x81A101 === -8281855 + var num = openBCISample.interpret24bitAsInt32(buf1); + num.should.be.approximately(-8281855, 1); + }); + }); describe('#floatTo3ByteBuffer', function () { it('converts random floats to a 3-byte buffer', function () { var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); diff --git a/test/openBCIBoard-Impedance-test.js b/test/openBCICyton-Impedance-test.js similarity index 98% rename from test/openBCIBoard-Impedance-test.js rename to test/openBCICyton-Impedance-test.js index 54cfec8..5155122 100644 --- a/test/openBCIBoard-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -1,13 +1,13 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var chai = require('chai'); -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCIBoard = require('../openBCIBoard'); -var openBCISample = openBCIBoard.OpenBCISample; -var k = openBCISample.k; +const bluebirdChecks = require('./bluebirdChecks'); +const chai = require('chai'); +const should = chai.should(); // eslint-disable-line no-unused-vars +const Cyton = require('../openBCICyton'); +const openBCISample = require('../openBCISample'); +const k = openBCISample.k; -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); chai.use(chaiAsPromised); chai.use(sinonChai); @@ -16,7 +16,7 @@ describe('#impedanceTesting', function () { this.timeout(20000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); diff --git a/test/openBCIBoard-test.js b/test/openBCICyton-test.js similarity index 94% rename from test/openBCIBoard-test.js rename to test/openBCICyton-test.js index d4f820e..de40cd6 100644 --- a/test/openBCIBoard-test.js +++ b/test/openBCICyton-test.js @@ -1,17 +1,17 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var sinon = require('sinon'); -var chai = require('chai'); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCIBoard = require('../openBCIBoard'); -var openBCISample = openBCIBoard.OpenBCISample; -var k = openBCISample.k; -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); -var bufferEqual = require('buffer-equal'); -var fs = require('fs'); -var math = require('mathjs'); +const bluebirdChecks = require('./bluebirdChecks'); +const sinon = require('sinon'); +const chai = require('chai'); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const Cyton = require('../openBCICyton'); +const openBCISample = require('../openBCISample'); +const k = require('../openBCIConstants'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); +const bufferEqual = require('buffer-equal'); +const fs = require('fs'); +const math = require('mathjs'); chai.use(chaiAsPromised); chai.use(sinonChai); @@ -21,7 +21,7 @@ describe('openbci-sdk', function () { var ourBoard, masterPortName, realBoard, spy; before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.autoFindOpenBCIBoard() .then(portName => { ourBoard = null; @@ -59,14 +59,14 @@ describe('openbci-sdk', function () { return bluebirdChecks.noPendingPromises(); }); it('constructs with require', function () { - var OpenBCIBoard = require('../openBCIBoard').OpenBCIBoard; - ourBoard = new OpenBCIBoard({ + var OpenBCICyton = require('../index').Cyton; + ourBoard = new OpenBCICyton({ verbose: true }); expect(ourBoard.numberOfChannels()).to.equal(8); }); it('constructs with the correct default options', () => { - var board = new openBCIBoard.OpenBCIBoard(); + var board = new Cyton(); expect(board.options.boardType).to.equal(k.OBCIBoardDefault); expect(board.options.baudRate).to.equal(115200); expect(board.options.simulate).to.be.false; @@ -88,16 +88,16 @@ describe('openbci-sdk', function () { expect(board.isStreaming()).to.be.false; }); it('should be able to set ganglion mode', () => { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ boardType: 'ganglion' }); (board.options.boardType).should.equal('ganglion'); }); it('should be able to set set daisy mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ boardType: 'daisy' }); - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ boardtype: 'daisy' }); (ourBoard1.options.boardType).should.equal('daisy'); @@ -110,122 +110,122 @@ describe('openbci-sdk', function () { }); }); it('should be able to change baud rate', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ baudRate: 9600 }); (ourBoard1.options.baudRate).should.equal(9600); - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ baudrate: 9600 }); (ourBoard2.options.baudRate).should.equal(9600); }); it('should be able to enter simulate mode from the constructor', () => { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ simulate: true }); expect(board.options.simulate).to.be.true; }); it('should be able to set the simulator to board failure mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorBoardFailure: true }); expect(ourBoard1.options.simulatorBoardFailure).to.be.true; - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorboardfailure: true }); expect(ourBoard2.options.simulatorBoardFailure).to.be.true; }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorDaisyModuleAttached: true }); expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatordaisymoduleattached: true }); expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true; }); it('should be able to start the simulator with firmware version 2', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorFirmwareVersion: 'v2' }); (ourBoard1.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorfirmwareversion: 'v2' }); (ourBoard2.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); }); it('should be able to put the simulator in raw aux mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorHasAccelerometer: false }); expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorhasaccelerometer: false }); expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false; }); it('should be able to make the internal clock of the simulator run slow', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInternalClockDrift: -1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.lessThan(0); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinternalclockdrift: -1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.lessThan(0); }); it('should be able to make the internal clock of the simulator run fast', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInternalClockDrift: 1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.greaterThan(0); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinternalclockdrift: 1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.greaterThan(0); }); it('should be able to not inject alpha waves into the simulator', function () { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInjectAlpha: false }); expect(ourBoard1.options.simulatorInjectAlpha).to.be.false; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinjectalpha: false }); expect(ourBoard2.options.simulatorInjectAlpha).to.be.false; }); it('can turn 50Hz line noise on', function () { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInjectLineNoise: '50Hz' }); expect(ourBoard1.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinjectlinenoise: '50Hz' }); expect(ourBoard2.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); }); it('can turn no line noise on', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulatorInjectLineNoise: 'none' }); (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseNone); }); it('defaults to 60Hz line noise when bad input', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulatorInjectLineNoise: '20Hz' }); (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseHz60); }); it('can enter simulate mode with different sample rate', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulate: true, simulatorSampleRate: 69 }); @@ -234,18 +234,18 @@ describe('openbci-sdk', function () { (ourBoard.sampleRate()).should.equal(69); }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorSerialPortFailure: true }); expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorserialportfailure: true }); expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true; }); it('should be able to enter sync mode', function () { - var ourBoard = new openBCIBoard.OpenBCIBoard({ + var ourBoard = new Cyton({ sntpTimeSync: true }); expect(ourBoard.options.sntpTimeSync).to.be.true; @@ -262,30 +262,30 @@ describe('openbci-sdk', function () { }); it('should be able to change the ntp pool host', function () { var expectedPoolName = 'time.apple.com'; - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ sntpTimeSyncHost: expectedPoolName }); expect(ourBoard1.options.sntpTimeSyncHost).to.equal(expectedPoolName); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ sntptimesynchost: expectedPoolName }); expect(ourBoard2.options.sntpTimeSyncHost).to.equal(expectedPoolName); }); it('should be able to change the ntp pool port', function () { var expectedPortNumber = 73; - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ sntpTimeSyncPort: expectedPortNumber }); expect(ourBoard1.options.sntpTimeSyncPort).to.equal(expectedPortNumber); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ sntptimesyncport: expectedPortNumber }); expect(ourBoard2.options.sntpTimeSyncPort).to.equal(expectedPortNumber); }); it('should report when sntp fails', function (done) { - var ourBoard = new openBCIBoard.OpenBCIBoard({ + var ourBoard = new Cyton({ sntpTimeSync: true, sntpTimeSyncHost: 'no\'where' }); @@ -298,18 +298,18 @@ describe('openbci-sdk', function () { }); }); it('can enter verbose mode', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); (ourBoard.options.verbose).should.equal(true); }); it('should start in current stream state in the init mode', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.curParsingMode.should.equal(k.OBCIParsingReset); }); it('configures impedance testing variables correctly', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); (ourBoard.impedanceTest.active).should.equal(false); (ourBoard.impedanceTest.isTestingNInput).should.equal(false); (ourBoard.impedanceTest.isTestingPInput).should.equal(false); @@ -317,7 +317,7 @@ describe('openbci-sdk', function () { (ourBoard.impedanceTest.sampleNumber).should.equal(0); }); it('configures sync object correctly', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); expect(ourBoard.sync.curSyncObj).to.be.null; expect(ourBoard.sync.eventEmitter).to.be.null; expect(ourBoard.sync.objArray.length).to.equal(0); @@ -327,24 +327,24 @@ describe('openbci-sdk', function () { expect(ourBoard.sync.timeOffsetArray.length).to.equal(0); }); it('configures impedance array with the correct amount of channels for default', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); (ourBoard.impedanceArray.length).should.equal(8); }); it('configures impedance array with the correct amount of channels for daisy', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ boardType: 'daisy' }); (ourBoard.impedanceArray.length).should.equal(16); }); it('configures impedance array with the correct amount of channels for ganglion', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ boardType: 'ganglion' }); (ourBoard.impedanceArray.length).should.equal(4); }); it('should throw if passed an invalid option', function (done) { try { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ foo: 'bar' }); done('did not throw'); @@ -354,13 +354,13 @@ describe('openbci-sdk', function () { describe('#simulator', function () { after(() => bluebirdChecks.noPendingPromises()); it('can enable simulator after constructor', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); ourBoard.simulatorEnable().should.be.fulfilled.and.notify(done); }); it('should start sim and call disconnected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); var disconnectStub = sinon.stub(ourBoard, 'disconnect').returns(Promise.resolve()); @@ -375,27 +375,27 @@ describe('openbci-sdk', function () { }, done); }); it('should not enable the simulator if already simulating', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.simulatorEnable().should.be.rejected.and.notify(done); }); it('can disable simulator', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.simulatorDisable().should.be.fulfilled.and.notify(done); }); it('should not disable simulator if not in simulate mode', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); ourBoard.simulatorDisable().should.be.rejected.and.notify(done); }); it('should disable sim and call disconnected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -412,7 +412,7 @@ describe('openbci-sdk', function () { }); }); it('should be able to propagate constructor options to simulator', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -454,7 +454,7 @@ describe('openbci-sdk', function () { }); describe('#debug', function () { before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ debug: true }); ourBoard.connect(k.OBCISimulatorPortName).catch(done); @@ -488,7 +488,7 @@ describe('openbci-sdk', function () { describe('#boardTests', function () { this.timeout(3000); before(function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulate: !realBoard, verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -664,7 +664,7 @@ describe('openbci-sdk', function () { ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, ourBoard.disconnect() - ]).then(() => {+ + ]).then(() => { writeSpy.should.have.not.been.called; writeSpy.restore(); }); @@ -924,7 +924,7 @@ describe('openbci-sdk', function () { describe('#_processDataBuffer', function () { var _processQualifiedPacketSpy; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); _processQualifiedPacketSpy = sinon.spy(ourBoard, '_processQualifiedPacket'); @@ -1088,7 +1088,7 @@ describe('openbci-sdk', function () { var funcSpyTimeSyncSet, funcSpyTimeSyncedAccel, funcSpyTimeSyncedRawAux, funcSpyStandardRawAux, funcSpyStandardAccel; before(function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); // Put watchers on all functions @@ -1242,7 +1242,7 @@ describe('openbci-sdk', function () { var timeSyncSetPacket; var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: false }); }); @@ -1427,7 +1427,7 @@ describe('openbci-sdk', function () { describe('#_processPacket Errors', function () { var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: false }); }); @@ -1471,7 +1471,7 @@ describe('openbci-sdk', function () { describe('#time', function () { after(() => bluebirdChecks.noPendingPromises()); it('should use sntp time when sntpTimeSync specified in options', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true, sntpTimeSync: true }); @@ -1485,7 +1485,7 @@ describe('openbci-sdk', function () { }); }); it('should use Date.now() for time when sntpTimeSync is not specified in options', function () { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true }); var funcSpySntpNow = sinon.spy(board, '_sntpNow'); @@ -1499,7 +1499,7 @@ describe('openbci-sdk', function () { funcSpySntpNow = null; }); it('should emit sntpTimeLock event after sycned with ntp server', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true, sntpTimeSync: true }); @@ -1513,7 +1513,7 @@ describe('openbci-sdk', function () { describe('#sntpStart', function () { after(() => bluebirdChecks.noPendingPromises()); it('should be able to start ntp server', () => { - var board = new openBCIBoard.OpenBCIBoard(); + var board = new Cyton(); expect(board.sntp.isLive()).to.be.false; return Promise.all([ board.sntpStart() @@ -1532,7 +1532,7 @@ describe('openbci-sdk', function () { this.timeout(5000); var board; before(done => { - board = new openBCIBoard.OpenBCIBoard({ + board = new Cyton({ sntpTimeSync: true }); board.once('sntpTimeLock', () => { @@ -1561,7 +1561,7 @@ describe('openbci-sdk', function () { describe('#sntpGetOffset', function () { after(() => bluebirdChecks.noPendingPromises()); it('should get the sntp offset', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ sntpTimeSync: true }); board.once('sntpTimeLock', () => { @@ -1576,7 +1576,7 @@ describe('openbci-sdk', function () { var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); }); @@ -1653,7 +1653,7 @@ $$$`); describe('#_processBytes', function () { before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); }); @@ -1971,7 +1971,7 @@ $$$`); describe('#_finalizeNewSampleForDaisy', function () { var ourBoard, randomSampleGenerator, sampleEvent, failTimeout; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); randomSampleGenerator = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, false, 'none'); @@ -2081,13 +2081,13 @@ $$$`); describe('#usingVersionTwoFirmware', function () { after(() => bluebirdChecks.noPendingPromises()); it('should return true if firmware is version 2', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.info.firmware = 'v2'; expect(ourBoard.usingVersionTwoFirmware()).to.be.true; }); it('should return false if not firmware version 2', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); expect(ourBoard.usingVersionTwoFirmware()).to.be.false; }); @@ -2106,7 +2106,7 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2115,7 +2115,7 @@ $$$`); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2135,7 +2135,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2147,7 +2147,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2161,7 +2161,7 @@ $$$`); }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2175,7 +2175,7 @@ $$$`); }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2189,7 +2189,7 @@ $$$`); }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2203,7 +2203,7 @@ $$$`); }); it('should not change the channel if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2219,7 +2219,7 @@ $$$`); it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2247,7 +2247,7 @@ $$$`); }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2255,7 +2255,7 @@ $$$`); ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2275,7 +2275,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2288,7 +2288,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2301,7 +2301,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2314,7 +2314,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2328,7 +2328,7 @@ $$$`); }); it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2357,14 +2357,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioChannelGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2383,7 +2383,7 @@ $$$`); }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2395,7 +2395,7 @@ $$$`); }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2411,7 +2411,7 @@ $$$`); }).catch(err => done(err)); }); it('should get message even if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2438,7 +2438,7 @@ $$$`); }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2447,7 +2447,7 @@ $$$`); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2468,7 +2468,7 @@ $$$`); }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2481,7 +2481,7 @@ $$$`); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2495,7 +2495,7 @@ $$$`); }); it('should reject if no poll time is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2509,7 +2509,7 @@ $$$`); }); it('should reject if the requested new poll time is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2523,7 +2523,7 @@ $$$`); }); it('should reject if the requested new poll time is higher than 255', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2537,7 +2537,7 @@ $$$`); }); it('should not change the poll time if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2553,7 +2553,7 @@ $$$`); it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { var newPollTime = 69; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2584,14 +2584,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2610,7 +2610,7 @@ $$$`); }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2622,7 +2622,7 @@ $$$`); }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2638,7 +2638,7 @@ $$$`); }).catch(err => done(err)); }); it('should get failure message if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2666,28 +2666,28 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not try to set baud rate if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); }); it('should reject if no input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); }); it('should be rejected if input type incorrect', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); }); it('should not try to change the baud rate if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2706,7 +2706,7 @@ $$$`); }).catch(err => done(err)); }); it('should not try to change the baud rate if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2718,7 +2718,7 @@ $$$`); }).catch(err => done(err)); }); it('should set the baud rate to default if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2734,7 +2734,7 @@ $$$`); }).catch(err => done(err)); }); it('should set the baud rate to fast if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2764,14 +2764,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not get system status if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); }); it('should not get system status if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2790,7 +2790,7 @@ $$$`); }).catch(err => done(err)); }); it('should not get system status if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2802,7 +2802,7 @@ $$$`); }).catch(err => done(err)); }); it('should get up system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2818,7 +2818,7 @@ $$$`); }).catch(err => done(err)); }); it('should get down system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2', @@ -2839,7 +2839,7 @@ $$$`); describe('#radioTests', function () { this.timeout(0); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -2999,7 +2999,7 @@ $$$`); runHardwareValidation = false; } if (runHardwareValidation) { - board = new openBCIBoard.OpenBCIBoard({ + board = new Cyton({ verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); @@ -3082,7 +3082,7 @@ describe('#daisy', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorDaisyModuleAttached: true, @@ -3163,7 +3163,7 @@ describe('#syncWhileStreaming', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -3276,7 +3276,7 @@ describe('#syncErrors', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2' }); diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js index c26c1b3..fdd60e2 100644 --- a/test/openBCISimulator-test.js +++ b/test/openBCISimulator-test.js @@ -1,13 +1,13 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var bufferEqual = require('buffer-equal'); -var chai = require('chai'); -var chaiAsPromised = require(`chai-as-promised`); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCISimulator = require('../openBCISimulator'); -var openBCISample = require('../openBCISample'); -var k = openBCISample.k; +const bluebirdChecks = require('./bluebirdChecks'); +const bufferEqual = require('buffer-equal'); +const chai = require('chai'); +const chaiAsPromised = require(`chai-as-promised`); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const openBCISimulator = require('../openBCISimulator'); +const openBCISample = require('../openBCISample'); +const k = require('../openBCIConstants'); chai.use(chaiAsPromised); From 7f1f631ee09ec194b26f230fccd02a5b4854a01b Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:36:08 -0500 Subject: [PATCH 02/42] Add: test for print register settings --- test/openBCICyton-test.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index de40cd6..7ca8274 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -889,7 +889,23 @@ describe('openbci-sdk', function () { ourBoard.channelSet(1, true, 24, 'normal', 'taco', true, true).should.be.rejected.and.notify(done); }); }); - + describe('#printRegisterSettings', function () { + before(function (done) { + ourBoard.printRegisterSettings() + .then(() => { + done(); + }).catch(done); + }); + after(function () { + ourBoard.curParsingMode = k.OBCIParsingNormal; + }); + it('should send the correct register setting', function () { + spy.should.have.been.calledWith(k.OBCIMiscQueryRegisterSettings); + }); + it('should have set the proper parseing mode', function () { + ourBoard.curParsingMode = k.OBCIParsingEOT; + }); + }); describe('#impedanceTest Not Connected Rejects ', function () { it('rejects all channeles when not streaming', function (done) { ourBoard.impedanceTestAllChannels().should.be.rejected.and.notify(done); From a5683f45a14a45681e2e8ea507a10d1f36edeea5 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:46:56 -0500 Subject: [PATCH 03/42] Add test for testSignal function --- README.md | 2 +- changelog.md | 6 +++++ openBCICyton.js | 2 +- test/openBCICyton-test.js | 53 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4fe771d..3156afd 100644 --- a/README.md +++ b/README.md @@ -999,7 +999,7 @@ A String indicating which test signal to apply * `pulse1xFast` - Connect to test signal 1x Amplitude, fast pulse * `pulse1xSlow` - Connect to test signal 1x Amplitude, slow pulse * `pulse2xFast` - Connect to test signal 2x Amplitude, fast pulse - * `pulse2xFast` - Connect to test signal 2x Amplitude, slow pulse + * `pulse2xSlow` - Connect to test signal 2x Amplitude, slow pulse * `none` - Reset to default **_Returns_** a promise, if the commands were sent to write buffer. diff --git a/changelog.md b/changelog.md index 1ca28fd..92d9034 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,12 @@ * Major change to how board is initialized with removal of `factory` paradigm. * Drop support for Node 4 and 5 due to lack of EMACS 6 +### Bug Fixes +* Documentation error with `testSignal` function. + +### Enhancements +* Add more tests for public API functions. + # 1.4.1 ### Bug Fixes diff --git a/openBCICyton.js b/openBCICyton.js index 33ac7bf..2f6fd1d 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1113,7 +1113,7 @@ Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType * - Connect to test signal 1x Amplitude, slow pulse * - `pulse2xFast` * - Connect to test signal 2x Amplitude, fast pulse -* - `pulse2xFast` +* - `pulse2xSlow` * - Connect to test signal 2x Amplitude, slow pulse * - `none` * - Reset to default diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 7ca8274..a3cab72 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -906,6 +906,59 @@ describe('openbci-sdk', function () { ourBoard.curParsingMode = k.OBCIParsingEOT; }); }); + describe('#testSignal', function () { + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('dc').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToDC); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('ground').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToGround); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse1xFast').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xFast); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse1xSlow').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xSlow); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse2xFast').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xFast); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse2xSlow').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xSlow); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should reject with invalid test signal', function (done) { + ourBoard.testSignal('taco').should.be.rejected.and.notify(done); + }); + }); describe('#impedanceTest Not Connected Rejects ', function () { it('rejects all channeles when not streaming', function (done) { ourBoard.impedanceTestAllChannels().should.be.rejected.and.notify(done); From be03e19535c0b7c06b5790f3ddc11173fba0edab Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:52:16 -0500 Subject: [PATCH 04/42] Add: Coverage over sampleRate and numberOfChannels when info property is null --- test/openBCICyton-test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index a3cab72..5fe63ce 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -86,6 +86,11 @@ describe('openbci-sdk', function () { expect(board.numberOfChannels()).to.equal(8); expect(board.isConnected()).to.be.false; expect(board.isStreaming()).to.be.false; + it('should still get proper values if no info object', function () { + board.info = null; + expect(board.sampleRate()).to.equal(250); + expect(board.numberOfChannels()).to.equal(8); + }); }); it('should be able to set ganglion mode', () => { var board = new Cyton({ @@ -102,6 +107,7 @@ describe('openbci-sdk', function () { }); (ourBoard1.options.boardType).should.equal('daisy'); (ourBoard2.options.boardType).should.equal('daisy'); + ourBoard1.info = null; it('should get value for daisy', () => { ourBoard1.sampleRate().should.equal(125); }); From 8993a6c23ba89a798873f1cc7a21fa329fb48357 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 2 Nov 2016 17:32:56 -0400 Subject: [PATCH 05/42] Enh: Drop factory Add: index.js Remove: Node 4 and 5 support --- .travis.yml | 9 +- README.md | 598 ++-- changelog.md | 21 +- index.js | 2 + openBCIBoard.js | 2407 ----------------- openBCIConstants.js | 12 + openBCICyton.js | 2351 ++++++++++++++++ openBCISample.js | 8 +- openBCISimulator.js | 2 +- openBCIUtils.js | 35 + package.json | 9 +- test/OpenBCIConstants-test.js | 9 +- test/OpenBCISample-test.js | 72 +- ...test.js => openBCICyton-Impedance-test.js} | 18 +- ...nBCIBoard-test.js => openBCICyton-test.js} | 270 +- test/openBCISimulator-test.js | 18 +- 16 files changed, 2928 insertions(+), 2913 deletions(-) create mode 100644 index.js delete mode 100644 openBCIBoard.js create mode 100644 openBCICyton.js create mode 100644 openBCIUtils.js rename test/{openBCIBoard-Impedance-test.js => openBCICyton-Impedance-test.js} (98%) rename test/{openBCIBoard-test.js => openBCICyton-test.js} (94%) diff --git a/.travis.yml b/.travis.yml index 8179862..ed57ce7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,5 @@ language: node_js node_js: - - "4.0" - - "4.1" - - "4.2" - - "4.3" - - "4.4" - - "4.5" - - "4.6" - "5.11.0" - "6.0" - "6.1" @@ -17,6 +10,8 @@ node_js: - "6.6" - "6.7" - "6.8" + - "7.0" + - "7.1" install: - npm install --all script: diff --git a/README.md b/README.md index 2fe9742..90e7d27 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A Node.js module for OpenBCI ~ written with love by [Push The World!](http://www.pushtheworldllc.com) -We are proud to support all functionality of the OpenBCI 8 and 16 Channel boards and are actively developing and maintaining this module. +We are proud to support all functionality of the Cyton (8 and 16 Channel boards). For the Ganglion (4 channel) please visit [OpenBCI_NodeJS_Ganglion](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion). Push The World is actively developing and maintaining this module. The purpose of this module is to **get connected** and **start streaming** as fast as possible. @@ -19,15 +19,16 @@ Python researcher or developer? Check out how easy it is to [get access to the e ### Table of Contents: --- -1. [Installation](#install) -2. [TL;DR](#tldr) -3. [About](#About) -4. [General Overview](#general-overview) -5. [SDK Reference Guide](#sdk-reference-guide) - 1. [Constructor](#constructor) - 2. [Methods](#method) - 3. [Events](#event) - 4. [Constants](#constants) +1. [TL;DR](#tldr) +2. [Installation](#install) +3. [Cyton (32bit Board)](#cyton) + 1. [About](#about) + 2. [General Overview](#general-overview) + 3. [SDK Reference Guide](#sdk-reference-guide) + * [Constructor](#constructor) + * [Methods](#method) + * [Events](#event) + * [Constants](#constants) 6. [Interfacing With Other Tools](#interfacing-with-other-tools) 7. [Developing](#developing) 8. [Testing](#developing-testing) @@ -35,49 +36,59 @@ Python researcher or developer? Check out how easy it is to [get access to the e 10. [License](#license) 11. [Roadmap](#roadmap) -### Installation: -``` -npm install openbci -``` ### TL;DR: Get connected and [start streaming right now with the example code](examples/getStreaming/getStreaming.js). -```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +#### Cyton (8 and 16 channel boards) +```ecmascript 6 +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` - .then(function() { - ourBoard.on('ready',function() { - ourBoard.streamStart(); - ourBoard.on('sample',function(sample) { - /** Work with sample */ - for (var i = 0; i < ourBoard.numberOfChannels(); i++) { - console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts."); - // prints to the console - // "Channel 1: 0.00001987 Volts." - // "Channel 2: 0.00002255 Volts." - // ... - // "Channel 8: -0.00001875 Volts." - } - }); + .then(() => { + ourBoard.on('ready',() => { + ourBoard.streamStart(); + ourBoard.on('sample',(sample) => { + /** Work with sample */ + for (let i = 0; i < ourBoard.numberOfChannels(); i++) { + console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts."); + // prints to the console + // "Channel 1: 0.00001987 Volts." + // "Channel 2: 0.00002255 Volts." + // ... + // "Channel 8: -0.00001875 Volts." + } }); -}) + }); +}); ``` + +### Installation: -### About: +#### macOs +``` +npm install openbci +``` + +# Cyton + +## About: Want to know if the module really works? Check out some projects and organizations using it: * [_OpenEXP_](https://github.com/openexp/OpenEXP): an open-source desktop app for running experiments and collecting behavioral and physiological data. -* [_Thinker_](http://www.pushtheworldllc.com/#!thinker/uc1fn): a project building the world's first brainwave-word database. +* [_Thinker_](http://www.pushtheworldllc.com/): a project building the world's first brainwave-word database. * [_NeuroJS_](https://github.com/NeuroJS): a community dedicated to Neuroscience research using JavaScript, they have several great examples. Still not satisfied it works?? Check out this [detailed report](http://s132342840.onlinehome.us/pushtheworld/files/voltageVerificationTestPlanAndResults.pdf) that scientifically validates the output voltages of this module. -How are you still doubting and not using this already? Fine, go look at some of the [700 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it! +How are you still doubting and not using this already? Fine, go look at some of the [800 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it! +<<<<<<< 888be5d60f7a92e95c747ad1ee9eba02f9aedc9e Python researcher or developer? Check out how easy it is to [get access to the entire API in the Python example](examples/python)! ### General Overview: +======= +## General Overview: +>>>>>>> Enh: Drop factory Add: index.js Remove: Node 4 and 5 support Initialization -------------- @@ -85,49 +96,56 @@ Initialization Initializing the board: ```js -var OpenBCIBoard = require('openbci'); -var ourBoard = new OpenBCIBoard.OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ``` For initializing with options, such as verbose print outs: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ - verbose: true +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + verbose: true }); ``` Or if you don't have a board and want to use synthetic data: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ - simulate: true +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + simulate: true }); ``` Another useful way to start the simulator: ```js -var openBCIBoard = require('openbci'); -var k = openBCIBoard.OpenBCIConstants; -var ourBoard = openBCIBoard.OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const k = require('openbci').Constants; +const ourBoard = new Cyton(); ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true - .then(function(boardSerial) { - ourBoard.on('ready',function() { + .then((boardSerial) => { + ourBoard.on('ready',() => { /** Start streaming, reading registers, what ever your heart desires */ }); - }).catch(function(err) { + }).catch((err) => { /** Handle connection errors */ }); ``` +or if you are using ES6: +```js +import { Cyton, Constants } from 'openbci'; +const ourBoard = new Cyton(); +ourBoard.connect(Constants.OBCISimulatorPortName); +``` + To debug, it's amazing, do: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ - simulate: true +var Cyton = require('openbci').Cyton; +var ourBoard = new Cyton({ + debug: true }); ``` ps: go [checkout out the example](examples/debug/debug.js) to do it right now! @@ -139,8 +157,8 @@ You MUST wait for the 'ready' event to be emitted before streaming/talking with so installing the 'sample' listener and writing before the ready event might result in... nothing at all. ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { /** Start streaming, reading registers, what ever your heart desires */ @@ -172,8 +190,8 @@ To get a ['sample'](#event-sample) event, you need to: 3. In callback for ['ready'](#event-ready) emitter, call [`streamStart()`](#method-stream-start) 4. Install the ['sample'](#event-sample) event emitter ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function() { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -187,7 +205,8 @@ ourBoard.connect(portName).then(function() { ``` Close the connection with [`.streamStop()`](#method-stream-stop) and disconnect with [`.disconnect()`](#method-disconnect) ```js -var ourBoard = new require('openbci').OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.streamStop().then(ourBoard.disconnect()); ``` @@ -201,60 +220,61 @@ Keep your resync interval above 50ms. While it's important to resync every coupl Using local computer time: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard, - ourBoard = new OpenBCIBoard({ - verbose:true - }); +const Cyton = require('openbci').Cyton; +const k = require('openbci').Constants; +const ourBoard = new Cyton({ + verbose:true +}); const resyncPeriodMin = 5; // re sync every five minutes const secondsInMinute = 60; -var sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! -var timeSyncPossible = false; +let sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! +let timeSyncPossible = false; // Call to connect ourBoard.connect(portName).then(() => { - ourBoard.on('ready',() => { - // Get the sample rate after 'ready' - sampleRate = ourBoard.sampleRate(); - // 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(); - - ourBoard.streamStart() - .then(() => { - /** Start streaming command sent to board. */ - }) - .catch(err => { - console.log(`stream start: ${err}`); - }) - }); - - // PTW recommends sample driven - ourBoard.on('sample',sample => { - // Resynchronize every every 5 minutes - if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) { - ourBoard.syncClocksFull() - .then(syncObj => { - // Sync was successful - if (syncObj.valid) { - // Log the object to check it out! - console.log(`syncObj`,syncObj); - - // Sync was not successful - } else { - // Retry it - console.log(`Was not able to sync, please retry?`); - } - }); - } - - if (sample.timeStamp) { // true after the first sync - console.log(`NTP Time Stamp ${sample.timeStamp}`); - } - - }); + ourBoard.on('ready',() => { + // Get the sample rate after 'ready' + sampleRate = ourBoard.sampleRate(); + // 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(); + + ourBoard.streamStart() + .then(() => { + /** Start streaming command sent to board. */ + }) + .catch(err => { + console.log(`stream start: ${err}`); + }); + }); + + // PTW recommends sample driven + ourBoard.on('sample',sample => { + // Resynchronize every every 5 minutes + if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) { + ourBoard.syncClocksFull() + .then(syncObj => { + // Sync was successful + if (syncObj.valid) { + // Log the object to check it out! + console.log(`syncObj`,syncObj); + + // Sync was not successful + } else { + // Retry it + console.log(`Was not able to sync, please retry?`); + } + }); + } + + if (sample.timeStamp) { // true after the first sync + console.log(`NTP Time Stamp ${sample.timeStamp}`); + } + + }); }) .catch(err => { - console.log(`connect: ${err}`); + console.log(`connect: ${err}`); }); ``` @@ -265,8 +285,8 @@ You must have the OpenBCI board connected to the PC before trying to automatical If a port is not automatically found, then call [`.listPorts()`](#method-list-ports) to get a list of all serial ports this would be a good place to present a drop down picker list to the user, so they may manually select the serial port name. ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.autoFindOpenBCIBoard().then(portName => { if(portName) { /** @@ -307,8 +327,8 @@ Where there are the same number of elements as channels and each element can be Without further ado, here is an example: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -323,7 +343,7 @@ ourBoard.connect(portName).then(function(boardSerial) { ``` But wait! What is this `impedanceArray`? An Array of Objects, for each object: -```js +``` [{ channel: 1, P: { @@ -364,8 +384,8 @@ To run an impedance test on all inputs, one channel at a time: For example: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.streamStart(); ourBoard.on('impedanceArray', impedanceArray => { @@ -377,13 +397,13 @@ ourBoard.connect(portName).then(function(boardSerial) { See Reference Guide for a complete list of impedance tests. -# SDK Reference Guide: +## SDK Reference Guide: --------------- -## Constructor: +### Constructor: -### OpenBCIBoard (options) +#### Cyton (options) -Create new instance of an OpenBCI board. +Create new instance of an Cyton board. **_options (optional)_** @@ -391,10 +411,8 @@ Board optional configurations. * `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if firmware on board has been previously configured. * `boardType` {String} - Specifies type of OpenBCI board (3 possible boards) - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel board with Daisy Module - 16 Channels - * `ganglion` - 4 Channel board - (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 08/2016) + * `default` - 8 Channel Cyton board (Default) + * `daisy` - 8 Channel Cyton board with Daisy Module - 16 Channels * `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) * `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on the board is not polling the RFduino on the dongle. (Default `false`) * `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. This is useful if you want to test how your application reacts to a user requesting 16 channels but there is no daisy module actually attached, or vice versa, where there is a daisy module attached and the user only wants to use 8 channels. (Default `false`) @@ -424,9 +442,9 @@ Board optional configurations. **Note, we have added support for either all lowercase OR camel case for the options, use whichever style you prefer.** -## Methods: +### Methods: -### .autoFindOpenBCIBoard() +#### .autoFindOpenBCIBoard() Automatically find an OpenBCI board. @@ -434,7 +452,7 @@ Automatically find an OpenBCI board. **_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux or `OpenBCISimulator` if `this.options.simulate === true`. -### .channelOff(channelNumber) +#### .channelOff(channelNumber) Turn off a specified channel @@ -444,7 +462,7 @@ A number (1-16) specifying which channel you want to turn off. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .channelOn(channelNumber) +#### .channelOn(channelNumber) Turn on a specified channel @@ -454,7 +472,7 @@ A number (1-16) specifying which channel you want to turn on. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) +#### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) Send a channel setting command to the board. @@ -494,7 +512,7 @@ ourBoard.channelSet(2,false,24,'normal',true,true,false); // sends ['x','2','0','6','0','1','1','0','X'] to the command queue ``` -### .connect(portName) +#### .connect(portName) The essential precursor method to be called initially to establish a serial connection to the OpenBCI board. @@ -504,29 +522,17 @@ The system path of the OpenBCI board serial port to open. For example, `/dev/tty **_Returns_** a promise, fulfilled by a successful serial connection to the board. -### .debugSession() +#### .debugSession() Calls all [`.printPacketsBad()`](#method-print-packets-bad), [`.printPacketsRead()`](#method-print-packets-read), [`.printBytesIn()`](#method-print-bytes-in) -### .disconnect() +#### .disconnect() Closes the serial port opened by [`.connect()`](#method-connect). Waits for stop streaming command to be sent if currently streaming. **_Returns_** a promise, fulfilled by a successful close of the serial port object, rejected otherwise. -### .getSettingsForChannel(channelNumber) - -Gets the specified channelSettings register data from printRegisterSettings call. - -**_channelNumber_** - -A number specifying which channel you want to get data on. Only 1-8 at this time. - -**Note, at this time this does not work for the daisy board** - -**_Returns_** a promise, fulfilled if the command was sent to the board and the `.processBytes()` function is ready to reach for the specified channel. - -### .impedanceTestAllChannels() +#### .impedanceTestAllChannels() To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a little while to actually run (<8 seconds)! @@ -536,7 +542,7 @@ Don't forget to install the ['impedanceArray'](#event-impedance-array) emitter t **_Returns_** a promise upon completion of test. -### .impedanceTestChannels(arrayOfCommands) +#### .impedanceTestChannels(arrayOfCommands) **_arrayOfCommands_** @@ -553,7 +559,7 @@ Don't forget to install the `impedanceArray` emitter to receive the impendances! **_Returns_** a promise upon completion of test. -### .impedanceTestChannel(channelNumber) +#### .impedanceTestChannel(channelNumber) Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. @@ -565,8 +571,8 @@ A Number, specifies which channel you want to test. **Example** ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); ourBoard.connect(portName).then(function(boardSerial) { ourBoard.on('ready',function() { ourBoard.streamStart(); @@ -581,21 +587,21 @@ ourBoard.connect(portName).then(function(boardSerial) { }); ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: 2394.45, - text: 'good' + "channel": 1, + "P": { + "raw": 2394.45, + "text": "good" }, - N: { - raw: 7694.45, - text: 'ok' + "N": { + "raw": 7694.45, + "text": "ok" } } ``` -### .impedanceTestChannelInputP(channelNumber) +#### .impedanceTestChannelInputP(channelNumber) Run impedance test on a single channel, applying the test signal only to P input. @@ -607,10 +613,10 @@ A Number, specifies which channel you want to test. **Example** ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); +ourBoard.connect(portName).then(() => { + ourBoard.on('ready', () => { ourBoard.streamStart(); ourBoard.impedanceTestChannelInputP(1) .then(impedanceObject => { @@ -623,21 +629,21 @@ ourBoard.connect(portName).then(function(boardSerial) { }); ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: 2394.45, - text: 'good' + "channel": 1, + "P": { + "raw": 2394.45, + "text": "good" }, - N: { - raw: -1, - text: 'init' + "N": { + "raw": -1, + "text": "init" } } ``` -### .impedanceTestChannelInputN(channelNumber) +#### .impedanceTestChannelInputN(channelNumber) Run impedance test on a single channel, applying the test signal only to N input. @@ -648,11 +654,11 @@ A Number, specifies which channel you want to test. **_Returns_** a promise that resolves a single channel impedance object. **Example** -```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { +```ecmascript 6 +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton(); +ourBoard.connect(portName).then(() => { + ourBoard.on('ready',() => { ourBoard.streamStart(); ourBoard.impedanceTestChannelInputN(1) .then(impedanceObject => { @@ -660,54 +666,53 @@ ourBoard.connect(portName).then(function(boardSerial) { }) .catch(err => console.log(err)); }); -}).catch(function(err) { - /** Handle connection errors */ -}); +}).catch(err => console.log(err)); + ``` Where an impedance for this method call would look like: -```js +```json { - channel: 1, - P: { - raw: -1, - text: 'init' + "channel": 1, + "P": { + "raw": -1, + "text": "init" }, - N: { - raw: 7694.45, - text: 'ok' + "N": { + "raw": 7694.45, + "text": "ok" } } ``` -### .impedanceTestContinuousStart() +#### .impedanceTestContinuousStart() Sends command to turn on impedances for all channels and continuously calculate their impedances. **_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer -### .impedanceTestContinuousStop() +#### .impedanceTestContinuousStop() Sends command to turn off impedances for all channels and stop continuously calculate their impedances. **_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer -### .isConnected() +#### .isConnected() Checks if the driver is connected to a board. **_Returns_** a boolean, true if connected -### .isStreaming() +#### .isStreaming() Checks if the board is currently sending samples. **_Returns_** a boolean, true if streaming -### .listPorts() +#### .listPorts() List available ports so the user can choose a device when not automatically found. **_Returns_** a promise, fulfilled with a list of available serial ports. -### .numberOfChannels() +#### .numberOfChannels() Get the current number of channels available to use. (i.e. 8 or 16). @@ -715,25 +720,25 @@ Get the current number of channels available to use. (i.e. 8 or 16). **_Returns_** a number, the total number of available channels. -### .printBytesIn() +#### .printBytesIn() Prints the total number of bytes that were read in this session to the console. -### .printPacketsBad() +#### .printPacketsBad() Prints the total number of packets that were not able to be read in this session to the console. -### .printPacketsRead() +#### .printPacketsRead() Prints the total number of packets that were read in this session to the console. -### .printRegisterSettings() +#### .printRegisterSettings() Prints all register settings for the ADS1299 and the LIS3DH on the OpenBCI board. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .radioBaudRateSet(speed) +#### .radioBaudRateSet(speed) Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial data is sent from the Host to the Serial driver. The rate can either be set to default or fast. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the new baud rate after closing the current serial port and reopening one. @@ -745,7 +750,7 @@ Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, **_Returns_** {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. -### .radioChannelGet() +#### .radioChannelGet() Used to query the OpenBCI system for it's radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve an Object. See `returns` below. @@ -753,7 +758,7 @@ Used to query the OpenBCI system for it's radio channel number. The function wil **_Returns_** {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in the condition that there system is experiencing board communications failure. -### .radioChannelSet(channelNumber) +#### .radioChannelSet(channelNumber) Used to set the system radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -765,7 +770,7 @@ Used to set the system radio channel number. The function will reject if not con **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioChannelSetHostOverride(channelNumber) +#### .radioChannelSetHostOverride(channelNumber) Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if your dongle and board are not on the right channel and bring down your radio system if you take your dongle and board are not on the same channel. Use with caution! The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -777,7 +782,7 @@ Used to set the ONLY the radio dongle Host channel number. This will fix your ra **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioPollTimeGet() +#### .radioPollTimeGet() Used to query the OpenBCI system for it's device's poll time. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the poll time when fulfilled. It's important to note that if the board is not on, this function will always be rejected with a failure message. @@ -785,7 +790,7 @@ Used to query the OpenBCI system for it's device's poll time. The function will **_Returns_** {Promise} - Resolves with the new poll time, rejects with err. -### .radioPollTimeSet(pollTime) +#### .radioPollTimeSet(pollTime) Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this sets the interval at which the Device polls the Host for new information. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. @@ -797,7 +802,7 @@ Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is **_Returns_** {Promise} - Resolves with the new channel number, rejects with err. -### .radioSystemStatusGet() +#### .radioSystemStatusGet() Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are in fact ready to start trying to connect and such. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the same channel and powered, then this will resolve true. @@ -805,7 +810,7 @@ Used to ask the Host if it's radio system is up. This is useful to quickly deter **_Returns_** {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. -### .sampleRate() +#### .sampleRate() Get the current sample rate. @@ -813,7 +818,7 @@ Get the current sample rate. **_Returns_** a number, the current sample rate. -### .sdStart(recordingDuration) +#### .sdStart(recordingDuration) Start logging to the SD card. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. @@ -835,13 +840,13 @@ The duration you want to log SD information for. Opens a new SD file to write in **_Returns_** resolves if the command was added to the write queue. -### .sdStop() +#### .sdStop() Stop logging to the SD card and close any open file. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. The success message contains a lot of useful information about what happened when writing to the SD card. **_Returns_** resolves if the command was added to the write queue. -### .simulatorEnable() +#### .simulatorEnable() To enter simulate mode. Must call [`.connect()`](#method-connect) after. @@ -849,7 +854,7 @@ To enter simulate mode. Must call [`.connect()`](#method-connect) after. **_Returns_** a promise, fulfilled if able to enter simulate mode, reject if not. -### .simulatorDisable() +#### .simulatorDisable() To leave simulate mode. @@ -857,27 +862,27 @@ To leave simulate mode. **_Returns_** a promise, fulfilled if able to stop simulate mode, reject if not. -### .sntp +#### .sntp Extends the popular SNTP package on [npmjs](https://www.npmjs.com/package/sntp) -### .sntpGetOffset() +#### .sntpGetOffset() Stateful method for querying the current offset only when the last one is too old. (defaults to daily) **_Returns_** a promise with the time offset -### .sntpStart() +#### .sntpStart() This starts the SNTP server and gets it to remain in sync with the SNTP server. **_Returns_** a promise if the module was able to sync with NTP server. -### .sntpStop() +#### .sntpStop() Stops the SNTP from updating -### .softReset() +#### .softReset() Sends a soft reset command to the board. @@ -885,7 +890,7 @@ Sends a soft reset command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue. -### .streamStart() +#### .streamStart() Sends a start streaming command to the board. @@ -893,7 +898,7 @@ Sends a start streaming command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. -### .streamStop() +#### .streamStop() Sends a stop streaming command to the board. @@ -901,7 +906,7 @@ Sends a stop streaming command to the board. **_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. -### .syncClocks() +#### .syncClocks() Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using version +2 firmware. @@ -909,25 +914,25 @@ Send the command to tell the board to start the syncing protocol. Must be connec **_Returns_** {Promise} resolves if the command was sent to the write queue, rejects if unable. -### .syncClocksFull() +#### .syncClocksFull() Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using v2 firmware. Uses the `synced` event to ensure multiple syncs don't overlap. **Note, this functionality requires OpenBCI Firmware Version 2.0** **_Returns_** {Promise} resolves if `synced` event is emitted, rejects if not connected or using firmware v2. Resolves with a synced object: -```javascript +```js { - boardTime: 0, // The time contained in the time sync set packet. - correctedTransmissionTime: false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation. - timeSyncSent: 0, // The time the `<` was sent to the Dongle. - timeSyncSentConfirmation: 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle. - timeSyncSetPacket: 0, // The time the set packet was received from the Board. - timeRoundTrip: 0, // Simply timeSyncSetPacket - timeSyncSent. - timeTransmission: 0, // Estimated time it took for time sync set packet to be sent from Board to Driver. - timeOffset: 0, // The map (or translation) from boardTime to module time. - timeOffsetMaster: 0, // The map (or translation) from boardTime to module time averaged over time syncs. - valid: false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below. + "boardTime": 0, // The time contained in the time sync set packet. + "correctedTransmissionTime": false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation. + "timeSyncSent": 0, // The time the `<` was sent to the Dongle. + "timeSyncSentConfirmation": 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle. + "timeSyncSetPacket": 0, // The time the set packet was received from the Board. + "timeRoundTrip": 0, // Simply timeSyncSetPacket - timeSyncSent. + "timeTransmission": 0, // Estimated time it took for time sync set packet to be sent from Board to Driver. + "timeOffset": 0, // The map (or translation) from boardTime to module time. + "timeOffsetMaster": 0, // The map (or translation) from boardTime to module time averaged over time syncs. + "valid": false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below. } ``` @@ -936,73 +941,71 @@ Send the command to tell the board to start the syncing protocol. Must be connec Syncing multiple times to base the offset of the average of the four syncs. ```javascript -var OpenBCIBoard = require('openbci').OpenBCIBoard, - ourBoard = new OpenBCIBoard({ - verbose:true - }); - -var portName = /* INSERT PORT NAME HERE */; -var samples = []; // Array to store time synced samples into -var timeSyncActivated = false; +const Cyton = require('openbci').Cyton; +const ourBoard = new Cyton({ + verbose:true +}); +let portName = /* INSERT PORT NAME HERE */; +let samples = []; // Array to store time synced samples into +let timeSyncActivated = false; ourBoard.connect(portName) - .then(() => { - ourBoard.on('ready',() => { - ourBoard.streamStart() - .then(() => { - /** Could also call `.syncClocksFull()` here */ - }) - .catch(err => { - console.log(`Error starting stream ${err}`); - }) - }); - ourBoard.on('sample',sample => { - /** If we are not synced, then do that! */ - if (timeSyncActivated === false) { - timeSyncActivated = true; - ourBoard.syncClocksFull() - .then(syncObj => { - if (syncObj.valid) { - console.log('1st sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('2nd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('3rd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('4th sync done'); - - } - /* Do awesome time syncing stuff */ - }) - .catch(err => { - console.log(`sync err ${err}`); - }); + .then(() => { + ourBoard.on('ready',() => { + ourBoard.streamStart() + .then(() => { + /** Could also call `.syncClocksFull()` here */ + }) + .catch(err => { + console.log(`Error starting stream ${err}`); + }) + }); + ourBoard.on('sample',sample => { + /** If we are not synced, then do that! */ + if (timeSyncActivated === false) { + timeSyncActivated = true; + ourBoard.syncClocksFull() + .then(syncObj => { + if (syncObj.valid) { + console.log('1st sync done'); } - if (startLoggingSamples && sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) { - /** If you only want to log samples with time stamps */ - samples.push(sample); + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('2nd sync done'); } - }); - }) + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('3rd sync done'); + } + return ourBoard.syncClocksFull(); + }) + .then(syncObj => { + if (syncObj.valid) { + console.log('4th sync done'); + } + /* Do awesome time syncing stuff */ + }) + .catch(err => { + console.log(`sync err ${err}`); + }); + } + if (sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) { + /** If you only want to log samples with time stamps */ + samples.push(sample); + } + }); +}) .catch(err => { - console.log(`connect ${err}`); + console.log(`connect ${err}`); }); ``` -### .testSignal(signal) +#### .testSignal(signal) Apply the internal test signal to all channels. @@ -1020,13 +1023,13 @@ A String indicating which test signal to apply **_Returns_** a promise, if the commands were sent to write buffer. -### .time() +#### .time() Uses `._sntpNow()` time when sntpTimeSync specified `true` in options, or else Date.now() for time. **_Returns_** time since UNIX epoch in ms. -### .usingVersionTwoFirmware() +#### .usingVersionTwoFirmware() Convenience method to determine if you can use firmware v2.x.x capabilities. @@ -1034,7 +1037,7 @@ Convenience method to determine if you can use firmware v2.x.x capabilities. **_Returns_** a boolean, true if using firmware version 2 or greater. -### .write(dataToWrite) +#### .write(dataToWrite) Send commands to the board. Due to the OpenBCI board firmware 1.0, a 10ms spacing **must** be observed between every command sent to the board. This method handles the timing and spacing between characters by adding characters to a global write queue and pulling from it every 10ms. If you are using firmware version +2.0 then you no spacing will be used. @@ -1066,50 +1069,49 @@ ourBoard.write('c'); ourBoard.write('o'); ``` -## Events: +### Events: -### .on('close', callback) +#### .on('close', callback) Emitted when the serial connection to the board is closed. -### .on('droppedPacket', callback) +#### .on('droppedPacket', callback) Emitted when a packet (or packets) are dropped. Returns an array. -### .on('error', callback) +#### .on('error', callback) Emitted when there is an on the serial port. -### .on('impedanceArray', callback) +#### .on('impedanceArray', callback) Emitted when there is a new impedanceArray available. Returns an array. -### .on('query', callback) +#### .on('query', callback) Emitted resulting in a call to [`.getChannelSettings()`](#method-get-settings-for-channel) with the channelSettingsObject -### .on('rawDataPacket', callback) +#### .on('rawDataPacket', callback) Emitted when there is a new raw data packet available. -### .on('ready', callback) +#### .on('ready', callback) Emitted when the board is in a ready to start streaming state. -### .on('sample', callback) +#### .on('sample', callback) Emitted when there is a new sample available. -### .on('synced', callback) +#### .on('synced', callback) Emitted when there is a new sample available. -## Constants: +### Constants: To use the constants file simply: ```js -var openBCIBoard = require('openbci'); -var k = openBCIBoard.OpenBCIConstants; +const k = require('openbci').Constants; console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console. ``` @@ -1194,7 +1196,7 @@ MIT ## Roadmap: -1. Ganglion integration (2.x) +1. Ganglion integration (3.x) 2. Compatible with node streams (3.x) 3. Remove factory paradigm from main file (3.x) 5. ES6/ES7 total adoption (3.x) diff --git a/changelog.md b/changelog.md index 7695e26..2af7616 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,22 @@ +# 2.0.0 + +### New Features +* `index.js` file allows for ES6 destructing + +### Breaking Changes +* Change name of `OpenBCIBoard` to `Cyton` to follow new naming convention. + Simply change: + ```ecmascript 6 + const OpenBCIBoard = require('openbci').OpenBCIBoard; + const ourBoard = new OpenBCIBoard(); + ``` + ```ecmascript 6 + const Cyton = require('openbci').Cyton; + const ourBoard = new Cyton(); + ``` +* Major change to how board is initialized with removal of `factory` paradigm. +* Drop support for Node 4 and 5 due to lack of EMACS 6 + # 1.4.3 ### New examples @@ -237,7 +256,7 @@ The second major release for the OpenBCI Node.js SDK brings major changes, impro ### Bug Fixes * updates to README.me and comments to change ntp to sntp, because the two are similar, but not the same and we do not want to be misleading -* Extended [Stnp](https://www.npmjs.com/package/sntp) to main openBCIBoard.js +* Extended [Stnp](https://www.npmjs.com/package/sntp) to main openBCICyton.js * Add `.sntpNow()` function to get ntp time. # 0.3.1 diff --git a/index.js b/index.js new file mode 100644 index 0000000..c6cbbaa --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +module.exports.Cyton = require('./openBCICyton'); +module.exports.Constants = require('./openBCIConstants'); diff --git a/openBCIBoard.js b/openBCIBoard.js deleted file mode 100644 index cb01d5d..0000000 --- a/openBCIBoard.js +++ /dev/null @@ -1,2407 +0,0 @@ -'use strict'; -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var stream = require('stream'); -var SerialPort = require('serialport'); -var openBCISample = require('./openBCISample'); -var k = openBCISample.k; -var openBCISimulator = require('./openBCISimulator'); -var Sntp = require('sntp'); -var bufferEqual = require('buffer-equal'); -var math = require('mathjs'); - -/** -* @description SDK for OpenBCI Board {@link www.openbci.com} -* @module 'openbci-sdk' -*/ -function OpenBCIFactory () { - var factory = this; - - var _options = { - boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], - baudRate: 115200, - simulate: false, - simulatorBoardFailure: false, - simulatorDaisyModuleAttached: false, - simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], - simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], - simulatorLatencyTime: 16, - simulatorBufferSize: 4096, - simulatorHasAccelerometer: true, - simulatorInternalClockDrift: 0, - simulatorInjectAlpha: true, - simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], - simulatorSampleRate: 250, - simulatorSerialPortFailure: false, - sntpTimeSync: false, - sntpTimeSyncHost: 'pool.ntp.org', - sntpTimeSyncPort: 123, - verbose: false, - debug: false - }; - - /** - * @description The initialization method to call first, before any other method. - * @param options (optional) - Board optional configurations. - * - `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if - * firmware on board has been previously configured. - * - * - `boardType` {String} - Specifies type of OpenBCI board. - * 3 Possible Boards: - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. - * `ganglion` - 4 Channel board - * (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) - * - * - `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting - * `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) - * - * - `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on - * the board is not polling the RFduino on the dongle. (Default `false`) - * - * - `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. - * This is useful if you want to test how your application reacts to a user requesting 16 channels - * but there is no daisy module actually attached, or vice versa, where there is a daisy module - * attached and the user only wants to use 8 channels. (Default `false`) - * - * - `simulatorFirmwareVersion` {String} - Allows simulator to be started with firmware version 2 features - * 2 Possible Options: - * `v1` - Firmware Version 1 (Default) - * `v2` - Firmware Version 2 - * - * - `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which - * occurs commonly in real devices. It is recommended to test code with this enabled. - * 4 Possible Options: - * `none` - do not fragment packets; output complete chunks immediately when produced (Default) - * `random` - output random small chunks of data interspersed with full buffers - * `fullBuffers` - allow buffers to fill up until the latency timer has expired - * `oneByOne` - output each byte separately - * - * - `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers, - if `simulatorFragmentation` is specified. (Default `16`) - * - * - `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is - * specified. (Default `4096`) - * - * - `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) - * - * - `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) - * - * - `simulatorInjectLineNoise` {String} - Injects line noise on channels. - * 3 Possible Options: - * `60Hz` - 60Hz line noise (Default) [America] - * `50Hz` - 50Hz line noise [Europe] - * `none` - Do not inject line noise. - * - * - `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if - * `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that - * setting and this sample rate will be used. (Default is `250`) - * - * - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely - * due to a OpenBCI dongle not being plugged in. - * - * - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source - * of truth instead of local computer time. If you are running experiements on your local - * computer, keep this `false`. (Default `false`) - * - * - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). - * - * - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`) - * - * - `verbose` {Boolean} - Print out useful debugging events. (Default `false`) - * - * - `debug` {Boolean} - Print out a raw dump of bytes sent and received. (Default `false`) - * - * @constructor - * @author AJ Keller (@pushtheworldllc) - */ - function OpenBCIBoard (options) { - options = (typeof options !== 'function') && options || {}; - var opts = {}; - - stream.Stream.call(this); - - /** Configuring Options */ - var o; - for (o in _options) { - var userOption = (o in options) ? o : o.toLowerCase(); - var userValue = options[userOption]; - delete options[userOption]; - - 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'); - - // Set to global options object - this.options = opts; - - /** Properties (keep alphabetical) */ - // Arrays - this.accelArray = [0, 0, 0]; // X, Y, Z - this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); - this.writeOutArray = []; - // Booleans - this._streaming = false; - // Buffers - this.buffer = null; - this.masterBuffer = masterBufferMaker(); - // Objects - this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType)); - this.impedanceTest = { - active: false, - isTestingPInput: false, - isTestingNInput: false, - onChannel: 0, - sampleNumber: 0, - continuousMode: false, - impedanceForChannel: 0 - }; - this.info = { - boardType: this.options.boardType, - sampleRate: k.OBCISampleRate125, - firmware: k.OBCIFirmwareV1, - numberOfChannels: k.OBCINumberOfChannelsDefault, - missedPackets: 0 - }; - if (this.options.boardType === k.OBCIBoardDefault) { - this.info.sampleRate = k.OBCISampleRate250; - } - - this._lowerChannelsSampleObject = null; - this.serial = null; - this.sync = { - curSyncObj: null, - eventEmitter: null, - objArray: [], - sntpActive: false, - timeOffsetMaster: 0, - timeOffsetAvg: 0, - timeOffsetArray: [] - }; - this.writer = null; - // Numbers - this.badPackets = 0; - this.curParsingMode = k.OBCIParsingReset; - this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); - this.previousSampleNumber = -1; - this.sampleCount = 0; - this.timeOfPacketArrival = 0; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - // Strings - - // NTP - if (this.options.sntpTimeSync) { - // establishing ntp connection - this.sntpStart() - .catch(ignored => { - // try again once after a delay - return new Promise((resolve, reject) => { - setTimeout(resolve, 500); - }).then(() => this.sntpStart()); - }) - .then(() => { - if (this.options.verbose) console.log('SNTP: connected'); - }) - .catch(err => { - if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); - this.emit('error', err); - }); - } - - // TODO: Add connect immediately functionality, suggest this to be the default... - } - - // This allows us to use the emitter class freely outside of the module - util.inherits(OpenBCIBoard, stream.Stream); - - /** - * @description The essential precursor method to be called initially to establish a - * serial connection to the OpenBCI board. - * @param portName - a string that contains the port name of the OpenBCIBoard. - * @returns {Promise} if the board was able to connect. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.connect = function (portName) { - return new Promise((resolve, reject) => { - if (this.isConnected()) return reject('already connected!'); - - /* istanbul ignore else */ - if (this.options.simulate || portName === k.OBCISimulatorPortName) { - this.options.simulate = true; - // If we are simulating, set portName to fake name - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('using faux board ' + portName); - this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { - accel: this.options.simulatorHasAccelerometer, - alpha: this.options.simulatorInjectAlpha, - boardFailure: this.options.simulatorBoardFailure, - daisy: this.options.simulatorDaisyModuleAttached, - drift: this.options.simulatorInternalClockDrift, - firmwareVersion: this.options.simulatorFirmwareVersion, - fragmentation: this.options.simulatorFragmentation, - latencyTime: this.options.simulatorLatencyTime, - bufferSize: this.options.simulatorBufferSize, - lineNoise: this.options.simulatorInjectLineNoise, - sampleRate: this.options.simulatorSampleRate, - serialPortFailure: this.options.simulatorSerialPortFailure, - verbose: this.options.verbose - }); - } else { - this.portName = portName; - if (this.options.verbose) console.log('using real board ' + portName); - this.serial = new SerialPort(portName, { - baudRate: this.options.baudRate - }, (err) => { - if (err) reject(err); - }); - } - - if (this.options.verbose) console.log('Serial port connected'); - - this.serial.on('data', data => { - this._processBytes(data); - }); - this.serial.once('open', () => { - if (this.options.verbose) console.log('Serial port open'); - new Promise(resolve => { - // TODO: document why this 300 ms delay is needed - setTimeout(resolve, this.options.simulate ? 50 : 300); - }).then(() => { - if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); - return this.write(k.OBCIStreamStop); - }).then(() => { - return new Promise(resolve => this.serial.flush(resolve)); - }).then(() => { - // TODO: document why this 250 ms delay is needed - return new Promise(resolve => setTimeout(resolve, 250)); - }).then(() => { - if (this.options.verbose) console.log('Sending soft reset'); - // TODO: this promise chain resolves early because - // A. some legacy code (in tests) sets the ready handler after this resolves - // and - // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented - // which is C. not implemented yet except in a manner such that replies occur in the write handler, - // resulting in the EOT arriving before this resolves - // Fix one or more of the above 3 situations, then move resolve() to the next block. - resolve(); - return this.softReset(); - }).then(() => { - if (this.options.verbose) console.log("Waiting for '$$$'"); - }); - }); - this.serial.once('close', () => { - if (this.options.verbose) console.log('Serial Port Closed'); - // 'close' is emitted in _disconnected() - this._disconnected('port closed'); - }); - this.serial.once('error', (err) => { - if (this.options.verbose) console.log('Serial Port Error'); - this.emit('error', err); - this._disconnected(err); - }); - }); - }; - - /** - * @description Called once when for any reason the serial port is no longer open. - * @private - */ - OpenBCIBoard.prototype._disconnected = function (err) { - this._streaming = false; - - clearTimeout(this.writer); - this.writer = null; - - this.serial.removeAllListeners(); - this.serial = null; - - this.emit('close'); - - while (this.writeOutArray.length > 0) { - var command = this.writeOutArray.pop(); - if (command.reject) command.reject(err); - } - }; - - /** - * @description Closes the serial port. Waits for stop streaming command to - * be sent if currently streaming. - * @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.disconnect = function () { - return Promise.resolve() - .then(() => { - if (this.isStreaming()) { - if (this.options.verbose) console.log('stop streaming'); - return this.streamStop(); - } - }) - .then(() => { - if (!this.isConnected()) { - return Promise.reject('no board connected'); - } else { - return new Promise((resolve, reject) => { - // serial emitting 'close' will call _disconnected - this.serial.close(() => { - resolve(); - }); - }); - } - }); - }; - - /** - * @description Checks if the driver is connected to a board. - * @returns {boolean} - True if connected. - */ - OpenBCIBoard.prototype.isConnected = function () { - if (!this.serial) return false; - return this.serial.isOpen(); - }; - - /** - * @description Checks if the board is currently sending samples. - * @returns {boolean} - True if streaming. - */ - OpenBCIBoard.prototype.isStreaming = function () { - return this._streaming; - }; - - /** - * @description Sends a start streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board will start streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.streamStart = function () { - return new Promise((resolve, reject) => { - if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); - this._streaming = true; - this._reset_ABANDONED(); // framework is incomplete but looks useful - this.write(k.OBCIStreamStart).then(resolve, reject); - }); - }; - - /** - * @description Sends a stop streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board stopped streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.streamStop = function () { - return new Promise((resolve, reject) => { - if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); - this._streaming = false; - this.write(k.OBCIStreamStop).then(resolve, reject); - }); - }; - - /** - * @description To start simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.simulatorEnable = function () { - return new Promise((resolve, reject) => { - if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? - if (this.isConnected()) { - this.disconnect() // disconnect first - .then(() => { - this.options.simulate = true; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = true; - resolve(); - } - }); - }; - - /** - * @description To stop simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.simulatorDisable = function () { - return new Promise((resolve, reject) => { - if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? - if (this.isConnected()) { - this.disconnect() - .then(() => { - this.options.simulate = false; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = false; - resolve(); - } - }); - }; - - /** - * @description To be able to easily write to the board but ensure that we never send commands - * with less than a 10ms spacing between sends in early version boards. This uses - * an array and shifts off the entries until there are none left. - * @param dataToWrite - Either a single character or an Array of characters - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.write = function (dataToWrite) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) { - reject('not connected'); - } else { - if (Array.isArray(dataToWrite)) { // Got an input array - var len = dataToWrite.length; - for (var i = 0; i < len; i++) { - this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); - } - this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; - } else { - this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); - } - - if (!this.writer) { // there is no writer started - var writerFunction = () => { - if (this.writeOutArray.length === 0) { - this.writer = null; - return; - } - - var command = this.writeOutArray.shift(); - var promise = this._writeAndDrain(command.cmd); - - promise.then(() => { - this.writer = setTimeout(writerFunction, this.writeOutDelay); - }, () => { - // write failed but more commands may be pending that need a result - writerFunction(); - }); - - if (command.reject) { - promise.catch(err => { - if (this.options.verbose) console.log('write failure: ' + err); - command.reject(err); - }); - } - if (command.resolve) promise.then(command.resolve); - }; - this.writer = setTimeout(writerFunction, this.writeOutDelay); - } - } - }); - }; - - /** - * @description Should be used to send data to the board - * @param data {Buffer} - The data to write out - * @returns {Promise} if signal was able to be sent - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._writeAndDrain = function (data) { - this._debugBytes('>>>', data); - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Serial port not open'); - this.serial.write(data, (error) => { - if (error) { - console.log('Error [writeAndDrain]: ' + error); - reject(error); - } else { - this.serial.drain(function () { - resolve(); - }); - } - }); - }); - }; - - /** - * @description Automatically find an OpenBCI board. - * Note: This method is used for convenience and should be used when trying to - * connect to a board. If you find a case (i.e. a platform (linux, - * windows...) that this does not work, please open an issue and - * we will add support! - * @returns {Promise} - Fulfilled with portName, rejected when can't find the board. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.autoFindOpenBCIBoard = function () { - var serialPatterns = [ - { // mac - comName: /usbserial-D/ - }, - { // linux - comName: /^\/dev\/ttyUSB/, - manufacturer: /^FTDI$/, - serialNumber: /^FTDI_FT231X_USB_UART/, - vendorId: /^0x0403$/, - productId: /^0x6015$/ - } - ]; - return new Promise((resolve, reject) => { - /* istanbul ignore else */ - if (this.options.simulate) { - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('auto found sim board'); - resolve(k.OBCISimulatorPortName); - } else { - SerialPort.list((err, ports) => { - if (err) { - if (this.options.verbose) console.log('serial port err'); - reject(err); - } - // This is one big if statement - if (ports.some(port => { - return serialPatterns.some(patterns => { - for (var attribute in patterns) { - if (!String(port[attribute]).match(patterns[attribute])) { - return false; - } - } - this.portName = port.comName; - return true; - }); - })) { - if (this.options.verbose) console.log('auto found board'); - resolve(this.portName); - } else { - if (this.options.verbose) console.log('could not find board'); - reject('Could not auto find board'); - } - }); - } - }); - }; - - /** - * @description Convenience method to determine if you can use firmware v2.x.x - * capabilities. - * @returns {boolean} - True if using firmware version 2 or greater. Should - * be called after a `.softReset()` because we can parse the output of that - * to determine if we are using firmware version 2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.usingVersionTwoFirmware = function () { - if (this.options.simulate) { - return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; - } else { - return this.info.firmware === k.OBCIFirmwareV2; - } - }; - - /** - * @description Used to set the system radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelSet = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); - }); - }; - - /** - * @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if - * your dongle and board are not on the right channel and bring down your radio system if you take your - * dongle and board are not on the same channel. Use with caution! The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelSetHostOverride = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(`${data.toString()}`); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); - }); - }; - - /** - * @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * an Object. See `returns` below. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in - * the condition that there system is experiencing board communications failure. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioChannelGet = function () { - // The function to run on timeout - var badCommsTimeout; - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 500); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { - resolve({ - channelNumber: data[data.length - 4], - data: data - }); - } else { - reject(`Error [radioChannelGet]: ${data.toString()}`); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); - }); - }; - - /** - * @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * the poll time when fulfilled. It's important to note that if the board is not on, this function will always - * be rejected with a failure message. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves with the poll time, rejects with an error message. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioPollTimeGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - var pollTime = data[data.length - 4]; - resolve(pollTime); - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); - }); - }; - - /** - * @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the - * Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this - * sets the interval at which the Device polls the Host for new information. Further the function should reject - * if currently streaming. Lastly and more important, if the board is not running the new firmware then this - * functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this - * function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 - * @since 1.0.0 - * @returns {Promise} - Resolves with new poll time, rejects with error message. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioPollTimeSet = function (pollTime) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); - if (!k.isNumber(pollTime)) return reject('Must input type Number'); - if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); - if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); // Ditch the eot $$$ - } else { - reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); - }); - }; - - /** - * @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the - * Host and the Board is the Device. Only the Device can initiate a communication between the two entities. - * There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then - * all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial - * data is sent from the Host to the Serial driver. The rate can either be set to default or fast. - * Further the function should reject if currently streaming. Lastly and more important, if the board is not - * running the new firmware then this functionality does not exist and thus this method will reject. - * If the board is using firmware 2+ then this function should resolve the new baud rate after closing the - * current serial port and reopening one. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) - * @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioBaudRateSet = function (speed) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (!k.isString(speed)) return reject('Must input type String'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - var eotBuf = new Buffer('$$$'); - var newBaudRateBuf; - for (var i = data.length; i > 3; i--) { - if (bufferEqual(data.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = data.slice(i - 9, i - 3); - break; - } - } - var newBaudRateNum = Number(newBaudRateBuf.toString()); - if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { - return reject('Error parse mismatch, restart your system!'); - } - if (!this.isConnected()) { - reject('Lost connection to device during baud set'); - } else if (openBCISample.isSuccessInBuffer(data)) { - // Change the sample rate here - if (this.options.simulate === false) { - this.serial.update({baudRate: newBaudRateNum}, err => { - if (err) return reject(err); - else resolve(newBaudRateNum); - }); - } else { - resolve(newBaudRateNum); - } - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - if (speed === k.OBCIRadioBaudRateFastStr) { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); - } else { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); - } - }); - }; - - /** - * @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are - * in fact ready to start trying to connect and such. The function will reject if not connected to the serial - * port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the - * same channel and powered, then this will resolve true. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.radioSystemStatusGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (this.options.verbose) console.log(data.toString()); - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(true); - } else { - resolve(false); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); - }); - }; - - /** - * @description List available ports so the user can choose a device when not - * automatically found. - * Note: This method is used for convenience essentially just wrapping up - * serial port. - * @author Andy Heusser (@andyh616) - * @returns {Promise} - On fulfill will contain an array of Serial ports to use. - */ - OpenBCIBoard.prototype.listPorts = function () { - return new Promise((resolve, reject) => { - SerialPort.list((err, ports) => { - if (err) reject(err); - else { - ports.push({ - comName: k.OBCISimulatorPortName, - manufacturer: '', - serialNumber: '', - pnpId: '', - locationId: '', - vendorId: '', - productId: '' - }); - resolve(ports); - } - }); - }); - }; - - /** - * @description Sends a soft reset command to the board - * @returns {Promise} - * Note: The softReset command MUST be sent to the board before you can start - * streaming. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.softReset = function () { - this.curParsingMode = k.OBCIParsingReset; - return this.write(k.OBCIMiscSoftReset); - }; - - /** - * @description To get the specified channelSettings register data from printRegisterSettings call - * @param channelNumber - a number - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - // TODO: REDO THIS FUNCTION - OpenBCIBoard.prototype.getSettingsForChannel = function (channelNumber) { - return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { - // this.searchingBuf = newSearchingBuffer - return this.printRegisterSettings(); - }); - }; - - /** - * @description To print out the register settings to the console - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.printRegisterSettings = function () { - return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { - this.curParsingMode = k.OBCIParsingChannelSettings; - }); - }; - - /** - * @description Send a command to the board to turn a specified channel off - * @param channelNumber - * @returns {Promise.} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelOff = function (channelNumber) { - return k.commandChannelOff(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); - }; - - /** - * @description Send a command to the board to turn a specified channel on - * @param channelNumber - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelOn = function (channelNumber) { - return k.commandChannelOn(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); - }; - - /** - * @description To send a channel setting command to the board - * @param channelNumber - Number (1-16) - * @param powerDown - Bool (true -> OFF, false -> ON (default)) - * turns the channel on or off - * @param gain - Number (1,2,4,6,8,12,24(default)) - * sets the gain for the channel - * @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) - * selects the ADC channel input source - * @param bias - Bool (true -> Include in bias (default), false -> remove from bias) - * selects to include the channel input in bias generation - * @param srb2 - Bool (true -> Connect this input to SRB2 (default), - * false -> Disconnect this input from SRB2) - * Select to connect (true) this channel's P input to the SRB2 pin. This closes - * a switch between P input and SRB2 for the given channel, and allows the - * P input to also remain connected to the ADC. - * @param srb1 - Bool (true -> connect all N inputs to SRB1, - * false -> Disconnect all N inputs from SRB1 (default)) - * Select to connect (true) all channels' N inputs to SRB1. This effects all pins, - * and disconnects all N inputs from the ADC. - * @returns {Promise} resolves if sent, rejects on bad input or no board - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { - var arrayOfCommands = []; - return new Promise((resolve, reject) => { - k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) - .then((val) => { - arrayOfCommands = val.commandArray; - this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; - return this.write(arrayOfCommands); - }).then(resolve, reject); - }); - }; - - /** - * @description Apply the internal test signal to all channels - * @param signal - A string indicating which test signal to apply - * - `dc` - * - Connect to DC signal - * - `ground` - * - Connect to internal GND (VDD - VSS) - * - `pulse1xFast` - * - Connect to test signal 1x Amplitude, fast pulse - * - `pulse1xSlow` - * - Connect to test signal 1x Amplitude, slow pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, fast pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, slow pulse - * - `none` - * - Reset to default - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.testSignal = function (signal) { - return new Promise((resolve, reject) => { - k.getTestSignalCommand(signal) - .then(command => { - return this.write(command); - }) - .then(() => resolve()) - .catch(err => reject(err)); - }); - }; - - /** - * @description - Sends command to turn on impedances for all channels and continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestContinuousStart = function () { - return new Promise((resolve, reject) => { - if (this.impedanceTest.active) return reject('Error: test already active'); - if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); - - this.impedanceTest.active = true; - this.impedanceTest.continuousMode = true; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, true)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); - }; - - /** - * @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestContinuousStop = function () { - return new Promise((resolve, reject) => { - if (!this.impedanceTest.active) return reject('Error: no test active'); - if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); - - this.impedanceTest.active = false; - this.impedanceTest.continuousMode = false; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, false)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); - }; - - /** - * @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a - * little while to actually run (<8 seconds)! - * @returns {Promise} - Resovles when complete testing all the channels. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestAllChannels = function () { - var upperLimit = k.OBCINumberOfChannelsDefault; - - /* istanbul ignore if */ - if (this.options.daisy) { - upperLimit = k.OBCINumberOfChannelsDaisy; - } - - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > upperLimit) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - this.impedanceTestChannel(channelNumber) - .then(() => { - resolve(completeChannelImpedanceTest(channelNumber + 1)); - /* istanbul ignore next */ - }).catch(err => reject(err)); - } - }); - }; - - return completeChannelImpedanceTest(1); - }; - - /** - * @description To test specific input configurations of channels! - * @param arrayOfChannels - The array of configurations where: - * 'p' or 'P' is only test P input - * 'n' or 'N' is only test N input - * 'b' or 'B' is test both inputs (takes 66% longer to run) - * '-' to ignore channel - * EXAMPLE: - * For 8 channel board: ['-','N','n','p','P','-','b','b'] - * (Note: it doesn't matter if capitalized or not) - * @returns {Promise} - Fulfilled with a loaded impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannels = function (arrayOfChannels) { - if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - // Check proper length of array - if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > arrayOfChannels.length) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - - var testCommand = arrayOfChannels[channelNumber - 1]; - - if (testCommand === 'p' || testCommand === 'P') { - this.impedanceTestChannelInputP(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'n' || testCommand === 'N') { - this.impedanceTestChannelInputN(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'b' || testCommand === 'B') { - this.impedanceTestChannel(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else { // skip ('-') condition - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - } - } - }); - }; - return completeChannelImpedanceTest(1); - }; - - /** - * @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannel = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. - }) - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /** - * @description Run impedance test on a single channel, applying the test signal only to P input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannelInputP = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /** - * @description Run impedance test on a single channel, applying the test signal to N input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.impedanceTestChannelInputN = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); - }; - - /* istanbul ignore next */ - /** - * @description To apply the impedance test signal to an input for any given channel - * @param channelNumber - Number - The channel you want to test. - * @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. - * @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. - * @returns {Promise} - With Number value of channel number - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected'); - - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tSending command to apply test signal to P input.'); - } else if (!pInput && nInput) { - console.log('\tSending command to apply test signal to N input.'); - } else if (pInput && nInput) { - console.log('\tSending command to apply test signal to P and N inputs.'); - } else { - console.log('\tSending command to stop applying test signal to both P and N inputs.'); - } - } - - if (!pInput && !nInput) { - this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort - } else { - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong - } - if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); - // Get impedance settings to send the board - k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { - return this.write(commandsArray); - }).then(() => { - /** - * If either pInput or nInput are true then we should start calculating impedance. Setting - * this.impedanceTest.active to true here allows us to route every sample for an impedance - * calculation instead of the normal sample output. - */ - if (pInput || nInput) this.impedanceTest.active = true; - resolve(channelNumber); - }, (err) => { - reject(err); - }); - }); - }; - - /** - * @description Calculates the impedance for a specified channel for a set time - * @param channelNumber - A Number, the channel number you want to test. - * @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. - * @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tCalculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tCalculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tCalculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - this.impedanceTest.onChannel = channelNumber; - this.impedanceTest.sampleNumber = 0; // Reset the sample number - this.impedanceTest.isTestingPInput = pInput; - this.impedanceTest.isTestingNInput = nInput; - // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) - // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) - setTimeout(() => { // Calculate for 250ms - this.impedanceTest.onChannel = 0; - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tDone calculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tDone calculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tDone calculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; - if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; - resolve(channelNumber); - }, 400); - }); - }; - - /** - * @description Calculates average and gets textual value of impedance for a specified channel - * @param channelNumber - A Number, the channel number you want to finalize. - * @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. - * @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tFinalizing impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tFinalizing impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tFinalizing impedance for P and N input.'); - } else { - console.log('\tNot Finalizing impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - - if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); - if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); - - setTimeout(() => { - resolve(channelNumber); - }, 50); // Introduce a delay to allow for extra time in case of back to back tests - }); - }; - - /** - * @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request - * response from the board. - * @param recordingDuration {String} - The duration you want to log SD information for. Limited to: - * '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' - * @returns {Promise} - Resolves when the command has been written. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sdStart = function (recordingDuration) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - k.sdSettingForString(recordingDuration) - .then(command => { - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(command).then(resolve, reject); - }) - .catch(err => reject(err)); - }); - }; - - /** - * @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted - * with request response from the board. - * @returns {Promise} - Resolves when written - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sdStop = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(k.OBCISDLogStop).then(resolve, reject); - }); - }; - - /** - * @description Get the the current sample rate is. - * @returns {Number} The sample rate - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sampleRate = function () { - if (this.options.simulate) { - return this.options.simulatorSampleRate; - } else { - if (this.info) { - return this.info.sampleRate; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCISampleRate125; - case k.OBCIBoardDefault: - default: - return k.OBCISampleRate250; - } - } - } - }; - - /** - * @description This function is used as a convenience method to determine how many - * channels the current board is using. - * @returns {Number} A number - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.numberOfChannels = function () { - if (this.info) { - return this.info.numberOfChannels; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCINumberOfChannelsDaisy; - case k.OBCIBoardDefault: - default: - return k.OBCINumberOfChannelsDefault; - } - } - }; - - /** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.syncClocks = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); - }); - }; - - /** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs - * don't overlap. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.1.0 - * @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.syncClocksFull = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - var timeout = setTimeout(() => { - return reject('syncClocksFull timeout after 500ms with no sync'); - }, 500); // Should not take more than 1s to sync up - this.sync.eventEmitter = syncObj => { - clearTimeout(timeout); - return resolve(syncObj); - }; - this.once('synced', this.sync.eventEmitter); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet) - .catch(err => { - clearTimeout(timeout); - return reject(err); - }); - }); - }; - - /** - * @description Output passed bytes on the console as a hexdump, if enabled - * @param prefix - label to show to the left of bytes - * @param data - bytes to output, a buffer or string - * @private - */ - OpenBCIBoard.prototype._debugBytes = function (prefix, data) { - if (!this.options.debug) return; - - if (typeof data === 'string') data = new Buffer(data); - - console.log('Debug bytes:'); - - for (var j = 0; j < data.length;) { - var hexPart = ''; - var ascPart = ''; - for (var end = Math.min(data.length, j + 16); j < end; ++j) { - var byt = data[j]; - - var hex = ('0' + byt.toString(16)).slice(-2); - hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in - hexPart += hex; - - var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; - ascPart += asc; - } - - // pad to fixed width for alignment - hexPart = (hexPart + ' ').substring(0, 3 * 17); - - console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); - } - }; - - /** - * @description Consider the '_processBytes' method to be the work horse of this - * entire framework. This method gets called any time there is new - * data coming in on the serial port. If you are familiar with the - * 'serialport' package, then every time data is emitted, this function - * gets sent the input data. The data comes in very fragmented, sometimes - * we get half of a packet, and sometimes we get 3 and 3/4 packets, so - * we will need to store what we don't read for next time. - * @param data - a buffer of unknown size - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processBytes = function (data) { - this._debugBytes(this.curParsingMode + '<<', data); - - // Concat old buffer - var oldDataBuffer = null; - if (this.buffer) { - oldDataBuffer = this.buffer; - data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); - } - - switch (this.curParsingMode) { - case k.OBCIParsingEOT: - if (openBCISample.doesBufferHaveEOT(data)) { - this.curParsingMode = k.OBCIParsingNormal; - this.emit('eot', data); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingReset: - // Does the buffer have an EOT in it? - if (openBCISample.doesBufferHaveEOT(data)) { - this._processParseBufferForReset(data); - this.curParsingMode = k.OBCIParsingNormal; - this.emit('ready'); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingTimeSyncSent: - // If there is only one match - if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { - if (this.options.verbose) console.log(`Found Time Sync Sent`); - this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); - this.curParsingMode = k.OBCIParsingNormal; - } - this.buffer = this._processDataBuffer(data); - break; - case k.OBCIParsingNormal: - default: - this.buffer = this._processDataBuffer(data); - break; - } - - if (this.buffer && oldDataBuffer) { - if (bufferEqual(this.buffer, oldDataBuffer)) { - this.buffer = null; - } - } - }; - - /** - * @description Used to extract samples out of a buffer of unknown length - * @param dataBuffer {Buffer} - A buffer to parse for samples - * @returns {Buffer} - Any data that was not pulled out of the buffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processDataBuffer = function (dataBuffer) { - if (!dataBuffer) return null; - var bytesToParse = dataBuffer.length; - // Exit if we have a buffer with less data than a packet - if (bytesToParse < k.OBCIPacketSize) return dataBuffer; - - var parsePosition = 0; - // Begin parseing - while (parsePosition <= bytesToParse - k.OBCIPacketSize) { - // Is the current byte a head byte that looks like 0xA0 - if (dataBuffer[parsePosition] === k.OBCIByteStart) { - // Now that we know the first is a head byte, let's see if the last one is a - // tail byte 0xCx where x is the set of numbers from 0-F (hex) - if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { - /** We just qualified a raw packet */ - // This could be a time set packet! - this.timeOfPacketArrival = this.time(); - // Grab the raw packet, make a copy of it. - var rawPacket; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } else { - rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } - - // Emit that buffer - this.emit('rawDataPacket', rawPacket); - // Submit the packet for processing - this._processQualifiedPacket(rawPacket); - // Overwrite the dataBuffer with a new buffer - var tempBuf; - if (parsePosition > 0) { - tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); - } else { - tempBuf = dataBuffer.slice(k.OBCIPacketSize); - } - if (tempBuf.length === 0) { - dataBuffer = null; - } else { - if (k.getVersionNumber(process.version) >= 6) { - dataBuffer = Buffer.from(tempBuf); - } else { - dataBuffer = new Buffer(tempBuf); - } - } - // Move the parse position up one packet - parsePosition = -1; - bytesToParse -= k.OBCIPacketSize; - } - } - parsePosition++; - } - - return dataBuffer; - }; - - /** - * @description Alters the global info object by parseing an incoming soft reset key - * @param dataBuffer {Buffer} - The soft reset data buffer - * @returns {Buffer} - If there is data left in the buffer, just it will be returned. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processParseBufferForReset = function (dataBuffer) { - if (openBCISample.countADSPresent(dataBuffer) === 2) { - this.info.boardType = k.OBCIBoardDaisy; - this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; - this.info.sampleRate = k.OBCISampleRate125; - } else { - this.info.boardType = k.OBCIBoardDefault; - this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; - this.info.sampleRate = k.OBCISampleRate250; - } - - if (openBCISample.findV2Firmware(dataBuffer)) { - this.info.firmware = k.OBCIFirmwareV2; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - } else { - this.info.firmware = k.OBCIFirmwareV1; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - } - }; - - /** - * @description Used to route qualified packets to their proper parsers - * @param rawDataPacketBuffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { - if (!rawDataPacketBuffer) return; - if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; - var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); - if (missedPacketArray) { - this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); - } - this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; - var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); - switch (packetType) { - case k.OBCIStreamPacketStandardAccel: - this._processPacketStandardAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketStandardRawAux: - this._processPacketStandardRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketUserDefinedType: - // Do nothing for User Defined Packets - break; - case k.OBCIStreamPacketAccelTimeSyncSet: - // Don't waste any time! - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketAccelTimeSynced: - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSyncSet: - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSynced: - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - default: - // Don't do anything if the packet is not defined - break; - } - }; - - /** - * @description A method used to compute impedances. - * @param sampleObject - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processImpedanceTest = function (sampleObject) { - var impedanceArray; - if (this.impedanceTest.continuousMode) { - // console.log('running in continuous mode...') - // openBCISample.debugPrettyPrint(sampleObject) - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.emit('impedanceArray', impedanceArray); - } - } else if (this.impedanceTest.onChannel !== 0) { - // Only calculate impedance for one channel - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; - } - } - }; - - /** - * @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketStandardAccel = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketStandardRawAux = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @param timeOfPacketArrival {Number} - The time the packet arrived. - * @private - * @returns {Object} - A time sync object. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { - /** - * Helper function for creating a bad sync object - * @param err {object} - Can be any object - * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} - */ - var getBadObject = (err) => { - var badObject = openBCISample.newSyncObject(); - badObject.timeOffsetMaster = this.sync.timeOffsetMaster; - // Create an error - badObject.error = err; - return badObject; - }; - - var syncObj = {}; - - if (this.sync.curSyncObj === null) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); - // Missed comma - } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); - } else { - try { - this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; - if (this.options.verbose) console.log('Got time set packet from the board'); - let boardTime = openBCISample.getFromTimePacketTime(rawPacket); - this.sync.curSyncObj.boardTime = boardTime; - // if (this.options.verbose) { - // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) - // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) - // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) - // } - - // Calculate the time between sending the `<` to getting the set packet, call this the round trip length - this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; - if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); - - // If the sync sent conf and set packet arrive in different serial flushes - // ------------------------------------------ - // | | timeTransmission | < GOOD :) - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - // - // Assume it's good... - this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); - - // If the conf and the set packet arrive in the same serial flush we have big problem! - // ------------------------------------------ - // | | | < BAD :( - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { - // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point - this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); - if (this.options.verbose) console.log(`Had to correct transmission time`); - this.sync.curSyncObj.correctedTransmissionTime = true; - } - - // Calculate the offset #finally - this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; - if (this.options.verbose) { - console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); - console.log(`Board time: ${boardTime}`); - } - - // Add to array - if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { - // Shift the oldest one out of the array - this.sync.timeOffsetArray.shift(); - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } else { - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } - - // Calculate the master time offset that we use averaging to compute - if (this.sync.timeOffsetArray.length > 1) { - var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); - this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); - } else { - this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; - } - - this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; - - if (this.options.verbose) { - console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); - } - - // Set the valid object to true - this.sync.curSyncObj.valid = true; - - // Make a byte by byte copy of event - syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); - - // Save obj to the global array - this.sync.objArray.push(syncObj); - } catch (err) { - // Log if verbose. - if (this.options.verbose) console.log(err.message); - syncObj = getBadObject(err); - } - } - // Fix the curParsingMode back to normal - this.curParsingMode = k.OBCIParsingNormal; - // Emit synced object - this.emit(k.OBCIEmitterSynced, syncObj); - // Set global to null - this.sync.curSyncObj = null; - // Return the object - return syncObj; - }; - - /** - * @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets - * an accelerometer value. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncedAccel = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that - * shall be emitted as a raw buffer and not scaled. - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } - }; - - /** - * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are - * being tested. - * @param sampleObject {Object} - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._finalizeNewSample = function (sampleObject) { - sampleObject._count = this.sampleCount++; - if (this.impedanceTest.active) { - this._processImpedanceTest(sampleObject); - } else { - // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper - // channels (9-16) come in packets with even sample numbers - if (this.info.boardType === k.OBCIBoardDaisy) { - // Send the sample for downstream sample compaction - this._finalizeNewSampleForDaisy(sampleObject); - } else { - this.emit(k.OBCIEmitterSample, sampleObject); - } - } - }; - - /** - * @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber - * sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a - * sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be - * emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the - * missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. - * @param sampleObject {Object} - The sample object to finalize - * @private - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype._finalizeNewSampleForDaisy = function (sampleObject) { - if (openBCISample.isOdd(sampleObject.sampleNumber)) { - // Check for the skipped packet condition - if (this._lowerChannelsSampleObject) { - // The last packet was odd... missed the even packet - this.info.missedPackets++; - } - this._lowerChannelsSampleObject = sampleObject; - } else { - // Make sure there is an odd packet waiting to get merged with this packet - if (this._lowerChannelsSampleObject) { - // Merge these two samples - var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); - // Set the _lowerChannelsSampleObject object to null - this._lowerChannelsSampleObject = null; - // Emite the new merged sample - this.emit('sample', mergedSample); - } else { - // Missed the odd packet, i.e. two evens in a row - this.info.missedPackets++; - } - } - }; - - /** - * @description Reset the master buffer and reset the number of bad packets. - * @author AJ Keller (@pushtheworldllc) - */ - // TODO: nothing is using these constructs, but they look like good constructs. See contents of masterBufferMaker() - OpenBCIBoard.prototype._reset_ABANDONED = function () { - this.masterBuffer = masterBufferMaker(); - this.badPackets = 0; - }; - - /** - * @description Stateful method for querying the current offset only when the last - * one is too old. (defaults to daily) - * @returns {Promise} A promise with the time offset - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sntpGetOffset = function () { - this.options.sntpTimeSync = true; - - if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); - - return new Promise((resolve, reject) => { - Sntp.offset({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, function (err, offset) { - if (err) reject(err); - else resolve(offset); - }); - }); - }; - - /** - * @description Allows users to utilize all features of sntp if they want to... - */ - OpenBCIBoard.prototype.sntp = Sntp; - - /** - * @description This gets the time plus offset - * @private - */ - OpenBCIBoard.prototype._sntpNow = Sntp.now; - - /** - * @description This starts the SNTP server and gets it to remain in sync with the SNTP server - * @returns {Promise} - A promise if the module was able to sync with ntp server. - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.sntpStart = function (options) { - this.options.sntpTimeSync = true; - - // Sntp.start doesn't actually report errors (2016-10-29) - // so functionality is first detected via sntpGetOffset - return this.sntpGetOffset().then(() => { - return new Promise((resolve, reject) => { - Sntp.start({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, () => { - this.sync.sntpActive = true; - this.emit('sntpTimeLock'); - resolve(); - }); - }); - }); - }; - - /** - * @description Stops the sntp from updating. - */ - OpenBCIBoard.prototype.sntpStop = function () { - Sntp.stop(); - this.options.sntpTimeSync = false; - this.sync.sntpActive = false; - }; - - /** - * @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time - * @returns {Number} - The time - * @author AJ Keller (@pushtheworldllc) - */ - OpenBCIBoard.prototype.time = function () { - if (this.options.sntpTimeSync) { - return this._sntpNow(); - } else { - return Date.now(); - } - }; - - /** - * @description This prints the total number of packets that were not able to be read - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printPacketsBad = function () { - if (this.badPackets > 1) { - console.log('Dropped a total of ' + this.badPackets + ' packets.'); - } else if (this.badPackets === 1) { - console.log('Dropped a total of 1 packet.'); - } else { - console.log('No packets dropped.'); - } - }; - - /** - * @description This prints the total bytes in - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printBytesIn = function () { - if (this.bytesIn > 1) { - console.log('Read in ' + this.bytesIn + ' bytes.'); - } else if (this.bytesIn === 1) { - console.log('Read one 1 packet in.'); - } else { - console.log('Read no packets.'); - } - }; - - /** - * @description This prints the total number of packets that have been read - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.printPacketsRead = function () { - if (this.masterBuffer.packetsRead > 1) { - console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); - } else if (this.masterBuffer.packetsIn === 1) { - console.log('Read 1 packet.'); - } else { - console.log('No packets read.'); - } - }; - - /** - * @description Nice convenience method to print some session details - * @author AJ Keller (@pushtheworldllc) - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.debugSession = function () { - this.printBytesIn(); - this.printPacketsRead(); - this.printPacketsBad(); - }; - - /** - * @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) - * @param channelSettingsObj - */ - /* istanbul ignore next */ - OpenBCIBoard.prototype.debugPrintChannelSettings = function (channelSettingsObj) { - console.log('-- Channel Settings Object --'); - var powerState = 'OFF'; - if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { - powerState = 'ON'; - } - console.log('---- POWER STATE: ' + powerState); - console.log('-- END --'); - }; - - /** - * @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. - * @param channelSettingsObject - * @returns {boolean} - */ - OpenBCIBoard.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { - return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; - }; - - // TODO: checkConnection (py: check_connection) - // TODO: reconnect (py: reconnect) - // TODO: testAuto - // TODO: getNbAUXChannels - // TODO: printIncomingText (py: print_incomming_text) - // TODO: warn - - factory.OpenBCIBoard = OpenBCIBoard; - factory.OpenBCIConstants = k; - factory.OpenBCISample = openBCISample; -} - -util.inherits(OpenBCIFactory, EventEmitter); - -module.exports = new OpenBCIFactory(); - -/** -* @description To parse a given channel given output from a print registers query -* @param rawChannelBuffer -* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -// function getChannelSettingsObj (rawChannelBuffer) { -// return new Promise(function (resolve, reject) { -// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { -// reject('Undefined or null channel buffer'); -// } -// -// var channelSettingsObject = { -// CHANNEL: '0', -// POWER_DOWN: '0', -// GAIN_SET: '0', -// INPUT_TYPE_SET: '0', -// BIAS_SET: '0', -// SRB2_SET: '0', -// SRB1_SET: '0' -// }; -// -// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits -// var sizeOfData = rawChannelBuffer.byteLength; -// -// var objIndex = 0; -// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data -// switch (objIndex) { -// case 0: -// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); -// break; -// default: -// break; -// } -// -// objIndex++; -// } -// resolve(channelSettingsObject); -// }); -// } - -function masterBufferMaker () { - var masterBuf = new Buffer(k.OBCIMasterBufferSize); - masterBuf.fill(0); - return { // Buffer used to store bytes in and read packets from - buffer: masterBuf, - positionRead: 0, - positionWrite: 0, - packetsIn: 0, - packetsRead: 0, - looseBytes: 0 - }; -} diff --git a/openBCIConstants.js b/openBCIConstants.js index 8b22505..990792b 100644 --- a/openBCIConstants.js +++ b/openBCIConstants.js @@ -203,6 +203,7 @@ const obciSimulatorFragmentationNone = 'none'; /** Possible Sample Rates */ const obciSampleRate125 = 125; +const obciSampleRate200 = 200; const obciSampleRate250 = 250; /** Max sample number */ @@ -232,9 +233,13 @@ const obciByteStart = 0xA0; const obciByteStop = 0xC0; /** Errors */ +const errorNobleAlreadyScanning = 'Scan already under way'; +const errorNobleNotAlreadyScanning = 'No scan started'; +const errorNobleNotInPoweredOnState = 'Please turn blue tooth on.'; const errorInvalidByteLength = 'Invalid Packet Byte Length'; const errorInvalidByteStart = 'Invalid Start Byte'; const errorInvalidByteStop = 'Invalid Stop Byte'; +const errorInvalidType = 'Invalid Type'; const errorTimeSyncIsNull = "'this.sync.curSyncObj' must not be null"; const errorTimeSyncNoComma = 'Missed the time sync sent confirmation. Try sync again'; const errorUndefinedOrNullInput = 'Undefined or Null Input'; @@ -337,6 +342,7 @@ const obciEmitterClose = 'close'; const obciEmitterDroppedPacket = 'droppedPacket'; const obciEmitterError = 'error'; const obciEmitterImpedanceArray = 'impedanceArray'; +const obciEmitterMessage = 'message'; const obciEmitterQuery = 'query'; const obciEmitterRawDataPacket = 'rawDataPacket'; const obciEmitterReady = 'ready'; @@ -749,6 +755,7 @@ module.exports = { }, /** Possible Sample Rates */ OBCISampleRate125: obciSampleRate125, + OBCISampleRate200: obciSampleRate200, OBCISampleRate250: obciSampleRate250, /** Max sample number */ OBCISampleNumberMax: obciSampleNumberMax, @@ -758,9 +765,13 @@ module.exports = { OBCIByteStart: obciByteStart, OBCIByteStop: obciByteStop, /** Errors */ + OBCIErrorNobleAlreadyScanning: errorNobleAlreadyScanning, + OBCIErrorNobleNotAlreadyScanning: errorNobleNotAlreadyScanning, + OBCIErrorNobleNotInPoweredOnState: errorNobleNotInPoweredOnState, OBCIErrorInvalidByteLength: errorInvalidByteLength, OBCIErrorInvalidByteStart: errorInvalidByteStart, OBCIErrorInvalidByteStop: errorInvalidByteStop, + OBCIErrorInvalidType: errorInvalidType, OBCIErrorTimeSyncIsNull: errorTimeSyncIsNull, OBCIErrorTimeSyncNoComma: errorTimeSyncNoComma, OBCIErrorUndefinedOrNullInput: errorUndefinedOrNullInput, @@ -896,6 +907,7 @@ module.exports = { OBCIEmitterDroppedPacket: obciEmitterDroppedPacket, OBCIEmitterError: obciEmitterError, OBCIEmitterImpedanceArray: obciEmitterImpedanceArray, + OBCIEmitterMessage: obciEmitterMessage, OBCIEmitterQuery: obciEmitterQuery, OBCIEmitterRawDataPacket: obciEmitterRawDataPacket, OBCIEmitterReady: obciEmitterReady, diff --git a/openBCICyton.js b/openBCICyton.js new file mode 100644 index 0000000..33ac7bf --- /dev/null +++ b/openBCICyton.js @@ -0,0 +1,2351 @@ +'use strict'; +const bufferEqual = require('buffer-equal'); +const EventEmitter = require('events').EventEmitter; +const math = require('mathjs'); +const util = require('util'); +const SerialPort = require('serialport'); +const openBCISample = require('./openBCISample'); +const k = openBCISample.k; +const openBCISimulator = require('./openBCISimulator'); +const Sntp = require('sntp'); +const openBCIUtils = require('./openBCIUtils'); +/** +* @description SDK for OpenBCI Board {@link www.openbci.com} +* @module 'openbci-sdk' +*/ + +const _options = { + boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], + baudRate: 115200, + simulate: false, + simulatorBoardFailure: false, + simulatorDaisyModuleAttached: false, + simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], + simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], + simulatorLatencyTime: 16, + simulatorBufferSize: 4096, + simulatorHasAccelerometer: true, + simulatorInternalClockDrift: 0, + simulatorInjectAlpha: true, + simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], + simulatorSampleRate: 250, + simulatorSerialPortFailure: false, + sntpTimeSync: false, + sntpTimeSyncHost: 'pool.ntp.org', + sntpTimeSyncPort: 123, + verbose: false, + debug: false +}; + +/** +* @description The initialization method to call first, before any other method. +* @param options (optional) - Board optional configurations. +* - `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if +* firmware on board has been previously configured. +* +* - `boardType` {String} - Specifies type of OpenBCI board. +* 3 Possible Boards: +* `default` - 8 Channel OpenBCI board (Default) +* `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. +* `ganglion` - 4 Channel board +* (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) +* +* - `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting +* `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) +* +* - `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on +* the board is not polling the RFduino on the dongle. (Default `false`) +* +* - `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. +* This is useful if you want to test how your application reacts to a user requesting 16 channels +* but there is no daisy module actually attached, or vice versa, where there is a daisy module +* attached and the user only wants to use 8 channels. (Default `false`) +* +* - `simulatorFirmwareVersion` {String} - Allows simulator to be started with firmware version 2 features +* 2 Possible Options: +* `v1` - Firmware Version 1 (Default) +* `v2` - Firmware Version 2 +* +* - `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which +* occurs commonly in real devices. It is recommended to test code with this enabled. +* 4 Possible Options: +* `none` - do not fragment packets; output complete chunks immediately when produced (Default) +* `random` - output random small chunks of data interspersed with full buffers +* `fullBuffers` - allow buffers to fill up until the latency timer has expired +* `oneByOne` - output each byte separately +* +* - `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers, + if `simulatorFragmentation` is specified. (Default `16`) +* +* - `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is +* specified. (Default `4096`) +* +* - `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) +* +* - `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) +* +* - `simulatorInjectLineNoise` {String} - Injects line noise on channels. +* 3 Possible Options: +* `60Hz` - 60Hz line noise (Default) [America] +* `50Hz` - 50Hz line noise [Europe] +* `none` - Do not inject line noise. +* +* - `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if +* `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that +* setting and this sample rate will be used. (Default is `250`) +* +* - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely +* due to a OpenBCI dongle not being plugged in. +* +* - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source +* of truth instead of local computer time. If you are running experiements on your local +* computer, keep this `false`. (Default `false`) +* +* - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). +* +* - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`) +* +* - `verbose` {Boolean} - Print out useful debugging events. (Default `false`) +* +* - `debug` {Boolean} - Print out a raw dump of bytes sent and received. (Default `false`) +* +* @constructor +* @author AJ Keller (@pushtheworldllc) +*/ +function Cyton (options) { + options = (typeof options !== 'function') && options || {}; + var opts = {}; + + /** Configuring Options */ + var o; + for (o in _options) { + var userOption = (o in options) ? o : o.toLowerCase(); + var userValue = options[userOption]; + delete options[userOption]; + + 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'); + + // Set to global options object + this.options = opts; + + /** Properties (keep alphabetical) */ + // Arrays + this.accelArray = [0, 0, 0]; // X, Y, Z + this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); + this.writeOutArray = []; + // Booleans + this._streaming = false; + // Buffers + this.buffer = null; + this.masterBuffer = masterBufferMaker(); + // Objects + this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType)); + this.impedanceTest = { + active: false, + isTestingPInput: false, + isTestingNInput: false, + onChannel: 0, + sampleNumber: 0, + continuousMode: false, + impedanceForChannel: 0 + }; + this.info = { + boardType: this.options.boardType, + sampleRate: k.OBCISampleRate125, + firmware: k.OBCIFirmwareV1, + numberOfChannels: k.OBCINumberOfChannelsDefault, + missedPackets: 0 + }; + if (this.options.boardType === k.OBCIBoardDefault) { + this.info.sampleRate = k.OBCISampleRate250; + } + + this._lowerChannelsSampleObject = null; + this.serial = null; + this.sync = { + curSyncObj: null, + eventEmitter: null, + objArray: [], + sntpActive: false, + timeOffsetMaster: 0, + timeOffsetAvg: 0, + timeOffsetArray: [] + }; + this.writer = null; + // Numbers + this.badPackets = 0; + this.curParsingMode = k.OBCIParsingReset; + this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); + this.previousSampleNumber = -1; + this.sampleCount = 0; + this.timeOfPacketArrival = 0; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; + // Strings + + // NTP + if (this.options.sntpTimeSync) { + // establishing ntp connection + this.sntpStart() + .catch(ignored => { + // try again once after a delay + return new Promise((resolve, reject) => { + setTimeout(resolve, 500); + }).then(() => this.sntpStart()); + }) + .then(() => { + if (this.options.verbose) console.log('SNTP: connected'); + }) + .catch(err => { + if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); + this.emit('error', err); + }); + } +} + +/** +* @description The essential precursor method to be called initially to establish a +* serial connection to the OpenBCI board. +* @param portName - a string that contains the port name of the Cyton. +* @returns {Promise} if the board was able to connect. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.connect = function (portName) { + return new Promise((resolve, reject) => { + if (this.isConnected()) return reject('already connected!'); + + /* istanbul ignore else */ + if (this.options.simulate || portName === k.OBCISimulatorPortName) { + this.options.simulate = true; + // If we are simulating, set portName to fake name + this.portName = k.OBCISimulatorPortName; + if (this.options.verbose) console.log('using faux board ' + portName); + this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { + accel: this.options.simulatorHasAccelerometer, + alpha: this.options.simulatorInjectAlpha, + boardFailure: this.options.simulatorBoardFailure, + daisy: this.options.simulatorDaisyModuleAttached, + drift: this.options.simulatorInternalClockDrift, + firmwareVersion: this.options.simulatorFirmwareVersion, + fragmentation: this.options.simulatorFragmentation, + latencyTime: this.options.simulatorLatencyTime, + bufferSize: this.options.simulatorBufferSize, + lineNoise: this.options.simulatorInjectLineNoise, + sampleRate: this.options.simulatorSampleRate, + serialPortFailure: this.options.simulatorSerialPortFailure, + verbose: this.options.verbose + }); + } else { + this.portName = portName; + if (this.options.verbose) console.log('using real board ' + portName); + this.serial = new SerialPort(portName, { + baudRate: this.options.baudRate + }, (err) => { + if (err) reject(err); + }); + } + + if (this.options.verbose) console.log('Serial port connected'); + + this.serial.on('data', data => { + this._processBytes(data); + }); + this.serial.once('open', () => { + if (this.options.verbose) console.log('Serial port open'); + new Promise(resolve => { + // TODO: document why this 300 ms delay is needed + setTimeout(resolve, this.options.simulate ? 50 : 300); + }).then(() => { + if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); + return this.write(k.OBCIStreamStop); + }).then(() => { + return new Promise(resolve => this.serial.flush(resolve)); + }).then(() => { + // TODO: document why this 250 ms delay is needed + return new Promise(resolve => setTimeout(resolve, 250)); + }).then(() => { + if (this.options.verbose) console.log('Sending soft reset'); + // TODO: this promise chain resolves early because + // A. some legacy code (in tests) sets the ready handler after this resolves + // and + // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented + // which is C. not implemented yet except in a manner such that replies occur in the write handler, + // resulting in the EOT arriving before this resolves + // Fix one or more of the above 3 situations, then move resolve() to the next block. + resolve(); + return this.softReset(); + }).then(() => { + if (this.options.verbose) console.log("Waiting for '$$$'"); + }); + }); + this.serial.once('close', () => { + if (this.options.verbose) console.log('Serial Port Closed'); + // 'close' is emitted in _disconnected() + this._disconnected('port closed'); + }); + this.serial.once('error', (err) => { + if (this.options.verbose) console.log('Serial Port Error'); + this.emit('error', err); + this._disconnected(err); + }); + }); +}; + +/** +* @description Called once when for any reason the serial port is no longer open. +* @private +*/ +Cyton.prototype._disconnected = function (err) { + this._streaming = false; + + clearTimeout(this.writer); + this.writer = null; + + this.serial.removeAllListeners(); + this.serial = null; + + this.emit('close'); + + while (this.writeOutArray.length > 0) { + var command = this.writeOutArray.pop(); + if (command.reject) command.reject(err); + } +}; + +/** +* @description Closes the serial port. Waits for stop streaming command to +* be sent if currently streaming. +* @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.disconnect = function () { + return Promise.resolve() + .then(() => { + if (this.isStreaming()) { + if (this.options.verbose) console.log('stop streaming'); + return this.streamStop(); + } + }) + .then(() => { + if (!this.isConnected()) { + return Promise.reject('no board connected'); + } else { + return new Promise((resolve, reject) => { + // serial emitting 'close' will call _disconnected + this.serial.close(() => { + resolve(); + }); + }); + } + }); +}; + +/** +* @description Checks if the driver is connected to a board. +* @returns {boolean} - True if connected. +*/ +Cyton.prototype.isConnected = function () { + if (!this.serial) return false; + return this.serial.isOpen(); +}; + +/** +* @description Checks if the board is currently sending samples. +* @returns {boolean} - True if streaming. +*/ +Cyton.prototype.isStreaming = function () { + return this._streaming; +}; + +/** +* @description Sends a start streaming command to the board. +* @returns {Promise} indicating if the signal was able to be sent. +* Note: You must have successfully connected to an OpenBCI board using the connect +* method. Just because the signal was able to be sent to the board, does not +* mean the board will start streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.streamStart = function () { + return new Promise((resolve, reject) => { + if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); + this._streaming = true; + this._reset_ABANDONED(); // framework is incomplete but looks useful + this.write(k.OBCIStreamStart).then(resolve, reject); + }); +}; + +/** +* @description Sends a stop streaming command to the board. +* @returns {Promise} indicating if the signal was able to be sent. +* Note: You must have successfully connected to an OpenBCI board using the connect +* method. Just because the signal was able to be sent to the board, does not +* mean the board stopped streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.streamStop = function () { + return new Promise((resolve, reject) => { + if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); + this._streaming = false; + this.write(k.OBCIStreamStop).then(resolve, reject); + }); +}; + +/** +* @description To start simulating an open bci board +* Note: Must be called after the constructor +* @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.simulatorEnable = function () { + return new Promise((resolve, reject) => { + if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? + if (this.isConnected()) { + this.disconnect() // disconnect first + .then(() => { + this.options.simulate = true; + resolve(); + }) + .catch(err => reject(err)); + } else { + this.options.simulate = true; + resolve(); + } + }); +}; + +/** +* @description To stop simulating an open bci board +* Note: Must be called after the constructor +* @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.simulatorDisable = function () { + return new Promise((resolve, reject) => { + if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? + if (this.isConnected()) { + this.disconnect() + .then(() => { + this.options.simulate = false; + resolve(); + }) + .catch(err => reject(err)); + } else { + this.options.simulate = false; + resolve(); + } + }); +}; + +/** +* @description To be able to easily write to the board but ensure that we never send commands +* with less than a 10ms spacing between sends in early version boards. This uses +* an array and shifts off the entries until there are none left. +* @param dataToWrite - Either a single character or an Array of characters +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.write = function (dataToWrite) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) { + reject('not connected'); + } else { + if (Array.isArray(dataToWrite)) { // Got an input array + var len = dataToWrite.length; + for (var i = 0; i < len; i++) { + this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); + } + this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; + } else { + this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); + } + + if (!this.writer) { // there is no writer started + var writerFunction = () => { + if (this.writeOutArray.length === 0) { + this.writer = null; + return; + } + + var command = this.writeOutArray.shift(); + var promise = this._writeAndDrain(command.cmd); + + promise.then(() => { + this.writer = setTimeout(writerFunction, this.writeOutDelay); + }, () => { + // write failed but more commands may be pending that need a result + writerFunction(); + }); + + if (command.reject) { + promise.catch(err => { + if (this.options.verbose) console.log('write failure: ' + err); + command.reject(err); + }); + } + if (command.resolve) promise.then(command.resolve); + }; + this.writer = setTimeout(writerFunction, this.writeOutDelay); + } + } + }); +}; + +/** +* @description Should be used to send data to the board +* @param data {Buffer} - The data to write out +* @returns {Promise} if signal was able to be sent +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._writeAndDrain = function (data) { + if (this.options.debug) openBCIUtils.debugBytes('>>>', data); + + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Serial port not open'); + this.serial.write(data, (error) => { + if (error) { + console.log('Error [writeAndDrain]: ' + error); + reject(error); + } else { + this.serial.drain(function () { + resolve(); + }); + } + }); + }); +}; + +/** +* @description Automatically find an OpenBCI board. +* Note: This method is used for convenience and should be used when trying to +* connect to a board. If you find a case (i.e. a platform (linux, +* windows...) that this does not work, please open an issue and +* we will add support! +* @returns {Promise} - Fulfilled with portName, rejected when can't find the board. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.autoFindOpenBCIBoard = function () { + var serialPatterns = [ + { // mac + comName: /usbserial-D/ + }, + { // linux + comName: /^\/dev\/ttyUSB/, + manufacturer: /^FTDI$/, + serialNumber: /^FTDI_FT231X_USB_UART/, + vendorId: /^0x0403$/, + productId: /^0x6015$/ + } + ]; + return new Promise((resolve, reject) => { + /* istanbul ignore else */ + if (this.options.simulate) { + this.portName = k.OBCISimulatorPortName; + if (this.options.verbose) console.log('auto found sim board'); + resolve(k.OBCISimulatorPortName); + } else { + SerialPort.list((err, ports) => { + if (err) { + if (this.options.verbose) console.log('serial port err'); + reject(err); + } + // This is one big if statement + if (ports.some(port => { + return serialPatterns.some(patterns => { + for (var attribute in patterns) { + if (!String(port[attribute]).match(patterns[attribute])) { + return false; + } + } + this.portName = port.comName; + return true; + }); + })) { + if (this.options.verbose) console.log('auto found board'); + resolve(this.portName); + } else { + if (this.options.verbose) console.log('could not find board'); + reject('Could not auto find board'); + } + }); + } + }); +}; + +/** +* @description Convenience method to determine if you can use firmware v2.x.x +* capabilities. +* @returns {boolean} - True if using firmware version 2 or greater. Should +* be called after a `.softReset()` because we can parse the output of that +* to determine if we are using firmware version 2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.usingVersionTwoFirmware = function () { + if (this.options.simulate) { + return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; + } else { + return this.info.firmware === k.OBCIFirmwareV2; + } +}; + +/** +* @description Used to set the system radio channel number. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. +* @since 1.0.0 +* @returns {Promise} - Resolves with the new channel number, rejects with err. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelSet = function (channelNumber) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); + if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); + if (!k.isNumber(channelNumber)) return reject('Must input type Number'); + if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); + if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); + } else { + reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); + }); +}; + +/** +* @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if +* your dongle and board are not on the right channel and bring down your radio system if you take your +* dongle and board are not on the same channel. Use with caution! The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. +* @since 1.0.0 +* @returns {Promise} - Resolves with the new channel number, rejects with err. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelSetHostOverride = function (channelNumber) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); + if (!k.isNumber(channelNumber)) return reject('Must input type Number'); + if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); + if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(`${data.toString()}`); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); + } else { + reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); + }); +}; + +/** +* @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve +* an Object. See `returns` below. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in +* the condition that there system is experiencing board communications failure. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioChannelGet = function () { + // The function to run on timeout + var badCommsTimeout; + + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the radio while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 500); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + if (openBCISample.isSuccessInBuffer(data)) { + resolve({ + channelNumber: data[data.length - 4], + data: data + }); + } else { + reject(`Error [radioChannelGet]: ${data.toString()}`); + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); + }); +}; + +/** +* @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not +* connected to the serial port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve +* the poll time when fulfilled. It's important to note that if the board is not on, this function will always +* be rejected with a failure message. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves with the poll time, rejects with an error message. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioPollTimeGet = function () { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + var pollTime = data[data.length - 4]; + resolve(pollTime); + } else { + reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); + }); +}; + +/** +* @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the +* Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this +* sets the interval at which the Device polls the Host for new information. Further the function should reject +* if currently streaming. Lastly and more important, if the board is not running the new firmware then this +* functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this +* function should resolve. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 +* @since 1.0.0 +* @returns {Promise} - Resolves with new poll time, rejects with error message. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioPollTimeSet = function (pollTime) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); + if (!k.isNumber(pollTime)) return reject('Must input type Number'); + if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); + if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(data[data.length - 4]); // Ditch the eot $$$ + } else { + reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); + }); +}; + +/** +* @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the +* Host and the Board is the Device. Only the Device can initiate a communication between the two entities. +* There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then +* all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial +* data is sent from the Host to the Serial driver. The rate can either be set to default or fast. +* Further the function should reject if currently streaming. Lastly and more important, if the board is not +* running the new firmware then this functionality does not exist and thus this method will reject. +* If the board is using firmware 2+ then this function should resolve the new baud rate after closing the +* current serial port and reopening one. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) +* @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioBaudRateSet = function (speed) { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (!k.isString(speed)) return reject('Must input type String'); + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + if (this.options.verbose) console.log(data.toString()); + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + var eotBuf = new Buffer('$$$'); + var newBaudRateBuf; + for (var i = data.length; i > 3; i--) { + if (bufferEqual(data.slice(i - 3, i), eotBuf)) { + newBaudRateBuf = data.slice(i - 9, i - 3); + break; + } + } + var newBaudRateNum = Number(newBaudRateBuf.toString()); + if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { + return reject('Error parse mismatch, restart your system!'); + } + if (!this.isConnected()) { + reject('Lost connection to device during baud set'); + } else if (openBCISample.isSuccessInBuffer(data)) { + // Change the sample rate here + if (this.options.simulate === false) { + this.serial.update({baudRate: newBaudRateNum}, err => { + if (err) return reject(err); + else resolve(newBaudRateNum); + }); + } else { + resolve(newBaudRateNum); + } + } else { + reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + if (speed === k.OBCIRadioBaudRateFastStr) { + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); + } else { + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); + } + }); +}; + +/** +* @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are +* in fact ready to start trying to connect and such. The function will reject if not connected to the serial +* port of the dongle. Further the function should reject if currently streaming. +* Lastly and more important, if the board is not running the new firmware then this functionality does not +* exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the +* same channel and powered, then this will resolve true. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.radioSystemStatusGet = function () { + var badCommsTimeout; + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); + if (this.isStreaming()) return reject("Don't change the poll time while streaming"); + if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); + + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + badCommsTimeout = setTimeout(() => { + reject('Please make sure your dongle is plugged in and using firmware v2'); + }, 1000); + + // Subscribe to the EOT event + this.once('eot', data => { + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + if (this.options.verbose) console.log(data.toString()); + + if (openBCISample.isSuccessInBuffer(data)) { + resolve(true); + } else { + resolve(false); + } + }); + + this.curParsingMode = k.OBCIParsingEOT; + + // Send the radio channel query command + this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); + }); +}; + +/** +* @description List available ports so the user can choose a device when not +* automatically found. +* Note: This method is used for convenience essentially just wrapping up +* serial port. +* @author Andy Heusser (@andyh616) +* @returns {Promise} - On fulfill will contain an array of Serial ports to use. +*/ +Cyton.prototype.listPorts = function () { + return new Promise((resolve, reject) => { + SerialPort.list((err, ports) => { + if (err) reject(err); + else { + ports.push({ + comName: k.OBCISimulatorPortName, + manufacturer: '', + serialNumber: '', + pnpId: '', + locationId: '', + vendorId: '', + productId: '' + }); + resolve(ports); + } + }); + }); +}; + +/** +* @description Sends a soft reset command to the board +* @returns {Promise} +* Note: The softReset command MUST be sent to the board before you can start +* streaming. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.softReset = function () { + this.curParsingMode = k.OBCIParsingReset; + return this.write(k.OBCIMiscSoftReset); +}; + +// /** +// * @description To get the specified channelSettings register data from printRegisterSettings call +// * @param channelNumber - a number +// * @returns {Promise.|*} +// * @author AJ Keller (@pushtheworldllc) +// */ +// // TODO: REDO THIS FUNCTION +// Cyton.prototype.getSettingsForChannel = function (channelNumber) { +// return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { +// // this.searchingBuf = newSearchingBuffer +// return this.printRegisterSettings(); +// }); +// }; + +/** +* @description To print out the register settings to the console +* @returns {Promise.|*} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.printRegisterSettings = function () { + return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { + this.curParsingMode = k.OBCIParsingEOT; + }); +}; + +/** +* @description Send a command to the board to turn a specified channel off +* @param channelNumber +* @returns {Promise.} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelOff = function (channelNumber) { + return k.commandChannelOff(channelNumber).then((charCommand) => { + // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) + return this.write(charCommand); + }); +}; + +/** +* @description Send a command to the board to turn a specified channel on +* @param channelNumber +* @returns {Promise.|*} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelOn = function (channelNumber) { + return k.commandChannelOn(channelNumber).then((charCommand) => { + // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) + return this.write(charCommand); + }); +}; + +/** +* @description To send a channel setting command to the board +* @param channelNumber - Number (1-16) +* @param powerDown - Bool (true -> OFF, false -> ON (default)) +* turns the channel on or off +* @param gain - Number (1,2,4,6,8,12,24(default)) +* sets the gain for the channel +* @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) +* selects the ADC channel input source +* @param bias - Bool (true -> Include in bias (default), false -> remove from bias) +* selects to include the channel input in bias generation +* @param srb2 - Bool (true -> Connect this input to SRB2 (default), +* false -> Disconnect this input from SRB2) +* Select to connect (true) this channel's P input to the SRB2 pin. This closes +* a switch between P input and SRB2 for the given channel, and allows the +* P input to also remain connected to the ADC. +* @param srb1 - Bool (true -> connect all N inputs to SRB1, +* false -> Disconnect all N inputs from SRB1 (default)) +* Select to connect (true) all channels' N inputs to SRB1. This effects all pins, +* and disconnects all N inputs from the ADC. +* @returns {Promise} resolves if sent, rejects on bad input or no board +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { + var arrayOfCommands = []; + return new Promise((resolve, reject) => { + k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) + .then((val) => { + arrayOfCommands = val.commandArray; + this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; + return this.write(arrayOfCommands); + }).then(resolve, reject); + }); +}; + +/** +* @description Apply the internal test signal to all channels +* @param signal - A string indicating which test signal to apply +* - `dc` +* - Connect to DC signal +* - `ground` +* - Connect to internal GND (VDD - VSS) +* - `pulse1xFast` +* - Connect to test signal 1x Amplitude, fast pulse +* - `pulse1xSlow` +* - Connect to test signal 1x Amplitude, slow pulse +* - `pulse2xFast` +* - Connect to test signal 2x Amplitude, fast pulse +* - `pulse2xFast` +* - Connect to test signal 2x Amplitude, slow pulse +* - `none` +* - Reset to default +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.testSignal = function (signal) { + return new Promise((resolve, reject) => { + k.getTestSignalCommand(signal) + .then(command => { + return this.write(command); + }) + .then(() => resolve()) + .catch(err => reject(err)); + }); +}; + +/** +* @description - Sends command to turn on impedances for all channels and continuously calculate their impedances +* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestContinuousStart = function () { + return new Promise((resolve, reject) => { + if (this.impedanceTest.active) return reject('Error: test already active'); + if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); + + this.impedanceTest.active = true; + this.impedanceTest.continuousMode = true; + + var chain = Promise.resolve(); + for (var i = 0; i < this.numberOfChannels(); i++) { + chain = chain + .then(() => k.getImpedanceSetter(i + 1, false, true)) + .then((commandsArray) => this.write(commandsArray)); + } + chain.then(resolve, reject); + }); +}; + +/** +* @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances +* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestContinuousStop = function () { + return new Promise((resolve, reject) => { + if (!this.impedanceTest.active) return reject('Error: no test active'); + if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); + + this.impedanceTest.active = false; + this.impedanceTest.continuousMode = false; + + var chain = Promise.resolve(); + for (var i = 0; i < this.numberOfChannels(); i++) { + chain = chain + .then(() => k.getImpedanceSetter(i + 1, false, false)) + .then((commandsArray) => this.write(commandsArray)); + } + chain.then(resolve, reject); + }); +}; + +/** +* @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a +* little while to actually run (<8 seconds)! +* @returns {Promise} - Resovles when complete testing all the channels. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestAllChannels = function () { + var upperLimit = k.OBCINumberOfChannelsDefault; + + /* istanbul ignore if */ + if (this.options.daisy) { + upperLimit = k.OBCINumberOfChannelsDaisy; + } + + if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + + // Recursive function call + var completeChannelImpedanceTest = (channelNumber) => { + return new Promise((resolve, reject) => { + if (channelNumber > upperLimit) { // Base case! + this.emit('impedanceArray', this.impedanceArray); + this.impedanceTest.onChannel = 0; + resolve(); + } else { + if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); + this.impedanceTestChannel(channelNumber) + .then(() => { + resolve(completeChannelImpedanceTest(channelNumber + 1)); + /* istanbul ignore next */ + }).catch(err => reject(err)); + } + }); + }; + + return completeChannelImpedanceTest(1); +}; + +/** +* @description To test specific input configurations of channels! +* @param arrayOfChannels - The array of configurations where: +* 'p' or 'P' is only test P input +* 'n' or 'N' is only test N input +* 'b' or 'B' is test both inputs (takes 66% longer to run) +* '-' to ignore channel +* EXAMPLE: +* For 8 channel board: ['-','N','n','p','P','-','b','b'] +* (Note: it doesn't matter if capitalized or not) +* @returns {Promise} - Fulfilled with a loaded impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannels = function (arrayOfChannels) { + if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); + if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + // Check proper length of array + if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); + + // Recursive function call + var completeChannelImpedanceTest = (channelNumber) => { + return new Promise((resolve, reject) => { + if (channelNumber > arrayOfChannels.length) { // Base case! + this.emit('impedanceArray', this.impedanceArray); + this.impedanceTest.onChannel = 0; + resolve(); + } else { + if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); + + var testCommand = arrayOfChannels[channelNumber - 1]; + + if (testCommand === 'p' || testCommand === 'P') { + this.impedanceTestChannelInputP(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else if (testCommand === 'n' || testCommand === 'N') { + this.impedanceTestChannelInputN(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else if (testCommand === 'b' || testCommand === 'B') { + this.impedanceTestChannel(channelNumber).then(() => { + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + }).catch(err => reject(err)); + } else { // skip ('-') condition + completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); + } + } + }); + }; + return completeChannelImpedanceTest(1); +}; + +/** +* @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannel = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. + }) + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/** +* @description Run impedance test on a single channel, applying the test signal only to P input. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannelInputP = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/** +* @description Run impedance test on a single channel, applying the test signal to N input. +* @param channelNumber - A Number, specifies which channel you want to test. +* @returns {Promise} - Fulfilled with a single channel impedance object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.impedanceTestChannelInputN = function (channelNumber) { + this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + return new Promise((resolve, reject) => { + this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. + .then(channelNumber => { + return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number + }) + .then(channelNumber => { + return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel + }) + .then(channelNumber => { + return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. + }) + .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) + .catch(err => reject(err)); + }); +}; + +/* istanbul ignore next */ +/** +* @description To apply the impedance test signal to an input for any given channel +* @param channelNumber - Number - The channel you want to test. +* @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. +* @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. +* @returns {Promise} - With Number value of channel number +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected'); + + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tSending command to apply test signal to P input.'); + } else if (!pInput && nInput) { + console.log('\tSending command to apply test signal to N input.'); + } else if (pInput && nInput) { + console.log('\tSending command to apply test signal to P and N inputs.'); + } else { + console.log('\tSending command to stop applying test signal to both P and N inputs.'); + } + } + + if (!pInput && !nInput) { + this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort + } else { + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong + } + if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); + // Get impedance settings to send the board + k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { + return this.write(commandsArray); + }).then(() => { + /** + * If either pInput or nInput are true then we should start calculating impedance. Setting + * this.impedanceTest.active to true here allows us to route every sample for an impedance + * calculation instead of the normal sample output. + */ + if (pInput || nInput) this.impedanceTest.active = true; + resolve(channelNumber); + }, (err) => { + reject(err); + }); + }); +}; + +/** +* @description Calculates the impedance for a specified channel for a set time +* @param channelNumber - A Number, the channel number you want to test. +* @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. +* @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. +* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tCalculating impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tCalculating impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tCalculating impedance for P and N input.'); + } else { + console.log('\tNot calculating impedance for either P and N input.'); + } + } + return new Promise((resolve, reject) => { + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); + if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); + if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + this.impedanceTest.onChannel = channelNumber; + this.impedanceTest.sampleNumber = 0; // Reset the sample number + this.impedanceTest.isTestingPInput = pInput; + this.impedanceTest.isTestingNInput = nInput; + // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) + // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) + setTimeout(() => { // Calculate for 250ms + this.impedanceTest.onChannel = 0; + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tDone calculating impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tDone calculating impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tDone calculating impedance for P and N input.'); + } else { + console.log('\tNot calculating impedance for either P and N input.'); + } + } + if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; + if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; + resolve(channelNumber); + }, 400); + }); +}; + +/** +* @description Calculates average and gets textual value of impedance for a specified channel +* @param channelNumber - A Number, the channel number you want to finalize. +* @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. +* @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. +* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { + /* istanbul ignore if */ + if (this.options.verbose) { + if (pInput && !nInput) { + console.log('\tFinalizing impedance for P input.'); + } else if (!pInput && nInput) { + console.log('\tFinalizing impedance for N input.'); + } else if (pInput && nInput) { + console.log('\tFinalizing impedance for P and N input.'); + } else { + console.log('\tNot Finalizing impedance for either P and N input.'); + } + } + return new Promise((resolve, reject) => { + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); + if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); + if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + + if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); + if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); + + setTimeout(() => { + resolve(channelNumber); + }, 50); // Introduce a delay to allow for extra time in case of back to back tests + }); +}; + +/** +* @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request +* response from the board. +* @param recordingDuration {String} - The duration you want to log SD information for. Limited to: +* '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' +* @returns {Promise} - Resolves when the command has been written. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sdStart = function (recordingDuration) { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + k.sdSettingForString(recordingDuration) + .then(command => { + // If we are not streaming, then expect a confirmation message back from the board + if (!this.isStreaming()) { + this.curParsingMode = k.OBCIParsingEOT; + } + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + this.write(command).then(resolve, reject); + }) + .catch(err => reject(err)); + }); +}; + +/** +* @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted +* with request response from the board. +* @returns {Promise} - Resolves when written +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sdStop = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + // If we are not streaming, then expect a confirmation message back from the board + if (!this.isStreaming()) { + this.curParsingMode = k.OBCIParsingEOT; + } + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + this.write(k.OBCISDLogStop).then(resolve, reject); + }); +}; + +/** +* @description Get the the current sample rate is. +* @returns {Number} The sample rate +* Note: This is dependent on if you configured the board correctly on setup options +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sampleRate = function () { + if (this.options.simulate) { + return this.options.simulatorSampleRate; + } else { + if (this.info) { + return this.info.sampleRate; + } else { + switch (this.boardType) { + case k.OBCIBoardDaisy: + return k.OBCISampleRate125; + case k.OBCIBoardDefault: + default: + return k.OBCISampleRate250; + } + } + } +}; + +/** +* @description This function is used as a convenience method to determine how many +* channels the current board is using. +* @returns {Number} A number +* Note: This is dependent on if you configured the board correctly on setup options +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.numberOfChannels = function () { + if (this.info) { + return this.info.numberOfChannels; + } else { + switch (this.boardType) { + case k.OBCIBoardDaisy: + return k.OBCINumberOfChannelsDaisy; + case k.OBCIBoardDefault: + default: + return k.OBCINumberOfChannelsDefault; + } + } +}; + +/** +* @description Send the command to tell the board to start the syncing protocol. Must be connected, +* streaming and using at least version 2.0.0 firmware. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.0.0 +* @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.syncClocks = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); + if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj.timeSyncSent = this.time(); + this.curParsingMode = k.OBCIParsingTimeSyncSent; + this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); + }); +}; + +/** +* @description Send the command to tell the board to start the syncing protocol. Must be connected, +* streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs +* don't overlap. +* **Note**: This functionality requires OpenBCI Firmware Version 2.0 +* @since 1.1.0 +* @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.syncClocksFull = function () { + return new Promise((resolve, reject) => { + if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); + if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + var timeout = setTimeout(() => { + return reject('syncClocksFull timeout after 500ms with no sync'); + }, 500); // Should not take more than 1s to sync up + this.sync.eventEmitter = syncObj => { + clearTimeout(timeout); + return resolve(syncObj); + }; + this.once('synced', this.sync.eventEmitter); + this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj.timeSyncSent = this.time(); + this.curParsingMode = k.OBCIParsingTimeSyncSent; + this._writeAndDrain(k.OBCISyncTimeSet) + .catch(err => { + clearTimeout(timeout); + return reject(err); + }); + }); +}; + +/** +* @description Consider the '_processBytes' method to be the work horse of this +* entire framework. This method gets called any time there is new +* data coming in on the serial port. If you are familiar with the +* 'serialport' package, then every time data is emitted, this function +* gets sent the input data. The data comes in very fragmented, sometimes +* we get half of a packet, and sometimes we get 3 and 3/4 packets, so +* we will need to store what we don't read for next time. +* @param data - a buffer of unknown size +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processBytes = function (data) { + if (this.options.debug) openBCIUtils.debugBytes(this.curParsingMode + '<<', data); + + // Concat old buffer + var oldDataBuffer = null; + if (this.buffer) { + oldDataBuffer = this.buffer; + data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); + } + + switch (this.curParsingMode) { + case k.OBCIParsingEOT: + if (openBCISample.doesBufferHaveEOT(data)) { + this.curParsingMode = k.OBCIParsingNormal; + this.emit('eot', data); + this.buffer = openBCISample.stripToEOTBuffer(data); + } else { + this.buffer = data; + } + break; + case k.OBCIParsingReset: + // Does the buffer have an EOT in it? + if (openBCISample.doesBufferHaveEOT(data)) { + this._processParseBufferForReset(data); + this.curParsingMode = k.OBCIParsingNormal; + this.emit('ready'); + this.buffer = openBCISample.stripToEOTBuffer(data); + } else { + this.buffer = data; + } + break; + case k.OBCIParsingTimeSyncSent: + // If there is only one match + if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { + if (this.options.verbose) console.log(`Found Time Sync Sent`); + this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); + this.curParsingMode = k.OBCIParsingNormal; + } + this.buffer = this._processDataBuffer(data); + break; + case k.OBCIParsingNormal: + default: + this.buffer = this._processDataBuffer(data); + break; + } + + if (this.buffer && oldDataBuffer) { + if (bufferEqual(this.buffer, oldDataBuffer)) { + this.buffer = null; + } + } +}; + +/** +* @description Used to extract samples out of a buffer of unknown length +* @param dataBuffer {Buffer} - A buffer to parse for samples +* @returns {Buffer} - Any data that was not pulled out of the buffer +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processDataBuffer = function (dataBuffer) { + if (!dataBuffer) return null; + var bytesToParse = dataBuffer.length; + // Exit if we have a buffer with less data than a packet + if (bytesToParse < k.OBCIPacketSize) return dataBuffer; + + var parsePosition = 0; + // Begin parseing + while (parsePosition <= bytesToParse - k.OBCIPacketSize) { + // Is the current byte a head byte that looks like 0xA0 + if (dataBuffer[parsePosition] === k.OBCIByteStart) { + // Now that we know the first is a head byte, let's see if the last one is a + // tail byte 0xCx where x is the set of numbers from 0-F (hex) + if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { + /** We just qualified a raw packet */ + // This could be a time set packet! + this.timeOfPacketArrival = this.time(); + // Grab the raw packet, make a copy of it. + var rawPacket; + if (k.getVersionNumber(process.version) >= 6) { + // From introduced in node version 6.x.x + rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); + } else { + rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); + } + + // Emit that buffer + this.emit('rawDataPacket', rawPacket); + // Submit the packet for processing + this._processQualifiedPacket(rawPacket); + // Overwrite the dataBuffer with a new buffer + var tempBuf; + if (parsePosition > 0) { + tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); + } else { + tempBuf = dataBuffer.slice(k.OBCIPacketSize); + } + if (tempBuf.length === 0) { + dataBuffer = null; + } else { + if (k.getVersionNumber(process.version) >= 6) { + dataBuffer = Buffer.from(tempBuf); + } else { + dataBuffer = new Buffer(tempBuf); + } + } + // Move the parse position up one packet + parsePosition = -1; + bytesToParse -= k.OBCIPacketSize; + } + } + parsePosition++; + } + + return dataBuffer; +}; + +/** + * @description Alters the global info object by parseing an incoming soft reset key + * @param dataBuffer {Buffer} - The soft reset data buffer + * @returns {Buffer} - If there is data left in the buffer, just it will be returned. + * @private + * @author AJ Keller (@pushtheworldllc) + */ +Cyton.prototype._processParseBufferForReset = function (dataBuffer) { + if (openBCISample.countADSPresent(dataBuffer) === 2) { + this.info.boardType = k.OBCIBoardDaisy; + this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; + this.info.sampleRate = k.OBCISampleRate125; + } else { + this.info.boardType = k.OBCIBoardDefault; + this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; + this.info.sampleRate = k.OBCISampleRate250; + } + + if (openBCISample.findV2Firmware(dataBuffer)) { + this.info.firmware = k.OBCIFirmwareV2; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + } else { + this.info.firmware = k.OBCIFirmwareV1; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; + } +}; + +/** +* @description Used to route qualified packets to their proper parsers +* @param rawDataPacketBuffer +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { + if (!rawDataPacketBuffer) return; + if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; + var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); + if (missedPacketArray) { + this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); + } + this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; + var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); + switch (packetType) { + case k.OBCIStreamPacketStandardAccel: + this._processPacketStandardAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketStandardRawAux: + this._processPacketStandardRawAux(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketUserDefinedType: + // Do nothing for User Defined Packets + break; + case k.OBCIStreamPacketAccelTimeSyncSet: + // Don't waste any time! + this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); + this._processPacketTimeSyncedAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketAccelTimeSynced: + this._processPacketTimeSyncedAccel(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketRawAuxTimeSyncSet: + this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); + this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); + break; + case k.OBCIStreamPacketRawAuxTimeSynced: + this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); + break; + default: + // Don't do anything if the packet is not defined + break; + } +}; + +/** +* @description A method used to compute impedances. +* @param sampleObject - A sample object that follows the normal standards. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processImpedanceTest = function (sampleObject) { + var impedanceArray; + if (this.impedanceTest.continuousMode) { + // console.log('running in continuous mode...') + // openBCISample.debugPrettyPrint(sampleObject) + impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); + if (impedanceArray) { + this.emit('impedanceArray', impedanceArray); + } + } else if (this.impedanceTest.onChannel !== 0) { + // Only calculate impedance for one channel + impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); + if (impedanceArray) { + this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; + } + } +}; + +/** +* @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketStandardAccel = function (rawPacket) { + try { + let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketStandardRawAux = function (rawPacket) { + try { + let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp +* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket +* @param timeOfPacketArrival {Number} - The time the packet arrived. +* @private +* @returns {Object} - A time sync object. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { + /** + * Helper function for creating a bad sync object + * @param err {object} - Can be any object + * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} + */ + var getBadObject = (err) => { + var badObject = openBCISample.newSyncObject(); + badObject.timeOffsetMaster = this.sync.timeOffsetMaster; + // Create an error + badObject.error = err; + return badObject; + }; + + var syncObj = {}; + + if (this.sync.curSyncObj === null) { + if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); + // Set the output to bad object + syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); + // Missed comma + } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { + if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); + // Set the output to bad object + syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); + } else { + try { + this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; + if (this.options.verbose) console.log('Got time set packet from the board'); + let boardTime = openBCISample.getFromTimePacketTime(rawPacket); + this.sync.curSyncObj.boardTime = boardTime; + // if (this.options.verbose) { + // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) + // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) + // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) + // } + + // Calculate the time between sending the `<` to getting the set packet, call this the round trip length + this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; + if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); + + // If the sync sent conf and set packet arrive in different serial flushes + // ------------------------------------------ + // | | timeTransmission | < GOOD :) + // ------------------------------------------ + // ^ ^ ^ + // s s s + // e e e + // n n t packet + // t t confirmation + // + // Assume it's good... + this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); + + // If the conf and the set packet arrive in the same serial flush we have big problem! + // ------------------------------------------ + // | | | < BAD :( + // ------------------------------------------ + // ^ ^ ^ + // s s s + // e e e + // n n t packet + // t t confirmation + if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { + // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point + this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); + if (this.options.verbose) console.log(`Had to correct transmission time`); + this.sync.curSyncObj.correctedTransmissionTime = true; + } + + // Calculate the offset #finally + this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; + if (this.options.verbose) { + console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); + console.log(`Board time: ${boardTime}`); + } + + // Add to array + if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { + // Shift the oldest one out of the array + this.sync.timeOffsetArray.shift(); + // Push the new value into the array + this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); + } else { + // Push the new value into the array + this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); + } + + // Calculate the master time offset that we use averaging to compute + if (this.sync.timeOffsetArray.length > 1) { + var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); + this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); + } else { + this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; + } + + this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; + + if (this.options.verbose) { + console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); + } + + // Set the valid object to true + this.sync.curSyncObj.valid = true; + + // Make a byte by byte copy of event + syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); + + // Save obj to the global array + this.sync.objArray.push(syncObj); + } catch (err) { + // Log if verbose. + if (this.options.verbose) console.log(err.message); + syncObj = getBadObject(err); + } + } + // Fix the curParsingMode back to normal + this.curParsingMode = k.OBCIParsingNormal; + // Emit synced object + this.emit(k.OBCIEmitterSynced, syncObj); + // Set global to null + this.sync.curSyncObj = null; + // Return the object + return syncObj; +}; + +/** +* @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets +* an accelerometer value. +* @param rawPacket - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { + // if (this.sync.active === false) console.log('Need to sync with board...') + try { + let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that +* shall be emitted as a raw buffer and not scaled. +* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { + // if (this.sync.active === false) console.log('Need to sync with board...') + try { + let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); + sample.rawPacket = rawPacket; + this._finalizeNewSample(sample); + } catch (err) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + if (this.options.verbose) console.log(err); + } +}; + +/** +* @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are +* being tested. +* @param sampleObject {Object} - A sample object that follows the normal standards. +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._finalizeNewSample = function (sampleObject) { + sampleObject._count = this.sampleCount++; + if (this.impedanceTest.active) { + this._processImpedanceTest(sampleObject); + } else { + // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper + // channels (9-16) come in packets with even sample numbers + if (this.info.boardType === k.OBCIBoardDaisy) { + // Send the sample for downstream sample compaction + this._finalizeNewSampleForDaisy(sampleObject); + } else { + this.emit(k.OBCIEmitterSample, sampleObject); + } + } +}; + +/** +* @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber +* sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a +* sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be +* emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the +* missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. +* @param sampleObject {Object} - The sample object to finalize +* @private +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { + if (openBCISample.isOdd(sampleObject.sampleNumber)) { + // Check for the skipped packet condition + if (this._lowerChannelsSampleObject) { + // The last packet was odd... missed the even packet + this.info.missedPackets++; + } + this._lowerChannelsSampleObject = sampleObject; + } else { + // Make sure there is an odd packet waiting to get merged with this packet + if (this._lowerChannelsSampleObject) { + // Merge these two samples + var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); + // Set the _lowerChannelsSampleObject object to null + this._lowerChannelsSampleObject = null; + // Emite the new merged sample + this.emit('sample', mergedSample); + } else { + // Missed the odd packet, i.e. two evens in a row + this.info.missedPackets++; + } + } +}; + +/** +* @description Reset the master buffer and reset the number of bad packets. +* @author AJ Keller (@pushtheworldllc) +*/ +// TODO: nothing is using these constructs, but they look like good constructs. See contents of masterBufferMaker() +Cyton.prototype._reset_ABANDONED = function () { + this.masterBuffer = masterBufferMaker(); + this.badPackets = 0; +}; + +/** +* @description Stateful method for querying the current offset only when the last +* one is too old. (defaults to daily) +* @returns {Promise} A promise with the time offset +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sntpGetOffset = function () { + this.options.sntpTimeSync = true; + + if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); + + return new Promise((resolve, reject) => { + Sntp.offset({ + host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org + port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) + clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes + timeout: 500 // Assume packet has been lost after 500 milliseconds + }, function (err, offset) { + if (err) reject(err); + else resolve(offset); + }); + }); +}; + +/** +* @description Allows users to utilize all features of sntp if they want to... +*/ +Cyton.prototype.sntp = Sntp; + +/** +* @description This gets the time plus offset +* @private +*/ +Cyton.prototype._sntpNow = Sntp.now; + +/** +* @description This starts the SNTP server and gets it to remain in sync with the SNTP server +* @returns {Promise} - A promise if the module was able to sync with ntp server. +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.sntpStart = function (options) { + this.options.sntpTimeSync = true; + + // Sntp.start doesn't actually report errors (2016-10-29) + // so functionality is first detected via sntpGetOffset + return this.sntpGetOffset().then(() => { + return new Promise((resolve, reject) => { + Sntp.start({ + host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org + port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) + clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes + timeout: 500 // Assume packet has been lost after 500 milliseconds + }, () => { + this.sync.sntpActive = true; + this.emit('sntpTimeLock'); + resolve(); + }); + }); + }); +}; + +/** +* @description Stops the sntp from updating. +*/ +Cyton.prototype.sntpStop = function () { + Sntp.stop(); + this.options.sntpTimeSync = false; + this.sync.sntpActive = false; +}; + +/** +* @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time +* @returns {Number} - The time +* @author AJ Keller (@pushtheworldllc) +*/ +Cyton.prototype.time = function () { + if (this.options.sntpTimeSync) { + return this._sntpNow(); + } else { + return Date.now(); + } +}; + +/** +* @description This prints the total number of packets that were not able to be read +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printPacketsBad = function () { + if (this.badPackets > 1) { + console.log('Dropped a total of ' + this.badPackets + ' packets.'); + } else if (this.badPackets === 1) { + console.log('Dropped a total of 1 packet.'); + } else { + console.log('No packets dropped.'); + } +}; + +/** +* @description This prints the total bytes in +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printBytesIn = function () { + if (this.bytesIn > 1) { + console.log('Read in ' + this.bytesIn + ' bytes.'); + } else if (this.bytesIn === 1) { + console.log('Read one 1 packet in.'); + } else { + console.log('Read no packets.'); + } +}; + +/** +* @description This prints the total number of packets that have been read +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.printPacketsRead = function () { + if (this.masterBuffer.packetsRead > 1) { + console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); + } else if (this.masterBuffer.packetsIn === 1) { + console.log('Read 1 packet.'); + } else { + console.log('No packets read.'); + } +}; + +/** +* @description Nice convenience method to print some session details +* @author AJ Keller (@pushtheworldllc) +*/ +/* istanbul ignore next */ +Cyton.prototype.debugSession = function () { + this.printBytesIn(); + this.printPacketsRead(); + this.printPacketsBad(); +}; + +/** +* @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) +* @param channelSettingsObj +*/ +/* istanbul ignore next */ +Cyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { + console.log('-- Channel Settings Object --'); + var powerState = 'OFF'; + if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { + powerState = 'ON'; + } + console.log('---- POWER STATE: ' + powerState); + console.log('-- END --'); +}; + +/** +* @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. +* @param channelSettingsObject +* @returns {boolean} +*/ +Cyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { + return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; +}; + +util.inherits(Cyton, EventEmitter); + +module.exports = Cyton; + +/** +* @description To parse a given channel given output from a print registers query +* @param rawChannelBuffer +* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 +* @returns {Promise} +* @author AJ Keller (@pushtheworldllc) +*/ +// function getChannelSettingsObj (rawChannelBuffer) { +// return new Promise(function (resolve, reject) { +// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { +// reject('Undefined or null channel buffer'); +// } +// +// var channelSettingsObject = { +// CHANNEL: '0', +// POWER_DOWN: '0', +// GAIN_SET: '0', +// INPUT_TYPE_SET: '0', +// BIAS_SET: '0', +// SRB2_SET: '0', +// SRB1_SET: '0' +// }; +// +// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits +// var sizeOfData = rawChannelBuffer.byteLength; +// +// var objIndex = 0; +// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data +// switch (objIndex) { +// case 0: +// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); +// break; +// default: +// break; +// } +// +// objIndex++; +// } +// resolve(channelSettingsObject); +// }); +// } + +function masterBufferMaker () { + var masterBuf = new Buffer(k.OBCIMasterBufferSize); + masterBuf.fill(0); + return { // Buffer used to store bytes in and read packets from + buffer: masterBuf, + positionRead: 0, + positionWrite: 0, + packetsIn: 0, + packetsRead: 0, + looseBytes: 0 + }; +} diff --git a/openBCISample.js b/openBCISample.js index b84c2a9..0877c14 100644 --- a/openBCISample.js +++ b/openBCISample.js @@ -1,7 +1,7 @@ 'use strict'; -var gaussian = require('gaussian'); -var k = require('./openBCIConstants'); -var StreamSearch = require('streamsearch'); +const gaussian = require('gaussian'); +const k = require('./openBCIConstants'); +const StreamSearch = require('streamsearch'); /** Constants for interpreting the EEG data */ // Reference voltage for ADC in ADS1299. @@ -274,7 +274,7 @@ var sampleModule = { return (prefix << 16) | (twoByteBuffer[0] << 8) | twoByteBuffer[1]; }, - interpret24bitAsInt32: threeByteBuffer => { + interpret24bitAsInt32: (threeByteBuffer) => { var prefix = 0; if (threeByteBuffer[0] > 127) { diff --git a/openBCISimulator.js b/openBCISimulator.js index bf9a244..1795ea1 100644 --- a/openBCISimulator.js +++ b/openBCISimulator.js @@ -120,7 +120,7 @@ function OpenBCISimulatorFactory () { if (size > this.outputBuffered) size = this.outputBuffered; - // buffer is copied because presently openBCIBoard.js reuses it + // buffer is copied because presently openBCICyton.js reuses it var outBuffer = new Buffer(this.outputBuffer.slice(0, size)); this.outputBuffer.copy(this.outputBuffer, 0, size, this.outputBuffered); diff --git a/openBCIUtils.js b/openBCIUtils.js new file mode 100644 index 0000000..a9024d5 --- /dev/null +++ b/openBCIUtils.js @@ -0,0 +1,35 @@ +module.exports = { + debugBytes +}; + +/** + * @description Output passed bytes on the console as a hexdump, if enabled + * @param prefix - label to show to the left of bytes + * @param data - bytes to output, a buffer or string + * @private + */ +function debugBytes (prefix, data) { + if (typeof data === 'string') data = new Buffer(data); + + console.log('Debug bytes:'); + + for (var j = 0; j < data.length;) { + var hexPart = ''; + var ascPart = ''; + for (var end = Math.min(data.length, j + 16); j < end; ++j) { + var byt = data[j]; + + var hex = ('0' + byt.toString(16)).slice(-2); + hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in + hexPart += hex; + + var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; + ascPart += asc; + } + + // pad to fixed width for alignment + hexPart = (hexPart + ' ').substring(0, 3 * 17); + + console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); + } +} diff --git a/package.json b/package.json index c6109b6..43455aa 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "openbci", - "version": "1.4.2", + "version": "2.0.0", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", - "main": "openBCIBoard", + "main": "index.js", "scripts": { "start": "node index", "test": "semistandard | snazzy && mocha test", @@ -16,10 +16,12 @@ "license": "MIT", "dependencies": { "buffer-equal": "^1.0.0", + "clone": "^2.0.0", "gaussian": "^1.0.0", + "lodash": "^4.16.6", "mathjs": "^3.3.0", "performance-now": "^0.2.0", - "serialport": "4.0.1", + "serialport": "4.0.6", "sntp": "^2.0.0", "streamsearch": "^0.1.2" }, @@ -53,6 +55,7 @@ "semistandard": { "globals": [ "describe", + "xdescribe", "context", "before", "beforeEach", diff --git a/test/OpenBCIConstants-test.js b/test/OpenBCIConstants-test.js index cae1ffa..0f01603 100644 --- a/test/OpenBCIConstants-test.js +++ b/test/OpenBCIConstants-test.js @@ -764,6 +764,9 @@ describe('OpenBCIConstants', function () { it('should be 125', function () { assert.equal(125, k.OBCISampleRate125); }); + it('should be 250', function () { + assert.equal(200, k.OBCISampleRate200); + }); it('should be 250', function () { assert.equal(250, k.OBCISampleRate250); }); @@ -1446,6 +1449,9 @@ describe('OpenBCIConstants', function () { it('Event Emitter Impedance Array', function () { assert.equal('impedanceArray', k.OBCIEmitterImpedanceArray); }); + it('Event Emitter Message', function () { + assert.equal('message', k.OBCIEmitterMessage); + }); it('Event Emitter Query', function () { assert.equal('query', k.OBCIEmitterQuery); }); @@ -1459,7 +1465,4 @@ describe('OpenBCIConstants', function () { assert.equal('sample', k.OBCIEmitterSample); }); }); - describe('Errors', function () { - - }); }); diff --git a/test/OpenBCISample-test.js b/test/OpenBCISample-test.js index 8ba9974..f856d46 100644 --- a/test/OpenBCISample-test.js +++ b/test/OpenBCISample-test.js @@ -2,28 +2,28 @@ * Created by ajk on 12/15/15. */ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var openBCISample = require('../openBCISample'); -var chai = require('chai'); -var expect = chai.expect; -var assert = chai.assert; -var should = chai.should(); // eslint-disable-line no-unused-vars - -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); +const bluebirdChecks = require('./bluebirdChecks'); +const openBCISample = require('../openBCISample'); +const chai = require('chai'); +const expect = chai.expect; +const assert = chai.assert; +const should = chai.should(); // eslint-disable-line no-unused-vars + +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); chai.use(chaiAsPromised); chai.use(sinonChai); -var bufferEqual = require('buffer-equal'); +const bufferEqual = require('buffer-equal'); -var k = openBCISample.k; +const k = require('../openBCIConstants'); const defaultChannelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); -var sampleBuf = openBCISample.samplePacket(); +const sampleBuf = openBCISample.samplePacket(); -var accelArray; +let accelArray; -var channelScaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); +const channelScaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); describe('openBCISample', function () { beforeEach(function () { @@ -600,28 +600,6 @@ describe('openBCISample', function () { expect(sample.timeStamp).to.equal(time + timeOffset); }); }); - describe('#interpret24bitAsInt32', function () { - it('converts a small positive number', function () { - var buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a large positive number', function () { - var buf1 = new Buffer([0x02, 0xC0, 0x01]); // 0x02C001 === 180225 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 180225); - }); - it('converts a small negative number', function () { - var buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-1, 1); - }); - it('converts a large negative number', function () { - var buf1 = new Buffer([0x81, 0xA1, 0x01]); // 0x81A101 === -8281855 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-8281855, 1); - }); - }); describe('#interpret16bitAsInt32', function () { it('converts a small positive number', function () { var buf1 = new Buffer([0x06, 0x90]); // 0x0690 === 1680 @@ -644,6 +622,28 @@ describe('openBCISample', function () { assert.equal(num, -32351); }); }); + describe('#interpret24bitAsInt32', function () { + it('converts a small positive number', function () { + var buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + var num = openBCISample.interpret24bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a large positive number', function () { + var buf1 = new Buffer([0x02, 0xC0, 0x01]); // 0x02C001 === 180225 + var num = openBCISample.interpret24bitAsInt32(buf1); + assert.equal(num, 180225); + }); + it('converts a small negative number', function () { + var buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 + var num = openBCISample.interpret24bitAsInt32(buf1); + num.should.be.approximately(-1, 1); + }); + it('converts a large negative number', function () { + var buf1 = new Buffer([0x81, 0xA1, 0x01]); // 0x81A101 === -8281855 + var num = openBCISample.interpret24bitAsInt32(buf1); + num.should.be.approximately(-8281855, 1); + }); + }); describe('#floatTo3ByteBuffer', function () { it('converts random floats to a 3-byte buffer', function () { var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); diff --git a/test/openBCIBoard-Impedance-test.js b/test/openBCICyton-Impedance-test.js similarity index 98% rename from test/openBCIBoard-Impedance-test.js rename to test/openBCICyton-Impedance-test.js index 54cfec8..5155122 100644 --- a/test/openBCIBoard-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -1,13 +1,13 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var chai = require('chai'); -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCIBoard = require('../openBCIBoard'); -var openBCISample = openBCIBoard.OpenBCISample; -var k = openBCISample.k; +const bluebirdChecks = require('./bluebirdChecks'); +const chai = require('chai'); +const should = chai.should(); // eslint-disable-line no-unused-vars +const Cyton = require('../openBCICyton'); +const openBCISample = require('../openBCISample'); +const k = openBCISample.k; -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); chai.use(chaiAsPromised); chai.use(sinonChai); @@ -16,7 +16,7 @@ describe('#impedanceTesting', function () { this.timeout(20000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); diff --git a/test/openBCIBoard-test.js b/test/openBCICyton-test.js similarity index 94% rename from test/openBCIBoard-test.js rename to test/openBCICyton-test.js index d4f820e..de40cd6 100644 --- a/test/openBCIBoard-test.js +++ b/test/openBCICyton-test.js @@ -1,17 +1,17 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var sinon = require('sinon'); -var chai = require('chai'); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCIBoard = require('../openBCIBoard'); -var openBCISample = openBCIBoard.OpenBCISample; -var k = openBCISample.k; -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); -var bufferEqual = require('buffer-equal'); -var fs = require('fs'); -var math = require('mathjs'); +const bluebirdChecks = require('./bluebirdChecks'); +const sinon = require('sinon'); +const chai = require('chai'); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const Cyton = require('../openBCICyton'); +const openBCISample = require('../openBCISample'); +const k = require('../openBCIConstants'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); +const bufferEqual = require('buffer-equal'); +const fs = require('fs'); +const math = require('mathjs'); chai.use(chaiAsPromised); chai.use(sinonChai); @@ -21,7 +21,7 @@ describe('openbci-sdk', function () { var ourBoard, masterPortName, realBoard, spy; before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.autoFindOpenBCIBoard() .then(portName => { ourBoard = null; @@ -59,14 +59,14 @@ describe('openbci-sdk', function () { return bluebirdChecks.noPendingPromises(); }); it('constructs with require', function () { - var OpenBCIBoard = require('../openBCIBoard').OpenBCIBoard; - ourBoard = new OpenBCIBoard({ + var OpenBCICyton = require('../index').Cyton; + ourBoard = new OpenBCICyton({ verbose: true }); expect(ourBoard.numberOfChannels()).to.equal(8); }); it('constructs with the correct default options', () => { - var board = new openBCIBoard.OpenBCIBoard(); + var board = new Cyton(); expect(board.options.boardType).to.equal(k.OBCIBoardDefault); expect(board.options.baudRate).to.equal(115200); expect(board.options.simulate).to.be.false; @@ -88,16 +88,16 @@ describe('openbci-sdk', function () { expect(board.isStreaming()).to.be.false; }); it('should be able to set ganglion mode', () => { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ boardType: 'ganglion' }); (board.options.boardType).should.equal('ganglion'); }); it('should be able to set set daisy mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ boardType: 'daisy' }); - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ boardtype: 'daisy' }); (ourBoard1.options.boardType).should.equal('daisy'); @@ -110,122 +110,122 @@ describe('openbci-sdk', function () { }); }); it('should be able to change baud rate', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ baudRate: 9600 }); (ourBoard1.options.baudRate).should.equal(9600); - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ baudrate: 9600 }); (ourBoard2.options.baudRate).should.equal(9600); }); it('should be able to enter simulate mode from the constructor', () => { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ simulate: true }); expect(board.options.simulate).to.be.true; }); it('should be able to set the simulator to board failure mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorBoardFailure: true }); expect(ourBoard1.options.simulatorBoardFailure).to.be.true; - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorboardfailure: true }); expect(ourBoard2.options.simulatorBoardFailure).to.be.true; }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorDaisyModuleAttached: true }); expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatordaisymoduleattached: true }); expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true; }); it('should be able to start the simulator with firmware version 2', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorFirmwareVersion: 'v2' }); (ourBoard1.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorfirmwareversion: 'v2' }); (ourBoard2.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); }); it('should be able to put the simulator in raw aux mode', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorHasAccelerometer: false }); expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorhasaccelerometer: false }); expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false; }); it('should be able to make the internal clock of the simulator run slow', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInternalClockDrift: -1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.lessThan(0); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinternalclockdrift: -1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.lessThan(0); }); it('should be able to make the internal clock of the simulator run fast', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInternalClockDrift: 1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.greaterThan(0); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinternalclockdrift: 1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.greaterThan(0); }); it('should be able to not inject alpha waves into the simulator', function () { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInjectAlpha: false }); expect(ourBoard1.options.simulatorInjectAlpha).to.be.false; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinjectalpha: false }); expect(ourBoard2.options.simulatorInjectAlpha).to.be.false; }); it('can turn 50Hz line noise on', function () { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorInjectLineNoise: '50Hz' }); expect(ourBoard1.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorinjectlinenoise: '50Hz' }); expect(ourBoard2.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); }); it('can turn no line noise on', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulatorInjectLineNoise: 'none' }); (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseNone); }); it('defaults to 60Hz line noise when bad input', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulatorInjectLineNoise: '20Hz' }); (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseHz60); }); it('can enter simulate mode with different sample rate', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulate: true, simulatorSampleRate: 69 }); @@ -234,18 +234,18 @@ describe('openbci-sdk', function () { (ourBoard.sampleRate()).should.equal(69); }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ simulatorSerialPortFailure: true }); expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true; // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ simulatorserialportfailure: true }); expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true; }); it('should be able to enter sync mode', function () { - var ourBoard = new openBCIBoard.OpenBCIBoard({ + var ourBoard = new Cyton({ sntpTimeSync: true }); expect(ourBoard.options.sntpTimeSync).to.be.true; @@ -262,30 +262,30 @@ describe('openbci-sdk', function () { }); it('should be able to change the ntp pool host', function () { var expectedPoolName = 'time.apple.com'; - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ sntpTimeSyncHost: expectedPoolName }); expect(ourBoard1.options.sntpTimeSyncHost).to.equal(expectedPoolName); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ sntptimesynchost: expectedPoolName }); expect(ourBoard2.options.sntpTimeSyncHost).to.equal(expectedPoolName); }); it('should be able to change the ntp pool port', function () { var expectedPortNumber = 73; - var ourBoard1 = new openBCIBoard.OpenBCIBoard({ + var ourBoard1 = new Cyton({ sntpTimeSyncPort: expectedPortNumber }); expect(ourBoard1.options.sntpTimeSyncPort).to.equal(expectedPortNumber); // Verify multi case support - var ourBoard2 = new openBCIBoard.OpenBCIBoard({ + var ourBoard2 = new Cyton({ sntptimesyncport: expectedPortNumber }); expect(ourBoard2.options.sntpTimeSyncPort).to.equal(expectedPortNumber); }); it('should report when sntp fails', function (done) { - var ourBoard = new openBCIBoard.OpenBCIBoard({ + var ourBoard = new Cyton({ sntpTimeSync: true, sntpTimeSyncHost: 'no\'where' }); @@ -298,18 +298,18 @@ describe('openbci-sdk', function () { }); }); it('can enter verbose mode', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); (ourBoard.options.verbose).should.equal(true); }); it('should start in current stream state in the init mode', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.curParsingMode.should.equal(k.OBCIParsingReset); }); it('configures impedance testing variables correctly', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); (ourBoard.impedanceTest.active).should.equal(false); (ourBoard.impedanceTest.isTestingNInput).should.equal(false); (ourBoard.impedanceTest.isTestingPInput).should.equal(false); @@ -317,7 +317,7 @@ describe('openbci-sdk', function () { (ourBoard.impedanceTest.sampleNumber).should.equal(0); }); it('configures sync object correctly', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); expect(ourBoard.sync.curSyncObj).to.be.null; expect(ourBoard.sync.eventEmitter).to.be.null; expect(ourBoard.sync.objArray.length).to.equal(0); @@ -327,24 +327,24 @@ describe('openbci-sdk', function () { expect(ourBoard.sync.timeOffsetArray.length).to.equal(0); }); it('configures impedance array with the correct amount of channels for default', function () { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); (ourBoard.impedanceArray.length).should.equal(8); }); it('configures impedance array with the correct amount of channels for daisy', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ boardType: 'daisy' }); (ourBoard.impedanceArray.length).should.equal(16); }); it('configures impedance array with the correct amount of channels for ganglion', function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ boardType: 'ganglion' }); (ourBoard.impedanceArray.length).should.equal(4); }); it('should throw if passed an invalid option', function (done) { try { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ foo: 'bar' }); done('did not throw'); @@ -354,13 +354,13 @@ describe('openbci-sdk', function () { describe('#simulator', function () { after(() => bluebirdChecks.noPendingPromises()); it('can enable simulator after constructor', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); ourBoard.simulatorEnable().should.be.fulfilled.and.notify(done); }); it('should start sim and call disconnected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); var disconnectStub = sinon.stub(ourBoard, 'disconnect').returns(Promise.resolve()); @@ -375,27 +375,27 @@ describe('openbci-sdk', function () { }, done); }); it('should not enable the simulator if already simulating', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.simulatorEnable().should.be.rejected.and.notify(done); }); it('can disable simulator', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.simulatorDisable().should.be.fulfilled.and.notify(done); }); it('should not disable simulator if not in simulate mode', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); ourBoard.simulatorDisable().should.be.rejected.and.notify(done); }); it('should disable sim and call disconnected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -412,7 +412,7 @@ describe('openbci-sdk', function () { }); }); it('should be able to propagate constructor options to simulator', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -454,7 +454,7 @@ describe('openbci-sdk', function () { }); describe('#debug', function () { before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ debug: true }); ourBoard.connect(k.OBCISimulatorPortName).catch(done); @@ -488,7 +488,7 @@ describe('openbci-sdk', function () { describe('#boardTests', function () { this.timeout(3000); before(function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ simulate: !realBoard, verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -664,7 +664,7 @@ describe('openbci-sdk', function () { ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, ourBoard.disconnect() - ]).then(() => {+ + ]).then(() => { writeSpy.should.have.not.been.called; writeSpy.restore(); }); @@ -924,7 +924,7 @@ describe('openbci-sdk', function () { describe('#_processDataBuffer', function () { var _processQualifiedPacketSpy; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); _processQualifiedPacketSpy = sinon.spy(ourBoard, '_processQualifiedPacket'); @@ -1088,7 +1088,7 @@ describe('openbci-sdk', function () { var funcSpyTimeSyncSet, funcSpyTimeSyncedAccel, funcSpyTimeSyncedRawAux, funcSpyStandardRawAux, funcSpyStandardAccel; before(function () { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); // Put watchers on all functions @@ -1242,7 +1242,7 @@ describe('openbci-sdk', function () { var timeSyncSetPacket; var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: false }); }); @@ -1427,7 +1427,7 @@ describe('openbci-sdk', function () { describe('#_processPacket Errors', function () { var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: false }); }); @@ -1471,7 +1471,7 @@ describe('openbci-sdk', function () { describe('#time', function () { after(() => bluebirdChecks.noPendingPromises()); it('should use sntp time when sntpTimeSync specified in options', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true, sntpTimeSync: true }); @@ -1485,7 +1485,7 @@ describe('openbci-sdk', function () { }); }); it('should use Date.now() for time when sntpTimeSync is not specified in options', function () { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true }); var funcSpySntpNow = sinon.spy(board, '_sntpNow'); @@ -1499,7 +1499,7 @@ describe('openbci-sdk', function () { funcSpySntpNow = null; }); it('should emit sntpTimeLock event after sycned with ntp server', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ verbose: true, sntpTimeSync: true }); @@ -1513,7 +1513,7 @@ describe('openbci-sdk', function () { describe('#sntpStart', function () { after(() => bluebirdChecks.noPendingPromises()); it('should be able to start ntp server', () => { - var board = new openBCIBoard.OpenBCIBoard(); + var board = new Cyton(); expect(board.sntp.isLive()).to.be.false; return Promise.all([ board.sntpStart() @@ -1532,7 +1532,7 @@ describe('openbci-sdk', function () { this.timeout(5000); var board; before(done => { - board = new openBCIBoard.OpenBCIBoard({ + board = new Cyton({ sntpTimeSync: true }); board.once('sntpTimeLock', () => { @@ -1561,7 +1561,7 @@ describe('openbci-sdk', function () { describe('#sntpGetOffset', function () { after(() => bluebirdChecks.noPendingPromises()); it('should get the sntp offset', function (done) { - var board = new openBCIBoard.OpenBCIBoard({ + var board = new Cyton({ sntpTimeSync: true }); board.once('sntpTimeLock', () => { @@ -1576,7 +1576,7 @@ describe('openbci-sdk', function () { var ourBoard; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); }); @@ -1653,7 +1653,7 @@ $$$`); describe('#_processBytes', function () { before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); }); @@ -1971,7 +1971,7 @@ $$$`); describe('#_finalizeNewSampleForDaisy', function () { var ourBoard, randomSampleGenerator, sampleEvent, failTimeout; before(() => { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true }); randomSampleGenerator = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, false, 'none'); @@ -2081,13 +2081,13 @@ $$$`); describe('#usingVersionTwoFirmware', function () { after(() => bluebirdChecks.noPendingPromises()); it('should return true if firmware is version 2', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); ourBoard.info.firmware = 'v2'; expect(ourBoard.usingVersionTwoFirmware()).to.be.true; }); it('should return false if not firmware version 2', () => { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new Cyton(); expect(ourBoard.usingVersionTwoFirmware()).to.be.false; }); @@ -2106,7 +2106,7 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2115,7 +2115,7 @@ $$$`); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2135,7 +2135,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2147,7 +2147,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2161,7 +2161,7 @@ $$$`); }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2175,7 +2175,7 @@ $$$`); }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2189,7 +2189,7 @@ $$$`); }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2203,7 +2203,7 @@ $$$`); }); it('should not change the channel if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2219,7 +2219,7 @@ $$$`); it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2247,7 +2247,7 @@ $$$`); }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2255,7 +2255,7 @@ $$$`); ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2275,7 +2275,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2288,7 +2288,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2301,7 +2301,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2314,7 +2314,7 @@ $$$`); }).catch(err => done(err)); }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2328,7 +2328,7 @@ $$$`); }); it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2357,14 +2357,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioChannelGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2383,7 +2383,7 @@ $$$`); }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2395,7 +2395,7 @@ $$$`); }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2411,7 +2411,7 @@ $$$`); }).catch(err => done(err)); }); it('should get message even if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2438,7 +2438,7 @@ $$$`); }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2447,7 +2447,7 @@ $$$`); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2468,7 +2468,7 @@ $$$`); }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2481,7 +2481,7 @@ $$$`); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2495,7 +2495,7 @@ $$$`); }); it('should reject if no poll time is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2509,7 +2509,7 @@ $$$`); }); it('should reject if the requested new poll time is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2523,7 +2523,7 @@ $$$`); }); it('should reject if the requested new poll time is higher than 255', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2537,7 +2537,7 @@ $$$`); }); it('should not change the poll time if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2553,7 +2553,7 @@ $$$`); it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { var newPollTime = 69; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2584,14 +2584,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2610,7 +2610,7 @@ $$$`); }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2622,7 +2622,7 @@ $$$`); }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2638,7 +2638,7 @@ $$$`); }).catch(err => done(err)); }); it('should get failure message if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -2666,28 +2666,28 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not try to set baud rate if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); }); it('should reject if no input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); }); it('should be rejected if input type incorrect', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); }); it('should not try to change the baud rate if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2706,7 +2706,7 @@ $$$`); }).catch(err => done(err)); }); it('should not try to change the baud rate if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2718,7 +2718,7 @@ $$$`); }).catch(err => done(err)); }); it('should set the baud rate to default if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2734,7 +2734,7 @@ $$$`); }).catch(err => done(err)); }); it('should set the baud rate to fast if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2764,14 +2764,14 @@ $$$`); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not get system status if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); }); it('should not get system status if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2790,7 +2790,7 @@ $$$`); }).catch(err => done(err)); }); it('should not get system status if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true }); @@ -2802,7 +2802,7 @@ $$$`); }).catch(err => done(err)); }); it('should get up system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -2818,7 +2818,7 @@ $$$`); }).catch(err => done(err)); }); it('should get down system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2', @@ -2839,7 +2839,7 @@ $$$`); describe('#radioTests', function () { this.timeout(0); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -2999,7 +2999,7 @@ $$$`); runHardwareValidation = false; } if (runHardwareValidation) { - board = new openBCIBoard.OpenBCIBoard({ + board = new Cyton({ verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); @@ -3082,7 +3082,7 @@ describe('#daisy', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorDaisyModuleAttached: true, @@ -3163,7 +3163,7 @@ describe('#syncWhileStreaming', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom @@ -3276,7 +3276,7 @@ describe('#syncErrors', function () { var ourBoard; this.timeout(4000); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2' }); diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js index c26c1b3..fdd60e2 100644 --- a/test/openBCISimulator-test.js +++ b/test/openBCISimulator-test.js @@ -1,13 +1,13 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var bufferEqual = require('buffer-equal'); -var chai = require('chai'); -var chaiAsPromised = require(`chai-as-promised`); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCISimulator = require('../openBCISimulator'); -var openBCISample = require('../openBCISample'); -var k = openBCISample.k; +const bluebirdChecks = require('./bluebirdChecks'); +const bufferEqual = require('buffer-equal'); +const chai = require('chai'); +const chaiAsPromised = require(`chai-as-promised`); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const openBCISimulator = require('../openBCISimulator'); +const openBCISample = require('../openBCISample'); +const k = require('../openBCIConstants'); chai.use(chaiAsPromised); From 9dd090df44f2c16eed6e32c01ad5549f82b2a32e Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:36:08 -0500 Subject: [PATCH 06/42] Add: test for print register settings --- test/openBCICyton-test.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index de40cd6..7ca8274 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -889,7 +889,23 @@ describe('openbci-sdk', function () { ourBoard.channelSet(1, true, 24, 'normal', 'taco', true, true).should.be.rejected.and.notify(done); }); }); - + describe('#printRegisterSettings', function () { + before(function (done) { + ourBoard.printRegisterSettings() + .then(() => { + done(); + }).catch(done); + }); + after(function () { + ourBoard.curParsingMode = k.OBCIParsingNormal; + }); + it('should send the correct register setting', function () { + spy.should.have.been.calledWith(k.OBCIMiscQueryRegisterSettings); + }); + it('should have set the proper parseing mode', function () { + ourBoard.curParsingMode = k.OBCIParsingEOT; + }); + }); describe('#impedanceTest Not Connected Rejects ', function () { it('rejects all channeles when not streaming', function (done) { ourBoard.impedanceTestAllChannels().should.be.rejected.and.notify(done); From 89b5f35abc68ac58f5c4d781acfa7607d3ec97f1 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:46:56 -0500 Subject: [PATCH 07/42] Add test for testSignal function --- README.md | 2 +- changelog.md | 6 +++++ openBCICyton.js | 2 +- test/openBCICyton-test.js | 53 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90e7d27..d8e8697 100644 --- a/README.md +++ b/README.md @@ -1018,7 +1018,7 @@ A String indicating which test signal to apply * `pulse1xFast` - Connect to test signal 1x Amplitude, fast pulse * `pulse1xSlow` - Connect to test signal 1x Amplitude, slow pulse * `pulse2xFast` - Connect to test signal 2x Amplitude, fast pulse - * `pulse2xFast` - Connect to test signal 2x Amplitude, slow pulse + * `pulse2xSlow` - Connect to test signal 2x Amplitude, slow pulse * `none` - Reset to default **_Returns_** a promise, if the commands were sent to write buffer. diff --git a/changelog.md b/changelog.md index 2af7616..a7899b4 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,12 @@ * Major change to how board is initialized with removal of `factory` paradigm. * Drop support for Node 4 and 5 due to lack of EMACS 6 +### Bug Fixes +* Documentation error with `testSignal` function. + +### Enhancements +* Add more tests for public API functions. + # 1.4.3 ### New examples diff --git a/openBCICyton.js b/openBCICyton.js index 33ac7bf..2f6fd1d 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1113,7 +1113,7 @@ Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType * - Connect to test signal 1x Amplitude, slow pulse * - `pulse2xFast` * - Connect to test signal 2x Amplitude, fast pulse -* - `pulse2xFast` +* - `pulse2xSlow` * - Connect to test signal 2x Amplitude, slow pulse * - `none` * - Reset to default diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 7ca8274..a3cab72 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -906,6 +906,59 @@ describe('openbci-sdk', function () { ourBoard.curParsingMode = k.OBCIParsingEOT; }); }); + describe('#testSignal', function () { + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('dc').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToDC); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('ground').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToGround); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse1xFast').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xFast); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse1xSlow').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xSlow); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse2xFast').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xFast); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should call the write function with proper command for dc test signal', function (done) { + ourBoard.testSignal('pulse2xSlow').then(() => { + setTimeout(() => { + spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xSlow); + done(); + }, 5 * k.OBCIWriteIntervalDelayMSShort); + }); + }); + it('should reject with invalid test signal', function (done) { + ourBoard.testSignal('taco').should.be.rejected.and.notify(done); + }); + }); describe('#impedanceTest Not Connected Rejects ', function () { it('rejects all channeles when not streaming', function (done) { ourBoard.impedanceTestAllChannels().should.be.rejected.and.notify(done); From 595c6dea5ae61d4e3428ea739bbb0c3f7ba24d31 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 28 Nov 2016 10:52:16 -0500 Subject: [PATCH 08/42] Add: Coverage over sampleRate and numberOfChannels when info property is null --- test/openBCICyton-test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index a3cab72..5fe63ce 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -86,6 +86,11 @@ describe('openbci-sdk', function () { expect(board.numberOfChannels()).to.equal(8); expect(board.isConnected()).to.be.false; expect(board.isStreaming()).to.be.false; + it('should still get proper values if no info object', function () { + board.info = null; + expect(board.sampleRate()).to.equal(250); + expect(board.numberOfChannels()).to.equal(8); + }); }); it('should be able to set ganglion mode', () => { var board = new Cyton({ @@ -102,6 +107,7 @@ describe('openbci-sdk', function () { }); (ourBoard1.options.boardType).should.equal('daisy'); (ourBoard2.options.boardType).should.equal('daisy'); + ourBoard1.info = null; it('should get value for daisy', () => { ourBoard1.sampleRate().should.equal(125); }); From 54d223533aad125772129b22ce878491472762bb Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 3 Apr 2017 13:25:17 -0400 Subject: [PATCH 09/42] Update dependencies --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index fbc0c90..7b805a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "1.5.1", + "version": "1.5.2", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "openBCIBoard", "scripts": { @@ -19,8 +19,8 @@ "buffer-equal": "^1.0.0", "gaussian": "^1.0.0", "mathjs": "^3.3.0", - "performance-now": "^0.2.0", - "serialport": "4.0.1", + "performance-now": "^2.1.0", + "serialport": "4.0.7", "sntp": "^2.0.0", "streamsearch": "^0.1.2" }, @@ -28,18 +28,18 @@ "test": "test" }, "devDependencies": { - "bluebird": "3.4.6", + "bluebird": "3.5.0", "chai": "^3.4.1", - "chai-as-promised": "^5.2.0", - "codecov": "^1.0.1", + "chai-as-promised": "^6.0.0", + "codecov": "^2.1.0", "istanbul": "^0.4.4", "mocha": "^3.0.2", "sandboxed-module": "^2.0.3", - "semistandard": "^9.0.0", - "sinon": "^1.17.2", + "semistandard": "^10.0.0", + "sinon": "^2.1.0", "sinon-as-promised": "^4.0.2", "sinon-chai": "^2.8.0", - "snazzy": "^5.0.0" + "snazzy": "^6.0.0" }, "repository": { "type": "git", From 483811b0923759bb0c2ee2f333a479d6ce662678 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 3 Apr 2017 13:26:07 -0400 Subject: [PATCH 10/42] Update changelog.md --- changelog.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/changelog.md b/changelog.md index bdc8776..a325349 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,17 @@ +# 1.5.2 + +### Dependency Package Updates +* `performance-now`: from `^0.2.0` to `2.1.0` +* `serialport` - from `4.0.1` to `4.0.7` + +### Development Dependency Package Updates +* `bluebird`: from `3.4.6` to `3.5.0` +* `chai-as-promised`: from `^5.2.0` to `^6.0.0` +* `codecov`: from `^1.0.1` to `^2.1.0` +* `semistandard`: from `^9.0.0` to `^10.0.0` +* `sinon`: from `^1.17.2` to `^2.1.0` +* `snazzy`: from `^5.0.0` to `^6.0.0` + # 1.5.1 ### New Features From af0cabbe56e0c97d62dec483dade3bdce9cd1adb Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Sun, 16 Jul 2017 20:23:51 -0400 Subject: [PATCH 11/42] Working on adding in utilities --- .travis.yml | 16 +- openBCIConstants.js | 1224 --------------------------- openBCICyton.js | 126 ++- openBCISample.js | 1196 -------------------------- openBCIUtils.js | 35 - package.json | 1 + test/OpenBCIConstants-test.js | 1481 --------------------------------- test/OpenBCISample-test.js | 1299 ----------------------------- 8 files changed, 52 insertions(+), 5326 deletions(-) delete mode 100644 openBCIConstants.js delete mode 100644 openBCISample.js delete mode 100644 openBCIUtils.js delete mode 100644 test/OpenBCIConstants-test.js delete mode 100644 test/OpenBCISample-test.js diff --git a/.travis.yml b/.travis.yml index 636141d..caec7f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,9 @@ language: node_js node_js: - - "5.11.0" - - "6.0" - - "6.1" - - "6.2" - - "6.3" - - "6.4" - - "6.5" - - "6.6" - - "6.7" - - "6.8" - - "7.0" - - "7.1" + - "4.8.4" + - "6.11.1" + - "7.10.1" + - "8.1.4" install: - npm install --all script: diff --git a/openBCIConstants.js b/openBCIConstants.js deleted file mode 100644 index a944706..0000000 --- a/openBCIConstants.js +++ /dev/null @@ -1,1224 +0,0 @@ -/** -* Created by ajk on 12/16/15. -* Purpose: This file folds all the constants for the -* OpenBCI Board -*/ -'use strict'; -/** Turning channels off */ -const obciChannelOff1 = '1'; -const obciChannelOff2 = '2'; -const obciChannelOff3 = '3'; -const obciChannelOff4 = '4'; -const obciChannelOff5 = '5'; -const obciChannelOff6 = '6'; -const obciChannelOff7 = '7'; -const obciChannelOff8 = '8'; -const obciChannelOff9 = 'q'; -const obciChannelOff10 = 'w'; -const obciChannelOff11 = 'e'; -const obciChannelOff12 = 'r'; -const obciChannelOff13 = 't'; -const obciChannelOff14 = 'y'; -const obciChannelOff15 = 'u'; -const obciChannelOff16 = 'i'; - -/** Turn channels on */ -const obciChannelOn1 = '!'; -const obciChannelOn2 = '@'; -const obciChannelOn3 = '#'; -const obciChannelOn4 = '$'; -const obciChannelOn5 = '%'; -const obciChannelOn6 = '^'; -const obciChannelOn7 = '&'; -const obciChannelOn8 = '*'; -const obciChannelOn9 = 'Q'; -const obciChannelOn10 = 'W'; -const obciChannelOn11 = 'E'; -const obciChannelOn12 = 'R'; -const obciChannelOn13 = 'T'; -const obciChannelOn14 = 'Y'; -const obciChannelOn15 = 'U'; -const obciChannelOn16 = 'I'; - -/** Test Signal Control Commands -* 1x - Voltage will be 1 * (VREFP - VREFN) / 2.4 mV -* 2x - Voltage will be 2 * (VREFP - VREFN) / 2.4 mV -*/ -const obciTestSignalConnectToDC = 'p'; -const obciTestSignalConnectToGround = '0'; -const obciTestSignalConnectToPulse1xFast = '='; -const obciTestSignalConnectToPulse1xSlow = '-'; -const obciTestSignalConnectToPulse2xFast = ']'; -const obciTestSignalConnectToPulse2xSlow = '['; - -/** Channel Setting Commands */ -const obciChannelCmdADCNormal = '0'; -const obciChannelCmdADCShorted = '1'; -const obciChannelCmdADCBiasDRP = '6'; -const obciChannelCmdADCBiasDRN = '7'; -const obciChannelCmdADCBiasMethod = '2'; -const obciChannelCmdADCMVDD = '3'; -const obciChannelCmdADCTemp = '4'; -const obciChannelCmdADCTestSig = '5'; -const obciChannelCmdBiasInclude = '1'; -const obciChannelCmdBiasRemove = '0'; -const obciChannelCmdChannel1 = '1'; -const obciChannelCmdChannel2 = '2'; -const obciChannelCmdChannel3 = '3'; -const obciChannelCmdChannel4 = '4'; -const obciChannelCmdChannel5 = '5'; -const obciChannelCmdChannel6 = '6'; -const obciChannelCmdChannel7 = '7'; -const obciChannelCmdChannel8 = '8'; -const obciChannelCmdChannel9 = 'Q'; -const obciChannelCmdChannel10 = 'W'; -const obciChannelCmdChannel11 = 'E'; -const obciChannelCmdChannel12 = 'R'; -const obciChannelCmdChannel13 = 'T'; -const obciChannelCmdChannel14 = 'Y'; -const obciChannelCmdChannel15 = 'U'; -const obciChannelCmdChannel16 = 'I'; -const obciChannelCmdGain1 = '0'; -const obciChannelCmdGain2 = '1'; -const obciChannelCmdGain4 = '2'; -const obciChannelCmdGain6 = '3'; -const obciChannelCmdGain8 = '4'; -const obciChannelCmdGain12 = '5'; -const obciChannelCmdGain24 = '6'; -const obciChannelCmdLatch = 'X'; -const obciChannelCmdPowerOff = '1'; -const obciChannelCmdPowerOn = '0'; -const obciChannelCmdSet = 'x'; -const obciChannelCmdSRB1Connect = '1'; -const obciChannelCmdSRB1Diconnect = '0'; -const obciChannelCmdSRB2Connect = '1'; -const obciChannelCmdSRB2Diconnect = '0'; - -/** Channel Setting Helper Strings */ -const obciStringADCNormal = 'normal'; -const obciStringADCShorted = 'shorted'; -const obciStringADCBiasMethod = 'biasMethod'; -const obciStringADCMvdd = 'mvdd'; -const obciStringADCTemp = 'temp'; -const obciStringADCTestSig = 'testSig'; -const obciStringADCBiasDrp = 'biasDrp'; -const obciStringADCBiasDrn = 'biasDrn'; - -/** Default Channel Settings */ -const obciChannelDefaultAllSet = 'd'; -const obciChannelDefaultAllGet = 'D'; - -/** LeadOff Impedance Commands */ -const obciChannelImpedanceLatch = 'Z'; -const obciChannelImpedanceSet = 'z'; -const obciChannelImpedanceTestSignalApplied = '1'; -const obciChannelImpedanceTestSignalAppliedNot = '0'; - -/** SD card Commands */ -const obciSDLogForHour1 = 'G'; -const obciSDLogForHour2 = 'H'; -const obciSDLogForHour4 = 'J'; -const obciSDLogForHour12 = 'K'; -const obciSDLogForHour24 = 'L'; -const obciSDLogForMin5 = 'A'; -const obciSDLogForMin15 = 'S'; -const obciSDLogForMin30 = 'F'; -const obciSDLogForSec14 = 'a'; -const obciSDLogStop = 'j'; - -/** SD Card String Commands */ -const obciStringSDHour1 = '1hour'; -const obciStringSDHour2 = '2hour'; -const obciStringSDHour4 = '4hour'; -const obciStringSDHour12 = '12hour'; -const obciStringSDHour24 = '24hour'; -const obciStringSDMin5 = '5min'; -const obciStringSDMin15 = '15min'; -const obciStringSDMin30 = '30min'; -const obciStringSDSec14 = '14sec'; - -/** Stream Data Commands */ -const obciStreamStart = 'b'; -const obciStreamStop = 's'; - -/** Miscellaneous */ -const obciMiscQueryRegisterSettings = '?'; -const obciMiscQueryRegisterSettingsChannel1 = 'CH1SET'; -const obciMiscQueryRegisterSettingsChannel2 = 'CH2SET'; -const obciMiscQueryRegisterSettingsChannel3 = 'CH3SET'; -const obciMiscQueryRegisterSettingsChannel4 = 'CH4SET'; -const obciMiscQueryRegisterSettingsChannel5 = 'CH5SET'; -const obciMiscQueryRegisterSettingsChannel6 = 'CH6SET'; -const obciMiscQueryRegisterSettingsChannel7 = 'CH7SET'; -const obciMiscQueryRegisterSettingsChannel8 = 'CH8SET'; -const obciMiscSoftReset = 'v'; - -/** 16 Channel Commands */ -const obciChannelMaxNumber8 = 'c'; -const obciChannelMaxNumber16 = 'C'; -const obciChannelMaxNumber8NoDaisyToRemove = ''; -const obciChannelMaxNumber8SuccessDaisyRemoved = 'daisy removed'; -const obciChannelMaxNumber16DaisyAlreadyAttached = '16'; -const obciChannelMaxNumber16DaisyAttached = 'daisy attached16'; -const obciChannelMaxNumber16NoDaisyAttached = 'no daisy to attach!8'; - -/** 60Hz line filter */ -const obciFilterDisable = 'g'; -const obciFilterEnable = 'f'; - -/** Triggers */ -const obciTrigger = '`'; - -/** Sync Clocks */ -const obciSyncTimeSet = '<'; -const obciSyncTimeSent = ','; - -/** Radio Key */ -const obciRadioKey = 0xF0; -/** Radio Commands */ -const obciRadioCmdChannelGet = 0x00; -const obciRadioCmdChannelSet = 0x01; -const obciRadioCmdChannelSetOverride = 0x02; -const obciRadioCmdPollTimeGet = 0x03; -const obciRadioCmdPollTimeSet = 0x04; -const obciRadioCmdBaudRateSetDefault = 0x05; -const obciRadioCmdBaudRateSetFast = 0x06; -const obciRadioCmdSystemStatus = 0x07; - -/** Possible number of channels */ -const obciNumberOfChannelsDaisy = 16; -const obciNumberOfChannelsDefault = 8; -const obciNumberOfChannelsGanglion = 4; - -/** Possible OpenBCI board types */ -const obciBoardDaisy = 'daisy'; -const obciBoardDefault = 'default'; -const obciBoardGanglion = 'ganglion'; - -/** Possible Simulator Line Noise injections */ -const obciSimulatorLineNoiseHz60 = '60Hz'; -const obciSimulatorLineNoiseHz50 = '50Hz'; -const obciSimulatorLineNoiseNone = 'none'; - -/** Possible Simulator Fragmentation modes */ -const obciSimulatorFragmentationRandom = 'random'; -const obciSimulatorFragmentationFullBuffers = 'fullBuffers'; -const obciSimulatorFragmentationOneByOne = 'oneByOne'; -const obciSimulatorFragmentationNone = 'none'; - -/** Possible Sample Rates */ -const obciSampleRate125 = 125; -const obciSampleRate200 = 200; -const obciSampleRate250 = 250; - -/** Max sample number */ -const obciSampleNumberMax = 255; - -/** Packet Size */ -const obciPacketSize = 33; - -/** OpenBCI V3 Standard Packet Positions */ -/** -* 0:[startByte] | 1:[sampleNumber] | 2:[Channel-1.1] | 3:[Channel-1.2] | 4:[Channel-1.3] | 5:[Channel-2.1] | 6:[Channel-2.2] | 7:[Channel-2.3] | 8:[Channel-3.1] | 9:[Channel-3.2] | 10:[Channel-3.3] | 11:[Channel-4.1] | 12:[Channel-4.2] | 13:[Channel-4.3] | 14:[Channel-5.1] | 15:[Channel-5.2] | 16:[Channel-5.3] | 17:[Channel-6.1] | 18:[Channel-6.2] | 19:[Channel-6.3] | 20:[Channel-7.1] | 21:[Channel-7.2] | 22:[Channel-7.3] | 23:[Channel-8.1] | 24:[Channel-8.2] | 25:[Channel-8.3] | 26:[Aux-1.1] | 27:[Aux-1.2] | 28:[Aux-2.1] | 29:[Aux-2.2] | 30:[Aux-3.1] | 31:[Aux-3.2] | 32:StopByte -*/ -const obciPacketPositionChannelDataStart = 2; // 0:startByte | 1:sampleNumber | [2:4] | [5:7] | [8:10] | [11:13] | [14:16] | [17:19] | [21:23] | [24:26] -const obciPacketPositionChannelDataStop = 25; // 24 bytes for channel data -const obciPacketPositionSampleNumber = 1; -const obciPacketPositionStartByte = 0; // first byte -const obciPacketPositionStopByte = 32; // [32] -const obciPacketPositionStartAux = 26; // [26,27]:Aux 1 | [28,29]:Aux 2 | [30,31]:Aux 3 -const obciPacketPositionStopAux = 31; // - - - [30,31]:Aux 3 | 32: Stop byte -const obciPacketPositionTimeSyncAuxStart = 26; -const obciPacketPositionTimeSyncAuxStop = 28; -const obciPacketPositionTimeSyncTimeStart = 28; -const obciPacketPositionTimeSyncTimeStop = 32; - -/** Notable Bytes */ -const obciByteStart = 0xA0; -const obciByteStop = 0xC0; - -/** Errors */ -const errorNobleAlreadyScanning = 'Scan already under way'; -const errorNobleNotAlreadyScanning = 'No scan started'; -const errorNobleNotInPoweredOnState = 'Please turn blue tooth on.'; -const errorInvalidByteLength = 'Invalid Packet Byte Length'; -const errorInvalidByteStart = 'Invalid Start Byte'; -const errorInvalidByteStop = 'Invalid Stop Byte'; -const errorInvalidType = 'Invalid Type'; -const errorTimeSyncIsNull = "'this.sync.curSyncObj' must not be null"; -const errorTimeSyncNoComma = 'Missed the time sync sent confirmation. Try sync again'; -const errorUndefinedOrNullInput = 'Undefined or Null Input'; - -/** Max Master Buffer Size */ -const obciMasterBufferSize = 4096; - -/** Impedance Calculation Variables */ -const obciLeadOffDriveInAmps = 0.000000006; -const obciLeadOffFrequencyHz = 31.5; - -/** Command send delay */ -const obciWriteIntervalDelayMSLong = 50; -const obciWriteIntervalDelayMSNone = 0; -const obciWriteIntervalDelayMSShort = 10; - -/** Impedance */ -const obciImpedanceTextBad = 'bad'; -const obciImpedanceTextNone = 'none'; -const obciImpedanceTextGood = 'good'; -const obciImpedanceTextInit = 'init'; -const obciImpedanceTextOk = 'ok'; - -const obciImpedanceThresholdGoodMin = 0; -const obciImpedanceThresholdGoodMax = 5000; -const obciImpedanceThresholdOkMin = 5001; -const obciImpedanceThresholdOkMax = 10000; -const obciImpedanceThresholdBadMin = 10001; -const obciImpedanceThresholdBadMax = 1000000; - -const obciImpedanceSeriesResistor = 2200; // There is a 2.2 k Ohm series resistor that must be subtracted - -/** Simulator */ -const obciSimulatorPortName = 'OpenBCISimulator'; - -/** -* Stream packet types/codes -*/ -const obciStreamPacketStandardAccel = 0; // 0000 -const obciStreamPacketStandardRawAux = 1; // 0001 -const obciStreamPacketUserDefinedType = 2; // 0010 -const obciStreamPacketAccelTimeSyncSet = 3; // 0011 -const obciStreamPacketAccelTimeSynced = 4; // 0100 -const obciStreamPacketRawAuxTimeSyncSet = 5; // 0101 -const obciStreamPacketRawAuxTimeSynced = 6; // 0110 - -/** Time from board */ -const obciStreamPacketTimeByteSize = 4; - -/** Time synced with accel packet */ -const obciAccelAxisX = 7; -const obciAccelAxisY = 8; -const obciAccelAxisZ = 9; - -/** Firmware version indicator */ -const obciFirmwareV1 = 'v1'; -const obciFirmwareV2 = 'v2'; - -/** Parse */ -const obciParseDaisy = 'Daisy'; -const obciParseFirmware = 'v2'; -const obciParseFailure = 'Failure'; -const obciParseEOT = '$$$'; -const obciParseSuccess = 'Success'; - -/** Used in parsing incoming serial data */ -const obciParsingChannelSettings = 2; -const obciParsingEOT = 4; -const obciParsingNormal = 3; -const obciParsingReset = 0; -const obciParsingTimeSyncSent = 1; - -/** Timeouts */ -const obciTimeoutProcessBytes = 500; // 0.5 seconds - -/** Simulator Board Configurations */ -const obciSimulatorRawAux = 'rawAux'; -const obciSimulatorStandard = 'standard'; - -/** OpenBCI Radio Limits */ -const obciRadioChannelMax = 25; -const obciRadioChannelMin = 1; -const obciRadioPollTimeMax = 255; -const obciRadioPollTimeMin = 0; - -/** Time sync stuff */ -const obciTimeSyncArraySize = 10; -const obciTimeSyncMultiplierWithSyncConf = 0.9; -const obciTimeSyncMultiplierWithoutSyncConf = 0.75; -const obciTimeSyncThresholdTransFailureMS = 10; // ms - -/** Baud Rates */ -const obciRadioBaudRateDefault = 115200; -const obciRadioBaudRateDefaultStr = 'default'; -const obciRadioBaudRateFast = 230400; -const obciRadioBaudRateFastStr = 'fast'; - -/** Emitters */ -const obciEmitterClose = 'close'; -const obciEmitterDroppedPacket = 'droppedPacket'; -const obciEmitterEot = 'eot'; -const obciEmitterError = 'error'; -const obciEmitterHardSet = 'hardSet'; -const obciEmitterImpedanceArray = 'impedanceArray'; -const obciEmitterMessage = 'message'; -const obciEmitterQuery = 'query'; -const obciEmitterRawDataPacket = 'rawDataPacket'; -const obciEmitterReady = 'ready'; -const obciEmitterSample = 'sample'; -const obciEmitterSynced = 'synced'; - -module.exports = { - /** Turning channels off */ - OBCIChannelOff1: obciChannelOff1, - OBCIChannelOff2: obciChannelOff2, - OBCIChannelOff3: obciChannelOff3, - OBCIChannelOff4: obciChannelOff4, - OBCIChannelOff5: obciChannelOff5, - OBCIChannelOff6: obciChannelOff6, - OBCIChannelOff7: obciChannelOff7, - OBCIChannelOff8: obciChannelOff8, - OBCIChannelOff9: obciChannelOff9, - OBCIChannelOff10: obciChannelOff10, - OBCIChannelOff11: obciChannelOff11, - OBCIChannelOff12: obciChannelOff12, - OBCIChannelOff13: obciChannelOff13, - OBCIChannelOff14: obciChannelOff14, - OBCIChannelOff15: obciChannelOff15, - OBCIChannelOff16: obciChannelOff16, - /** - * Purpose: To get the proper command to turn a channel off - * @param channelNumber - A number (1-16) of the desired channel - * @returns {Promise} - */ - commandChannelOff: function (channelNumber) { - return new Promise(function (resolve, reject) { - switch (channelNumber) { - case 1: - resolve(obciChannelOff1); - break; - case 2: - resolve(obciChannelOff2); - break; - case 3: - resolve(obciChannelOff3); - break; - case 4: - resolve(obciChannelOff4); - break; - case 5: - resolve(obciChannelOff5); - break; - case 6: - resolve(obciChannelOff6); - break; - case 7: - resolve(obciChannelOff7); - break; - case 8: - resolve(obciChannelOff8); - break; - case 9: - resolve(obciChannelOff9); - break; - case 10: - resolve(obciChannelOff10); - break; - case 11: - resolve(obciChannelOff11); - break; - case 12: - resolve(obciChannelOff12); - break; - case 13: - resolve(obciChannelOff13); - break; - case 14: - resolve(obciChannelOff14); - break; - case 15: - resolve(obciChannelOff15); - break; - case 16: - resolve(obciChannelOff16); - break; - default: - reject('Error [commandChannelOff]: Invalid Channel Number'); - break; - } - }); - }, - /** Turning channels on */ - OBCIChannelOn1: obciChannelOn1, - OBCIChannelOn2: obciChannelOn2, - OBCIChannelOn3: obciChannelOn3, - OBCIChannelOn4: obciChannelOn4, - OBCIChannelOn5: obciChannelOn5, - OBCIChannelOn6: obciChannelOn6, - OBCIChannelOn7: obciChannelOn7, - OBCIChannelOn8: obciChannelOn8, - OBCIChannelOn9: obciChannelOn9, - OBCIChannelOn10: obciChannelOn10, - OBCIChannelOn11: obciChannelOn11, - OBCIChannelOn12: obciChannelOn12, - OBCIChannelOn13: obciChannelOn13, - OBCIChannelOn14: obciChannelOn14, - OBCIChannelOn15: obciChannelOn15, - OBCIChannelOn16: obciChannelOn16, - commandChannelOn: function (channelNumber) { - return new Promise(function (resolve, reject) { - switch (channelNumber) { - case 1: - resolve(obciChannelOn1); - break; - case 2: - resolve(obciChannelOn2); - break; - case 3: - resolve(obciChannelOn3); - break; - case 4: - resolve(obciChannelOn4); - break; - case 5: - resolve(obciChannelOn5); - break; - case 6: - resolve(obciChannelOn6); - break; - case 7: - resolve(obciChannelOn7); - break; - case 8: - resolve(obciChannelOn8); - break; - case 9: - resolve(obciChannelOn9); - break; - case 10: - resolve(obciChannelOn10); - break; - case 11: - resolve(obciChannelOn11); - break; - case 12: - resolve(obciChannelOn12); - break; - case 13: - resolve(obciChannelOn13); - break; - case 14: - resolve(obciChannelOn14); - break; - case 15: - resolve(obciChannelOn15); - break; - case 16: - resolve(obciChannelOn16); - break; - default: - reject('Error [commandChannelOn]: Invalid Channel Number'); - break; - } - }); - }, - /** Test Signal Control Commands */ - OBCITestSignalConnectToDC: obciTestSignalConnectToDC, - OBCITestSignalConnectToGround: obciTestSignalConnectToGround, - OBCITestSignalConnectToPulse1xFast: obciTestSignalConnectToPulse1xFast, - OBCITestSignalConnectToPulse1xSlow: obciTestSignalConnectToPulse1xSlow, - OBCITestSignalConnectToPulse2xFast: obciTestSignalConnectToPulse2xFast, - OBCITestSignalConnectToPulse2xSlow: obciTestSignalConnectToPulse2xSlow, - getTestSignalCommand: (signal) => { - return new Promise((resolve, reject) => { - switch (signal) { - case 'dc': - resolve(obciTestSignalConnectToDC); - break; - case 'ground': - resolve(obciTestSignalConnectToGround); - break; - case 'pulse1xFast': - resolve(obciTestSignalConnectToPulse1xFast); - break; - case 'pulse1xSlow': - resolve(obciTestSignalConnectToPulse1xSlow); - break; - case 'pulse2xFast': - resolve(obciTestSignalConnectToPulse2xFast); - break; - case 'pulse2xSlow': - resolve(obciTestSignalConnectToPulse2xSlow); - break; - case 'none': - resolve(obciChannelDefaultAllSet); - break; - default: - reject('Invalid selection! Check your spelling.'); - break; - } - }); - }, - /** Channel Setting Commands */ - OBCIChannelCmdADCNormal: obciChannelCmdADCNormal, - OBCIChannelCmdADCShorted: obciChannelCmdADCShorted, - OBCIChannelCmdADCBiasDRP: obciChannelCmdADCBiasDRP, - OBCIChannelCmdADCBiasDRN: obciChannelCmdADCBiasDRN, - OBCIChannelCmdADCBiasMethod: obciChannelCmdADCBiasMethod, - OBCIChannelCmdADCMVDD: obciChannelCmdADCMVDD, - OBCIChannelCmdADCTemp: obciChannelCmdADCTemp, - OBCIChannelCmdADCTestSig: obciChannelCmdADCTestSig, - OBCIChannelCmdBiasInclude: obciChannelCmdBiasInclude, - OBCIChannelCmdBiasRemove: obciChannelCmdBiasRemove, - OBCIChannelCmdChannel1: obciChannelCmdChannel1, - OBCIChannelCmdChannel2: obciChannelCmdChannel2, - OBCIChannelCmdChannel3: obciChannelCmdChannel3, - OBCIChannelCmdChannel4: obciChannelCmdChannel4, - OBCIChannelCmdChannel5: obciChannelCmdChannel5, - OBCIChannelCmdChannel6: obciChannelCmdChannel6, - OBCIChannelCmdChannel7: obciChannelCmdChannel7, - OBCIChannelCmdChannel8: obciChannelCmdChannel8, - OBCIChannelCmdChannel9: obciChannelCmdChannel9, - OBCIChannelCmdChannel10: obciChannelCmdChannel10, - OBCIChannelCmdChannel11: obciChannelCmdChannel11, - OBCIChannelCmdChannel12: obciChannelCmdChannel12, - OBCIChannelCmdChannel13: obciChannelCmdChannel13, - OBCIChannelCmdChannel14: obciChannelCmdChannel14, - OBCIChannelCmdChannel15: obciChannelCmdChannel15, - OBCIChannelCmdChannel16: obciChannelCmdChannel16, - commandChannelForCmd, - OBCIChannelCmdGain1: obciChannelCmdGain1, - OBCIChannelCmdGain2: obciChannelCmdGain2, - OBCIChannelCmdGain4: obciChannelCmdGain4, - OBCIChannelCmdGain6: obciChannelCmdGain6, - OBCIChannelCmdGain8: obciChannelCmdGain8, - OBCIChannelCmdGain12: obciChannelCmdGain12, - OBCIChannelCmdGain24: obciChannelCmdGain24, - commandForGain, - OBCIChannelCmdLatch: obciChannelCmdLatch, - OBCIChannelCmdPowerOff: obciChannelCmdPowerOff, - OBCIChannelCmdPowerOn: obciChannelCmdPowerOn, - OBCIChannelCmdSet: obciChannelCmdSet, - OBCIChannelCmdSRB1Connect: obciChannelCmdSRB1Connect, - OBCIChannelCmdSRB1Diconnect: obciChannelCmdSRB1Diconnect, - OBCIChannelCmdSRB2Connect: obciChannelCmdSRB2Connect, - OBCIChannelCmdSRB2Diconnect: obciChannelCmdSRB2Diconnect, - /** Channel Settings Object */ - channelSettingsObjectDefault, - channelSettingsArrayInit: (numberOfChannels) => { - var newChannelSettingsArray = []; - for (var i = 0; i < numberOfChannels; i++) { - newChannelSettingsArray.push(channelSettingsObjectDefault(i)); - } - return newChannelSettingsArray; - }, - /** Channel Setting Helper Strings */ - OBCIStringADCNormal: obciStringADCNormal, - OBCIStringADCShorted: obciStringADCShorted, - OBCIStringADCBiasMethod: obciStringADCBiasMethod, - OBCIStringADCMvdd: obciStringADCMvdd, - OBCIStringADCTemp: obciStringADCTemp, - OBCIStringADCTestSig: obciStringADCTestSig, - OBCIStringADCBiasDrp: obciStringADCBiasDrp, - OBCIStringADCBiasDrn: obciStringADCBiasDrn, - /** - * @description To convert a string like 'normal' to the correct command (i.e. '1') - * @param adcString - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ - commandForADCString, - /** Default Channel Settings */ - OBCIChannelDefaultAllSet: obciChannelDefaultAllSet, - OBCIChannelDefaultAllGet: obciChannelDefaultAllGet, - /** LeadOff Impedance Commands */ - OBCIChannelImpedanceLatch: obciChannelImpedanceLatch, - OBCIChannelImpedanceSet: obciChannelImpedanceSet, - OBCIChannelImpedanceTestSignalApplied: obciChannelImpedanceTestSignalApplied, - OBCIChannelImpedanceTestSignalAppliedNot: obciChannelImpedanceTestSignalAppliedNot, - /** SD card Commands */ - OBCISDLogForHour1: obciSDLogForHour1, - OBCISDLogForHour2: obciSDLogForHour2, - OBCISDLogForHour4: obciSDLogForHour4, - OBCISDLogForHour12: obciSDLogForHour12, - OBCISDLogForHour24: obciSDLogForHour24, - OBCISDLogForMin5: obciSDLogForMin5, - OBCISDLogForMin15: obciSDLogForMin15, - OBCISDLogForMin30: obciSDLogForMin30, - OBCISDLogForSec14: obciSDLogForSec14, - OBCISDLogStop: obciSDLogStop, - /** SD Card String Commands */ - OBCIStringSDHour1: obciStringSDHour1, - OBCIStringSDHour2: obciStringSDHour2, - OBCIStringSDHour4: obciStringSDHour4, - OBCIStringSDHour12: obciStringSDHour12, - OBCIStringSDHour24: obciStringSDHour24, - OBCIStringSDMin5: obciStringSDMin5, - OBCIStringSDMin15: obciStringSDMin15, - OBCIStringSDMin30: obciStringSDMin30, - OBCIStringSDSec14: obciStringSDSec14, - /** - * @description Converts a sd string into the proper setting. - * @param stringCommand {String} - The length of time you want to record to the SD for. - * @returns {Promise} The command to send to the Board, returns an error on improper `stringCommand` - */ - sdSettingForString: (stringCommand) => { - return new Promise((resolve, reject) => { - switch (stringCommand) { - case obciStringSDHour1: - resolve(obciSDLogForHour1); - break; - case obciStringSDHour2: - resolve(obciSDLogForHour2); - break; - case obciStringSDHour4: - resolve(obciSDLogForHour4); - break; - case obciStringSDHour12: - resolve(obciSDLogForHour12); - break; - case obciStringSDHour24: - resolve(obciSDLogForHour24); - break; - case obciStringSDMin5: - resolve(obciSDLogForMin5); - break; - case obciStringSDMin15: - resolve(obciSDLogForMin15); - break; - case obciStringSDMin30: - resolve(obciSDLogForMin30); - break; - case obciStringSDSec14: - resolve(obciSDLogForSec14); - break; - default: - reject(new Error(TypeError)); - break; - - } - }); - }, - /** Stream Data Commands */ - OBCIStreamStart: obciStreamStart, - OBCIStreamStop: obciStreamStop, - /** Miscellaneous */ - OBCIMiscQueryRegisterSettings: obciMiscQueryRegisterSettings, - OBCIMiscQueryRegisterSettingsChannel1: obciMiscQueryRegisterSettingsChannel1, - OBCIMiscQueryRegisterSettingsChannel2: obciMiscQueryRegisterSettingsChannel2, - OBCIMiscQueryRegisterSettingsChannel3: obciMiscQueryRegisterSettingsChannel3, - OBCIMiscQueryRegisterSettingsChannel4: obciMiscQueryRegisterSettingsChannel4, - OBCIMiscQueryRegisterSettingsChannel5: obciMiscQueryRegisterSettingsChannel5, - OBCIMiscQueryRegisterSettingsChannel6: obciMiscQueryRegisterSettingsChannel6, - OBCIMiscQueryRegisterSettingsChannel7: obciMiscQueryRegisterSettingsChannel7, - OBCIMiscQueryRegisterSettingsChannel8: obciMiscQueryRegisterSettingsChannel8, - channelSettingsKeyForChannel: channelNumber => { - return new Promise((resolve, reject) => { - switch (channelNumber) { - case 1: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel1)); - break; - case 2: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel2)); - break; - case 3: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel3)); - break; - case 4: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel4)); - break; - case 5: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel5)); - break; - case 6: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel6)); - break; - case 7: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel7)); - break; - case 8: - resolve(new Buffer(obciMiscQueryRegisterSettingsChannel8)); - break; - default: - reject('Invalid channel number'); - break; - } - }); - }, - OBCIMiscSoftReset: obciMiscSoftReset, - /** 16 Channel Commands */ - OBCIChannelMaxNumber8: obciChannelMaxNumber8, - OBCIChannelMaxNumber16: obciChannelMaxNumber16, - OBCIChannelMaxNumber8NoDaisyToRemove: obciChannelMaxNumber8NoDaisyToRemove, - OBCIChannelMaxNumber8SuccessDaisyRemoved: obciChannelMaxNumber8SuccessDaisyRemoved, - OBCIChannelMaxNumber16DaisyAlreadyAttached: obciChannelMaxNumber16DaisyAlreadyAttached, - OBCIChannelMaxNumber16DaisyAttached: obciChannelMaxNumber16DaisyAttached, - OBCIChannelMaxNumber16NoDaisyAttached: obciChannelMaxNumber16NoDaisyAttached, - /** Filters */ - OBCIFilterDisable: obciFilterDisable, - OBCIFilterEnable: obciFilterEnable, - /** Triggers */ - OBCITrigger: obciTrigger, - /** Possible number of channels */ - OBCINumberOfChannelsDaisy: obciNumberOfChannelsDaisy, - OBCINumberOfChannelsDefault: obciNumberOfChannelsDefault, - OBCINumberOfChannelsGanglion: obciNumberOfChannelsGanglion, - /** Possible OpenBCI board types */ - OBCIBoardDaisy: obciBoardDaisy, - OBCIBoardDefault: obciBoardDefault, - OBCIBoardGanglion: obciBoardGanglion, - numberOfChannelsForBoardType: boardType => { - switch (boardType) { - case obciBoardDaisy: - return obciNumberOfChannelsDaisy; - case obciBoardGanglion: - return obciNumberOfChannelsGanglion; - default: - return obciNumberOfChannelsDefault; - } - }, - /** Possible Sample Rates */ - OBCISampleRate125: obciSampleRate125, - OBCISampleRate200: obciSampleRate200, - OBCISampleRate250: obciSampleRate250, - /** Max sample number */ - OBCISampleNumberMax: obciSampleNumberMax, - /** Packet Size */ - OBCIPacketSize: obciPacketSize, - /** Notable Bytes */ - OBCIByteStart: obciByteStart, - OBCIByteStop: obciByteStop, - /** Errors */ - OBCIErrorNobleAlreadyScanning: errorNobleAlreadyScanning, - OBCIErrorNobleNotAlreadyScanning: errorNobleNotAlreadyScanning, - OBCIErrorNobleNotInPoweredOnState: errorNobleNotInPoweredOnState, - OBCIErrorInvalidByteLength: errorInvalidByteLength, - OBCIErrorInvalidByteStart: errorInvalidByteStart, - OBCIErrorInvalidByteStop: errorInvalidByteStop, - OBCIErrorInvalidType: errorInvalidType, - OBCIErrorTimeSyncIsNull: errorTimeSyncIsNull, - OBCIErrorTimeSyncNoComma: errorTimeSyncNoComma, - OBCIErrorUndefinedOrNullInput: errorUndefinedOrNullInput, - /** Max Master Buffer Size */ - OBCIMasterBufferSize: obciMasterBufferSize, - /** Impedance Calculation Variables */ - OBCILeadOffDriveInAmps: obciLeadOffDriveInAmps, - OBCILeadOffFrequencyHz: obciLeadOffFrequencyHz, - /** Channel Setter Maker */ - getChannelSetter: channelSetter, - /** Impedance Setter Maker */ - getImpedanceSetter: impedanceSetter, - /** Command send delay */ - OBCIWriteIntervalDelayMSLong: obciWriteIntervalDelayMSLong, - OBCIWriteIntervalDelayMSNone: obciWriteIntervalDelayMSNone, - OBCIWriteIntervalDelayMSShort: obciWriteIntervalDelayMSShort, - /** Sync Clocks */ - OBCISyncTimeSent: obciSyncTimeSent, - OBCISyncTimeSet: obciSyncTimeSet, - /** Radio Key */ - OBCIRadioKey: obciRadioKey, - /** Radio Commands */ - OBCIRadioCmdChannelGet: obciRadioCmdChannelGet, - OBCIRadioCmdChannelSet: obciRadioCmdChannelSet, - OBCIRadioCmdChannelSetOverride: obciRadioCmdChannelSetOverride, - OBCIRadioCmdPollTimeGet: obciRadioCmdPollTimeGet, - OBCIRadioCmdPollTimeSet: obciRadioCmdPollTimeSet, - OBCIRadioCmdBaudRateSetDefault: obciRadioCmdBaudRateSetDefault, - OBCIRadioCmdBaudRateSetFast: obciRadioCmdBaudRateSetFast, - OBCIRadioCmdSystemStatus: obciRadioCmdSystemStatus, - /** Impedance */ - OBCIImpedanceTextBad: obciImpedanceTextBad, - OBCIImpedanceTextGood: obciImpedanceTextGood, - OBCIImpedanceTextInit: obciImpedanceTextInit, - OBCIImpedanceTextOk: obciImpedanceTextOk, - OBCIImpedanceTextNone: obciImpedanceTextNone, - OBCIImpedanceThresholdBadMax: obciImpedanceThresholdBadMax, - OBCIImpedanceSeriesResistor: obciImpedanceSeriesResistor, - getTextForRawImpedance: (value) => { - if (value > obciImpedanceThresholdGoodMin && value < obciImpedanceThresholdGoodMax) { - return obciImpedanceTextGood; - } else if (value > obciImpedanceThresholdOkMin && value < obciImpedanceThresholdOkMax) { - return obciImpedanceTextOk; - } else if (value > obciImpedanceThresholdBadMin && value < obciImpedanceThresholdBadMax) { - return obciImpedanceTextBad; - } else { - return obciImpedanceTextNone; - } - }, - /** Simulator */ - OBCISimulatorPortName: obciSimulatorPortName, - /** - * Stream packet types/codes - */ - OBCIStreamPacketStandardAccel: obciStreamPacketStandardAccel, - OBCIStreamPacketStandardRawAux: obciStreamPacketStandardRawAux, - OBCIStreamPacketUserDefinedType: obciStreamPacketUserDefinedType, - OBCIStreamPacketAccelTimeSyncSet: obciStreamPacketAccelTimeSyncSet, - OBCIStreamPacketAccelTimeSynced: obciStreamPacketAccelTimeSynced, - OBCIStreamPacketRawAuxTimeSyncSet: obciStreamPacketRawAuxTimeSyncSet, - OBCIStreamPacketRawAuxTimeSynced: obciStreamPacketRawAuxTimeSynced, - /** fun funcs */ - isNumber, - isBoolean, - isString, - isUndefined, - isNull, - /** OpenBCI V3 Standard Packet Positions */ - OBCIPacketPositionStartByte: obciPacketPositionStartByte, - OBCIPacketPositionStopByte: obciPacketPositionStopByte, - OBCIPacketPositionStartAux: obciPacketPositionStartAux, - OBCIPacketPositionStopAux: obciPacketPositionStopAux, - OBCIPacketPositionChannelDataStart: obciPacketPositionChannelDataStart, - OBCIPacketPositionChannelDataStop: obciPacketPositionChannelDataStop, - OBCIPacketPositionSampleNumber: obciPacketPositionSampleNumber, - OBCIPacketPositionTimeSyncAuxStart: obciPacketPositionTimeSyncAuxStart, - OBCIPacketPositionTimeSyncAuxStop: obciPacketPositionTimeSyncAuxStop, - OBCIPacketPositionTimeSyncTimeStart: obciPacketPositionTimeSyncTimeStart, - OBCIPacketPositionTimeSyncTimeStop: obciPacketPositionTimeSyncTimeStop, - /** Possible Simulator Line Noise injections */ - OBCISimulatorLineNoiseHz60: obciSimulatorLineNoiseHz60, - OBCISimulatorLineNoiseHz50: obciSimulatorLineNoiseHz50, - OBCISimulatorLineNoiseNone: obciSimulatorLineNoiseNone, - /** Possible Simulator Fragmentation modes */ - OBCISimulatorFragmentationRandom: obciSimulatorFragmentationRandom, - OBCISimulatorFragmentationFullBuffers: obciSimulatorFragmentationFullBuffers, - OBCISimulatorFragmentationOneByOne: obciSimulatorFragmentationOneByOne, - OBCISimulatorFragmentationNone: obciSimulatorFragmentationNone, - /** Firmware version indicator */ - OBCIFirmwareV1: obciFirmwareV1, - OBCIFirmwareV2: obciFirmwareV2, - /** Time synced accel packet */ - OBCIAccelAxisX: obciAccelAxisX, - OBCIAccelAxisY: obciAccelAxisY, - OBCIAccelAxisZ: obciAccelAxisZ, - /** Time from board */ - OBCIStreamPacketTimeByteSize: obciStreamPacketTimeByteSize, - /** Parse */ - OBCIParseDaisy: obciParseDaisy, - OBCIParseFailure: obciParseFailure, - OBCIParseFirmware: obciParseFirmware, - OBCIParseEOT: obciParseEOT, - OBCIParseSuccess: obciParseSuccess, - /** Used in parsing incoming serial data */ - OBCIParsingChannelSettings: obciParsingChannelSettings, - OBCIParsingEOT: obciParsingEOT, - OBCIParsingNormal: obciParsingNormal, - OBCIParsingReset: obciParsingReset, - OBCIParsingTimeSyncSent: obciParsingTimeSyncSent, - /** Timeouts */ - OBCITimeoutProcessBytes: obciTimeoutProcessBytes, - /** Simulator Board Configurations */ - OBCISimulatorRawAux: obciSimulatorRawAux, - OBCISimulatorStandard: obciSimulatorStandard, - /** Radio Channel Limits */ - OBCIRadioChannelMax: obciRadioChannelMax, - OBCIRadioChannelMin: obciRadioChannelMin, - OBCIRadioPollTimeMax: obciRadioPollTimeMax, - OBCIRadioPollTimeMin: obciRadioPollTimeMin, - /** Time sync stuff */ - OBCITimeSyncArraySize: obciTimeSyncArraySize, - OBCITimeSyncMultiplierWithSyncConf: obciTimeSyncMultiplierWithSyncConf, - OBCITimeSyncMultiplierWithoutSyncConf: obciTimeSyncMultiplierWithoutSyncConf, - OBCITimeSyncThresholdTransFailureMS: obciTimeSyncThresholdTransFailureMS, - /** Baud Rates */ - OBCIRadioBaudRateDefault: obciRadioBaudRateDefault, - OBCIRadioBaudRateDefaultStr: obciRadioBaudRateDefaultStr, - OBCIRadioBaudRateFast: obciRadioBaudRateFast, - OBCIRadioBaudRateFastStr: obciRadioBaudRateFastStr, - getVersionNumber, - /** Emitters */ - OBCIEmitterClose: obciEmitterClose, - OBCIEmitterDroppedPacket: obciEmitterDroppedPacket, - OBCIEmitterEot: obciEmitterEot, - OBCIEmitterError: obciEmitterError, - OBCIEmitterHardSet: obciEmitterHardSet, - OBCIEmitterImpedanceArray: obciEmitterImpedanceArray, - OBCIEmitterMessage: obciEmitterMessage, - OBCIEmitterQuery: obciEmitterQuery, - OBCIEmitterRawDataPacket: obciEmitterRawDataPacket, - OBCIEmitterReady: obciEmitterReady, - OBCIEmitterSample: obciEmitterSample, - OBCIEmitterSynced: obciEmitterSynced -}; - -/** -* @description To add a usability abstraction layer above channel setting commands. Due to the -* extensive and highly specific nature of the channel setting command chain, this -* will take several different human readable inputs and merge to one array filled -* with the correct commands, prime for sending directly to the write command. -* @param channelNumber - Number (1-16) -* @param powerDown - Bool (true -> OFF, false -> ON (default)) -* turns the channel on or off -* @param gain - Number (1,2,4,6,8,12,24(default)) -* sets the gain for the channel -* @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) -* selects the ADC channel input source -* @param bias - Bool (true -> Include in bias (default), false -> remove from bias) -* selects to include the channel input in bias generation -* @param srb2 - Bool (true -> Connect this input to SRB2 (default), -* false -> Disconnect this input from SRB2) -* Select to connect (true) this channel's P input to the SRB2 pin. This closes -* a switch between P input and SRB2 for the given channel, and allows the -* P input to also remain connected to the ADC. -* @param srb1 - Bool (true -> connect all N inputs to SRB1, -* false -> Disconnect all N inputs from SRB1 (default)) -* Select to connect (true) all channels' N inputs to SRB1. This effects all pins, -* and disconnects all N inputs from the ADC. -* @returns {Promise} resolves {commandArray: array of commands to be sent, - newChannelSettingsObject: an updated channel settings object - to be stored in openBCIBoard.channelSettingsArray}, - rejects on bad input or no board -*/ -function channelSetter (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { - // Used to store and assemble the commands - var cmdPowerDown, - cmdBias, - cmdSrb2, - cmdSrb1; - - return new Promise(function (resolve, reject) { - // Validate the input - if (!isNumber(channelNumber)) reject("channelNumber must be of type 'number' "); - if (!isBoolean(powerDown)) reject("powerDown must be of type 'boolean' "); - if (!isNumber(gain)) reject("gain must be of type 'number' "); - if (!isString(inputType)) reject("inputType must be of type 'string' "); - if (!isBoolean(bias)) reject("bias must be of type 'boolean' "); - if (!isBoolean(srb2)) reject("srb1 must be of type 'boolean' "); - if (!isBoolean(srb1)) reject("srb2 must be of type 'boolean' "); - - // Set Channel Number - var p1 = commandChannelForCmd(channelNumber) - .catch(err => reject(err)); - - // Set POWER_DOWN - cmdPowerDown = powerDown ? obciChannelCmdPowerOff : obciChannelCmdPowerOn; - - // Set Gain - var p2 = commandForGain(gain) - .catch(err => reject(err)); - - // Set ADC string - var p3 = commandForADCString(inputType) - .catch(err => reject(err)); - - // Set BIAS - cmdBias = bias ? obciChannelCmdBiasInclude : obciChannelCmdBiasRemove; - - // Set SRB2 - cmdSrb2 = srb2 ? obciChannelCmdSRB2Connect : obciChannelCmdSRB2Diconnect; - - // Set SRB1 - cmdSrb1 = srb1 ? obciChannelCmdSRB1Connect : obciChannelCmdSRB1Diconnect; - - var newChannelSettingsObject = { - channelNumber: channelNumber, - powerDown: powerDown, - gain: gain, - inputType: inputType, - bias: bias, - srb2: srb2, - srb1: srb1 - }; - - Promise.all([p1, p2, p3]).then(function (values) { - var outputArray = [ - obciChannelCmdSet, - values[0], - cmdPowerDown, - values[1], - values[2], - cmdBias, - cmdSrb2, - cmdSrb1, - obciChannelCmdLatch - ]; - resolve({commandArray: outputArray, newChannelSettingsObject: newChannelSettingsObject}); - }); - }); -} - -/** -* @description To build the array of commands to send to the board to measure impedance -* @param channelNumber -* @param pInputApplied - Bool (true -> Test Signal Applied, false -> Test Signal Not Applied (default)) -* applies the test signal to the P input -* @param nInputApplied - Bool (true -> Test Signal Applied, false -> Test Signal Not Applied (default)) -* applies the test signal to the N input -* @returns {Promise} - fulfilled will contain an array of comamnds -*/ -function impedanceSetter (channelNumber, pInputApplied, nInputApplied) { - var cmdNInputApplied, - cmdPInputApplied; - return new Promise((resolve, reject) => { - // validate inputs - if (!isNumber(channelNumber)) reject("channelNumber must be of type 'number' "); - if (!isBoolean(pInputApplied)) reject("pInputApplied must be of type 'boolean' "); - if (!isBoolean(nInputApplied)) reject("nInputApplied must be of type 'boolean' "); - - // Set pInputApplied - cmdPInputApplied = pInputApplied ? obciChannelImpedanceTestSignalApplied : obciChannelImpedanceTestSignalAppliedNot; - - // Set nInputApplied - cmdNInputApplied = nInputApplied ? obciChannelImpedanceTestSignalApplied : obciChannelImpedanceTestSignalAppliedNot; - - // Set Channel Number - commandChannelForCmd(channelNumber).then(command => { - var outputArray = [ - obciChannelImpedanceSet, - command, - cmdPInputApplied, - cmdNInputApplied, - obciChannelImpedanceLatch - ]; - // console.log(outputArray) - resolve(outputArray); - }).catch(err => reject(err)); - }); -} - -function isNumber (input) { - return (typeof input === 'number'); -} -function isBoolean (input) { - return (typeof input === 'boolean'); -} -function isString (input) { - return (typeof input === 'string'); -} -function isUndefined (input) { - return (typeof input === 'undefined'); -} -function isNull (input) { - return input === null; -} - -function commandForADCString (adcString) { - return new Promise(function (resolve, reject) { - switch (adcString) { - case obciStringADCNormal: - resolve(obciChannelCmdADCNormal); - break; - case obciStringADCShorted: - resolve(obciChannelCmdADCShorted); - break; - case obciStringADCBiasMethod: - resolve(obciChannelCmdADCBiasMethod); - break; - case obciStringADCMvdd: - resolve(obciChannelCmdADCMVDD); - break; - case obciStringADCTemp: - resolve(obciChannelCmdADCTemp); - break; - case obciStringADCTestSig: - resolve(obciChannelCmdADCTestSig); - break; - case obciStringADCBiasDrp: - resolve(obciChannelCmdADCBiasDRP); - break; - case obciStringADCBiasDrn: - resolve(obciChannelCmdADCBiasDRN); - break; - default: - reject('Invalid ADC string'); - break; - } - }); -} - -function commandForGain (gainSetting) { - return new Promise(function (resolve, reject) { - switch (gainSetting) { - case 1: - resolve(obciChannelCmdGain1); - break; - case 2: - resolve(obciChannelCmdGain2); - break; - case 4: - resolve(obciChannelCmdGain4); - break; - case 6: - resolve(obciChannelCmdGain6); - break; - case 8: - resolve(obciChannelCmdGain8); - break; - case 12: - resolve(obciChannelCmdGain12); - break; - case 24: - resolve(obciChannelCmdGain24); - break; - default: - reject('Invalid gain setting of ' + gainSetting + ' tisk tisk, gain must be (1,2,4,6,8,12,24)'); - break; - } - }); -} - -function commandChannelForCmd (channelNumber) { - return new Promise(function (resolve, reject) { - switch (channelNumber) { - case 1: - resolve(obciChannelCmdChannel1); - break; - case 2: - resolve(obciChannelCmdChannel2); - break; - case 3: - resolve(obciChannelCmdChannel3); - break; - case 4: - resolve(obciChannelCmdChannel4); - break; - case 5: - resolve(obciChannelCmdChannel5); - break; - case 6: - resolve(obciChannelCmdChannel6); - break; - case 7: - resolve(obciChannelCmdChannel7); - break; - case 8: - resolve(obciChannelCmdChannel8); - break; - case 9: - resolve(obciChannelCmdChannel9); - break; - case 10: - resolve(obciChannelCmdChannel10); - break; - case 11: - resolve(obciChannelCmdChannel11); - break; - case 12: - resolve(obciChannelCmdChannel12); - break; - case 13: - resolve(obciChannelCmdChannel13); - break; - case 14: - resolve(obciChannelCmdChannel14); - break; - case 15: - resolve(obciChannelCmdChannel15); - break; - case 16: - resolve(obciChannelCmdChannel16); - break; - default: - reject('Invalid channel number'); - break; - } - }); -} -function channelSettingsObjectDefault (channelNumber) { - return { - channelNumber: channelNumber, - powerDown: false, - gain: 24, - inputType: obciStringADCNormal, - bias: true, - srb2: true, - srb1: false - }; -} - -/** -* @description This function is used to extract the major version from a github -* version string. -* @returns {Number} The major version number -*/ -function getVersionNumber (versionStr) { - return Number(versionStr[1]); -} diff --git a/openBCICyton.js b/openBCICyton.js index 25bd86e..5eb94d4 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -3,8 +3,10 @@ const EventEmitter = require('events').EventEmitter; const util = require('util'); const stream = require('stream'); const SerialPort = require('serialport'); -const openBCISample = require('./openBCISample'); -const k = openBCISample.k; +const OpenBCIUtilities = require('openbci-utilities'); +const obciUtils = OpenBCIUtilities.Utilities; +const k = OpenBCIUtilities.Constants; +const obciDebug = OpenBCIUtilities.Debug; const openBCISimulator = require('./openBCISimulator'); const Sntp = require('sntp'); const bufferEqual = require('buffer-equal'); @@ -179,7 +181,7 @@ function OpenBCICyton (options) { this.buffer = null; this.masterBuffer = masterBufferMaker(); // Objects - this.impedanceTest = openBCISample.impedanceTestObjDefault(); + this.impedanceTest = obciUtils.impedanceTestObjDefault(); this.info = { boardType: this.options.boardType, sampleRate: k.OBCISampleRate125, @@ -206,7 +208,7 @@ function OpenBCICyton (options) { // Numbers this.badPackets = 0; this.curParsingMode = k.OBCIParsingReset; - this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); + this.impedanceArray = obciUtils.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); this.previousSampleNumber = -1; this.sampleCount = 0; this.timeOfPacketArrival = 0; @@ -540,7 +542,7 @@ OpenBCICyton.prototype.write = function (dataToWrite) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._writeAndDrain = function (data) { - this._debugBytes('>>>', data); + obciDebug.debugBytes('>>>', data); return new Promise((resolve, reject) => { if (!this.isConnected()) return reject('Serial port not open'); @@ -567,7 +569,7 @@ OpenBCICyton.prototype._writeAndDrain = function (data) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype.autoFindOpenBCIBoard = function () { - var serialPatterns = [ + const serialPatterns = [ { // mac comName: /usbserial-D/ }, @@ -665,7 +667,7 @@ OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { clearTimeout(badCommsTimeout); badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); } else { reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte @@ -715,7 +717,7 @@ OpenBCICyton.prototype.radioChannelSetHostOverride = function (channelNumber) { clearTimeout(badCommsTimeout); badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); } else { reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte @@ -762,7 +764,7 @@ OpenBCICyton.prototype.radioChannelGet = function () { // Remove the timeout! clearTimeout(badCommsTimeout); badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { resolve({ channelNumber: data[data.length - 4], data: data @@ -810,7 +812,7 @@ OpenBCICyton.prototype.radioPollTimeGet = function () { clearTimeout(badCommsTimeout); badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { var pollTime = data[data.length - 4]; resolve(pollTime); } else { @@ -862,7 +864,7 @@ OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { clearTimeout(badCommsTimeout); badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); // Ditch the eot $$$ } else { reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte @@ -925,7 +927,7 @@ OpenBCICyton.prototype.radioBaudRateSet = function (speed) { } if (!this.isConnected()) { reject('Lost connection to device during baud set'); - } else if (openBCISample.isSuccessInBuffer(data)) { + } else if (obciUtils.isSuccessInBuffer(data)) { // Change the sample rate here if (this.options.simulate === false) { this.serial.update({baudRate: newBaudRateNum}, err => { @@ -984,7 +986,7 @@ OpenBCICyton.prototype.radioSystemStatusGet = function () { if (this.options.verbose) console.log(data.toString()); - if (openBCISample.isSuccessInBuffer(data)) { + if (obciUtils.isSuccessInBuffer(data)) { resolve(true); } else { resolve(false); @@ -1054,7 +1056,7 @@ OpenBCICyton.prototype.overrideInfoForBoardType = function (boardType) { this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; this.info.sampleRate = k.OBCISampleRate125; this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); - this.impedanceArray = openBCISample.impedanceArray(k.OBCINumberOfChannelsDaisy); + this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsDaisy); break; case k.OBCIBoardDefault: default: @@ -1062,7 +1064,7 @@ OpenBCICyton.prototype.overrideInfoForBoardType = function (boardType) { this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; this.info.sampleRate = k.OBCISampleRate250; this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); - this.impedanceArray = openBCISample.impedanceArray(k.OBCINumberOfChannelsDefault); + this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsDefault); break; } }; @@ -1394,7 +1396,7 @@ OpenBCICyton.prototype.impedanceTestChannels = function (arrayOfChannels) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype.impedanceTestChannel = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. .then(channelNumber => { @@ -1424,7 +1426,7 @@ OpenBCICyton.prototype.impedanceTestChannel = function (channelNumber) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype.impedanceTestChannelInputP = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. .then(channelNumber => { @@ -1448,7 +1450,7 @@ OpenBCICyton.prototype.impedanceTestChannelInputP = function (channelNumber) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype.impedanceTestChannelInputN = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); + this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. .then(channelNumber => { @@ -1596,8 +1598,8 @@ OpenBCICyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); - if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); + if (pInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); + if (nInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); setTimeout(() => { resolve(channelNumber); @@ -1706,7 +1708,7 @@ OpenBCICyton.prototype.syncClocks = function () { if (!this.isConnected()) return reject('Must be connected to the device'); if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj = obciUtils.newSyncObject(); this.sync.curSyncObj.timeSyncSent = this.time(); this.curParsingMode = k.OBCIParsingTimeSyncSent; this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); @@ -1735,7 +1737,7 @@ OpenBCICyton.prototype.syncClocksFull = function () { return resolve(syncObj); }; this.once('synced', this.sync.eventEmitter); - this.sync.curSyncObj = openBCISample.newSyncObject(); + this.sync.curSyncObj = obciUtils.newSyncObject(); this.sync.curSyncObj.timeSyncSent = this.time(); this.curParsingMode = k.OBCIParsingTimeSyncSent; this._writeAndDrain(k.OBCISyncTimeSet) @@ -1746,40 +1748,6 @@ OpenBCICyton.prototype.syncClocksFull = function () { }); }; -/** - * @description Output passed bytes on the console as a hexdump, if enabled - * @param prefix - label to show to the left of bytes - * @param data - bytes to output, a buffer or string - * @private - */ -OpenBCICyton.prototype._debugBytes = function (prefix, data) { - if (!this.options.debug) return; - - if (typeof data === 'string') data = new Buffer(data); - - console.log('Debug bytes:'); - - for (var j = 0; j < data.length;) { - var hexPart = ''; - var ascPart = ''; - for (var end = Math.min(data.length, j + 16); j < end; ++j) { - var byt = data[j]; - - var hex = ('0' + byt.toString(16)).slice(-2); - hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in - hexPart += hex; - - var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; - ascPart += asc; - } - - // pad to fixed width for alignment - hexPart = (hexPart + ' ').substring(0, 3 * 17); - - console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); - } -}; - /** * @description Consider the '_processBytes' method to be the work horse of this * entire framework. This method gets called any time there is new @@ -1793,7 +1761,7 @@ OpenBCICyton.prototype._debugBytes = function (prefix, data) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._processBytes = function (data) { - this._debugBytes(this.curParsingMode + '<<', data); + obciDebug.debugBytes(this.curParsingMode + '<<', data); // Concat old buffer var oldDataBuffer = null; @@ -1804,17 +1772,17 @@ OpenBCICyton.prototype._processBytes = function (data) { switch (this.curParsingMode) { case k.OBCIParsingEOT: - if (openBCISample.doesBufferHaveEOT(data)) { + if (obciUtils.doesBufferHaveEOT(data)) { this.curParsingMode = k.OBCIParsingNormal; this.emit(k.OBCIEmitterEot, data); - this.buffer = openBCISample.stripToEOTBuffer(data); + this.buffer = obciUtils.stripToEOTBuffer(data); } else { this.buffer = data; } break; case k.OBCIParsingReset: // Does the buffer have an EOT in it? - if (openBCISample.doesBufferHaveEOT(data)) { + if (obciUtils.doesBufferHaveEOT(data)) { this._processParseBufferForReset(data); if (this.options.hardSet) { if (this.getBoardType() !== this.options.boardType) { @@ -1829,7 +1797,7 @@ OpenBCICyton.prototype._processBytes = function (data) { } else { this.curParsingMode = k.OBCIParsingNormal; this.emit(k.OBCIEmitterReady); - this.buffer = openBCISample.stripToEOTBuffer(data); + this.buffer = obciUtils.stripToEOTBuffer(data); } } else { if (this.getBoardType() !== this.options.boardType && this.options.verbose) { @@ -1837,7 +1805,7 @@ OpenBCICyton.prototype._processBytes = function (data) { } this.curParsingMode = k.OBCIParsingNormal; this.emit(k.OBCIEmitterReady); - this.buffer = openBCISample.stripToEOTBuffer(data); + this.buffer = obciUtils.stripToEOTBuffer(data); } } else { this.buffer = data; @@ -1845,7 +1813,7 @@ OpenBCICyton.prototype._processBytes = function (data) { break; case k.OBCIParsingTimeSyncSent: // If there is only one match - if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { + if (obciUtils.isTimeSyncSetConfirmationInBuffer(data)) { if (this.options.verbose) console.log(`Found Time Sync Sent`); this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); this.curParsingMode = k.OBCIParsingNormal; @@ -1885,7 +1853,7 @@ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { if (dataBuffer[parsePosition] === k.OBCIByteStart) { // Now that we know the first is a head byte, let's see if the last one is a // tail byte 0xCx where x is the set of numbers from 0-F (hex) - if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { + if (obciUtils.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { /** We just qualified a raw packet */ // This could be a time set packet! this.timeOfPacketArrival = this.time(); @@ -1937,13 +1905,13 @@ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._processParseBufferForReset = function (dataBuffer) { - if (openBCISample.countADSPresent(dataBuffer) === 2) { + if (obciUtils.countADSPresent(dataBuffer) === 2) { this.overrideInfoForBoardType(k.OBCIBoardDaisy); } else { this.overrideInfoForBoardType(k.OBCIBoardDefault); } - if (openBCISample.findV2Firmware(dataBuffer)) { + if (obciUtils.findV2Firmware(dataBuffer)) { this.info.firmware = k.OBCIFirmwareV2; this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; } else { @@ -1961,12 +1929,12 @@ OpenBCICyton.prototype._processParseBufferForReset = function (dataBuffer) { OpenBCICyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { if (!rawDataPacketBuffer) return; if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; - var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); + var missedPacketArray = obciUtils.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); if (missedPacketArray) { this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); } this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; - var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); + var packetType = obciUtils.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); switch (packetType) { case k.OBCIStreamPacketStandardAccel: this._processPacketStandardAccel(rawDataPacketBuffer); @@ -2008,14 +1976,14 @@ OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { var impedanceArray; if (this.impedanceTest.continuousMode) { // console.log('running in continuous mode...') - // openBCISample.debugPrettyPrint(sampleObject) - impedanceArray = openBCISample.impedanceCalculateArray(sampleObject, this.impedanceTest); + // obciUtils.debugPrettyPrint(sampleObject) + impedanceArray = obciUtils.impedanceCalculateArray(sampleObject, this.impedanceTest); if (impedanceArray) { this.emit('impedanceArray', impedanceArray); } } else if (this.impedanceTest.onChannel !== 0) { // Only calculate impedance for one channel - impedanceArray = openBCISample.impedanceCalculateArray(sampleObject, this.impedanceTest); + impedanceArray = obciUtils.impedanceCalculateArray(sampleObject, this.impedanceTest); if (impedanceArray) { this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; } @@ -2030,7 +1998,7 @@ OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { */ OpenBCICyton.prototype._processPacketStandardAccel = function (rawPacket) { try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); + let sample = obciUtils.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); sample.rawPacket = rawPacket; this._finalizeNewSample(sample); } catch (err) { @@ -2048,7 +2016,7 @@ OpenBCICyton.prototype._processPacketStandardAccel = function (rawPacket) { */ OpenBCICyton.prototype._processPacketStandardRawAux = function (rawPacket) { try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); + let sample = obciUtils.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); sample.rawPacket = rawPacket; this._finalizeNewSample(sample); } catch (err) { @@ -2073,7 +2041,7 @@ OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPa * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} */ var getBadObject = (err) => { - var badObject = openBCISample.newSyncObject(); + var badObject = obciUtils.newSyncObject(); badObject.timeOffsetMaster = this.sync.timeOffsetMaster; // Create an error badObject.error = err; @@ -2095,7 +2063,7 @@ OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPa try { this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; if (this.options.verbose) console.log('Got time set packet from the board'); - let boardTime = openBCISample.getFromTimePacketTime(rawPacket); + let boardTime = obciUtils.getFromTimePacketTime(rawPacket); this.sync.curSyncObj.boardTime = boardTime; // if (this.options.verbose) { // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) @@ -2202,7 +2170,7 @@ OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPa OpenBCICyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { // if (this.sync.active === false) console.log('Need to sync with board...') try { - let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); + let sample = obciUtils.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); sample.rawPacket = rawPacket; this._finalizeNewSample(sample); } catch (err) { @@ -2222,7 +2190,7 @@ OpenBCICyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { OpenBCICyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { // if (this.sync.active === false) console.log('Need to sync with board...') try { - let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); + let sample = obciUtils.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); sample.rawPacket = rawPacket; this._finalizeNewSample(sample); } catch (err) { @@ -2266,7 +2234,7 @@ OpenBCICyton.prototype._finalizeNewSample = function (sampleObject) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { - if (openBCISample.isOdd(sampleObject.sampleNumber)) { + if (obciUtils.isOdd(sampleObject.sampleNumber)) { // Check for the skipped packet condition if (this._lowerChannelsSampleObject) { // The last packet was odd... missed the even packet @@ -2277,7 +2245,7 @@ OpenBCICyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { // Make sure there is an odd packet waiting to get merged with this packet if (this._lowerChannelsSampleObject) { // Merge these two samples - var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); + var mergedSample = obciUtils.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); // Set the _lowerChannelsSampleObject object to null this._lowerChannelsSampleObject = null; // Emite the new merged sample diff --git a/openBCISample.js b/openBCISample.js deleted file mode 100644 index 54b5d0c..0000000 --- a/openBCISample.js +++ /dev/null @@ -1,1196 +0,0 @@ -'use strict'; -const gaussian = require('gaussian'); -const k = require('./openBCIConstants'); -const StreamSearch = require('streamsearch'); - -/** Constants for interpreting the EEG data */ -// Reference voltage for ADC in ADS1299. -// Set by its hardware. -const ADS1299_VREF = 4.5; -// Scale factor for aux data -const SCALE_FACTOR_ACCEL = 0.002 / Math.pow(2, 4); -// X, Y, Z -const ACCEL_NUMBER_AXIS = 3; -// Default ADS1299 gains array - -var sampleModule = { - - /** - * @description This takes a 33 byte packet and converts it based on the last four bits. - * 0000 - Standard OpenBCI V3 Sample Packet - * @param dataBuf {Buffer} - A 33 byte buffer - * @param channelSettingsArray (optional) - An array of channel settings that is an Array that has shape similar to the one - * calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is - * Array of objects that have key-value pair {gain:NUMBER} - * @param convertAuxToAccel (optional) {Boolean} - Do you want to convert to g's? (Defaults to true)* - * @returns {Object} - A standard sample object. - */ - parseRawPacketStandard: (dataBuf, channelSettingsArray, convertAuxToAccel) => { - const defaultChannelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); - - // channelSettingsArray is optional, defaults to CHANNEL_SETTINGS_ARRAY_DEFAULT - channelSettingsArray = channelSettingsArray || defaultChannelSettingsArray; - // By default convert to g's - if (convertAuxToAccel === undefined || convertAuxToAccel === null) convertAuxToAccel = true; - - // Check to make sure data is not null. - if (k.isUndefined(dataBuf) || k.isNull(dataBuf)) { - throw new Error(k.OBCIErrorUndefinedOrNullInput); - } - - // Check to make sure the buffer is the right size. - if (dataBuf.byteLength !== k.OBCIPacketSize) { - throw new Error(k.OBCIErrorInvalidByteLength); - } - - // Verify the correct stop byte. - if (dataBuf[0] !== k.OBCIByteStart) { - throw new Error(k.OBCIErrorInvalidByteStart); - } - - if (convertAuxToAccel) { - return parsePacketStandardAccel(dataBuf, channelSettingsArray); - } else { - return parsePacketStandardRawAux(dataBuf, channelSettingsArray); - } - }, - getRawPacketType, - getFromTimePacketAccel, - getFromTimePacketTime, - getFromTimePacketRawAux, - parsePacketTimeSyncedAccel, - parsePacketTimeSyncedRawAux, - /** - * @description Mainly used by the simulator to convert a randomly generated sample into a std OpenBCI V3 Packet - * @param sample - A sample object - * @returns {Buffer} - */ - convertSampleToPacketStandard: (sample) => { - var packetBuffer = new Buffer(k.OBCIPacketSize); - packetBuffer.fill(0); - - // start byte - packetBuffer[0] = k.OBCIByteStart; - - // sample number - packetBuffer[1] = sample.sampleNumber; - - // channel data - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - var threeByteBuffer = floatTo3ByteBuffer(sample.channelData[i]); - - threeByteBuffer.copy(packetBuffer, 2 + (i * 3)); - } - - for (var j = 0; j < 3; j++) { - var twoByteBuffer = floatTo2ByteBuffer(sample.auxData[j]); - - twoByteBuffer.copy(packetBuffer, (k.OBCIPacketSize - 1 - 6) + (i * 2)); - } - - // stop byte - packetBuffer[k.OBCIPacketSize - 1] = k.OBCIByteStop; - - return packetBuffer; - }, - /** - * @description Mainly used by the simulator to convert a randomly generated sample into a std OpenBCI V3 Packet - * @param sample - A sample object - * @param rawAux {Buffer} - A 6 byte long buffer to insert into raw buffer - * @returns {Buffer} - A 33 byte long buffer - */ - convertSampleToPacketRawAux: (sample, rawAux) => { - var packetBuffer = new Buffer(k.OBCIPacketSize); - packetBuffer.fill(0); - - // start byte - packetBuffer[0] = k.OBCIByteStart; - - // sample number - packetBuffer[1] = sample.sampleNumber; - - // channel data - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - var threeByteBuffer = floatTo3ByteBuffer(sample.channelData[i]); - - threeByteBuffer.copy(packetBuffer, 2 + (i * 3)); - } - - // Write the raw aux bytes - rawAux.copy(packetBuffer, 26); - - // stop byte - packetBuffer[k.OBCIPacketSize - 1] = makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux); - - return packetBuffer; - }, - /** - * @description Mainly used by the simulator to convert a randomly generated sample into an accel time sync set buffer - * @param sample {Buffer} - A sample object - * @param time {Number} - The time to inject into the sample. - * @returns {Buffer} - A time sync accel packet - */ - convertSampleToPacketAccelTimeSyncSet: (sample, time) => { - var buf = convertSampleToPacketAccelTimeSynced(sample, time); - buf[k.OBCIPacketPositionStopByte] = makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSyncSet); - return buf; - }, - /** - * @description Mainly used by the simulator to convert a randomly generated sample into an accel time synced buffer - * @param sample {Buffer} - A sample object - * @param time {Number} - The time to inject into the sample. - * @returns {Buffer} - A time sync accel packet - */ - convertSampleToPacketAccelTimeSynced, - /** - * @description Mainly used by the simulator to convert a randomly generated sample into a raw aux time sync set packet - * @param sample {Buffer} - A sample object - * @param time {Number} - The time to inject into the sample. - * @param rawAux {Buffer} - 2 byte buffer to inject into sample - * @returns {Buffer} - A time sync raw aux packet - */ - convertSampleToPacketRawAuxTimeSyncSet: (sample, time, rawAux) => { - var buf = convertSampleToPacketRawAuxTimeSynced(sample, time, rawAux); - buf[k.OBCIPacketPositionStopByte] = makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSyncSet); - return buf; - }, - convertSampleToPacketRawAuxTimeSynced, - debugPrettyPrint: (sample) => { - if (sample === null || sample === undefined) { - console.log('== Sample is undefined =='); - } else { - console.log('-- Sample --'); - console.log('---- Start Byte: ' + sample.startByte); - console.log('---- Sample Number: ' + sample.sampleNumber); - for (var i = 0; i < 8; i++) { - console.log('---- Channel Data ' + (i + 1) + ': ' + sample.channelData[i]); - } - if (sample.accelData) { - for (var j = 0; j < 3; j++) { - console.log('---- Accel Data ' + j + ': ' + sample.accelData[j]); - } - } - if (sample.auxData) { - console.log('---- Aux Data ' + sample.auxData); - } - console.log('---- Stop Byte: ' + sample.stopByte); - } - }, - samplePrintHeader: () => { - return ( - 'All voltages in Volts!' + - 'sampleNumber, channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8, aux1, aux2, aux3\n'); - }, - samplePrintLine: sample => { - return new Promise((resolve, reject) => { - if (sample === null || sample === undefined) reject('undefined sample'); - - resolve( - sample.sampleNumber + ',' + - sample.channelData[0].toFixed(8) + ',' + - sample.channelData[1].toFixed(8) + ',' + - sample.channelData[2].toFixed(8) + ',' + - sample.channelData[3].toFixed(8) + ',' + - sample.channelData[4].toFixed(8) + ',' + - sample.channelData[5].toFixed(8) + ',' + - sample.channelData[6].toFixed(8) + ',' + - sample.channelData[7].toFixed(8) + ',' + - sample.auxData[0].toFixed(8) + ',' + - sample.auxData[1].toFixed(8) + ',' + - sample.auxData[2].toFixed(8) + '\n' - ); - }); - }, - floatTo3ByteBuffer, - floatTo2ByteBuffer, - interpret16bitAsInt32: twoByteBuffer => { - var prefix = 0; - - if (twoByteBuffer[0] > 127) { - // console.log('\t\tNegative number') - prefix = 65535; // 0xFFFF - } - - return (prefix << 16) | (twoByteBuffer[0] << 8) | twoByteBuffer[1]; - }, - interpret24bitAsInt32: (threeByteBuffer) => { - var prefix = 0; - - if (threeByteBuffer[0] > 127) { - // console.log('\t\tNegative number') - prefix = 255; - } - - return (prefix << 24) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2]; - }, - impedanceArray: numberOfChannels => { - var impedanceArray = []; - for (var i = 0; i < numberOfChannels; i++) { - impedanceArray.push(newImpedanceObject(i + 1)); - } - return impedanceArray; - }, - impedanceObject: newImpedanceObject, - impedanceSummarize: singleInputObject => { - if (singleInputObject.raw > k.OBCIImpedanceThresholdBadMax) { // The case for no load (super high impedance) - singleInputObject.text = k.OBCIImpedanceTextNone; - } else { - singleInputObject.text = k.getTextForRawImpedance(singleInputObject.raw); // Get textual impedance - } - }, - newSample, - /** - * @description Create a configurable function to return samples for a simulator. This implements 1/f filtering injection to create more brain like data. - * @param numberOfChannels {Number} - The number of channels in the sample... either 8 or 16 - * @param sampleRateHz {Number} - The sample rate - * @param injectAlpha {Boolean} (optional) - True if you want to inject noise - * @param lineNoise {String} (optional) - A string that can be either: - * `60Hz` - 60Hz line noise (Default) (ex. __United States__) - * `50Hz` - 50Hz line noise (ex. __Europe__) - * `none` - Do not inject line noise. - * - * @returns {Function} - */ - randomSample: (numberOfChannels, sampleRateHz, injectAlpha, lineNoise) => { - const distribution = gaussian(0, 1); - const sineWaveFreqHz10 = 10; - const sineWaveFreqHz50 = 50; - const sineWaveFreqHz60 = 60; - const uVolts = 1000000; - - var sinePhaseRad = new Array(numberOfChannels + 1); // prevent index error with '+1' - sinePhaseRad.fill(0); - - var auxData = [0, 0, 0]; - var accelCounter = 0; - // With 250Hz, every 10 samples, with 125Hz, every 5... - var samplesPerAccelRate = Math.floor(sampleRateHz / 25); // best to make this an integer - if (samplesPerAccelRate < 1) samplesPerAccelRate = 1; - - // Init arrays to hold coefficients for each channel and init to 0 - // This gives the 1/f filter memory on each iteration - var b0 = new Array(numberOfChannels).fill(0); - var b1 = new Array(numberOfChannels).fill(0); - var b2 = new Array(numberOfChannels).fill(0); - - /** - * @description Use a 1/f filter - * @param previousSampleNumber {Number} - The previous sample number - */ - return previousSampleNumber => { - var sample = newSample(); - var whiteNoise; - for (var i = 0; i < numberOfChannels; i++) { // channels are 0 indexed - // This produces white noise - whiteNoise = distribution.ppf(Math.random()) * Math.sqrt(sampleRateHz / 2) / uVolts; - - switch (i) { - case 0: // Add 10Hz signal to channel 1... brainy - case 1: - if (injectAlpha) { - sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz10 / sampleRateHz; - if (sinePhaseRad[i] > 2 * Math.PI) { - sinePhaseRad[i] -= 2 * Math.PI; - } - whiteNoise += (5 * Math.SQRT2 * Math.sin(sinePhaseRad[i])) / uVolts; - } - break; - default: - if (lineNoise === k.OBCISimulatorLineNoiseHz60) { - // If we're in murica we want to add 60Hz line noise - sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz60 / sampleRateHz; - if (sinePhaseRad[i] > 2 * Math.PI) { - sinePhaseRad[i] -= 2 * Math.PI; - } - whiteNoise += (8 * Math.SQRT2 * Math.sin(sinePhaseRad[i])) / uVolts; - } else if (lineNoise === k.OBCISimulatorLineNoiseHz50) { - // add 50Hz line noise if we are not in america - sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz50 / sampleRateHz; - if (sinePhaseRad[i] > 2 * Math.PI) { - sinePhaseRad[i] -= 2 * Math.PI; - } - whiteNoise += (8 * Math.SQRT2 * Math.sin(sinePhaseRad[i])) / uVolts; - } - } - /** - * See http://www.firstpr.com.au/dsp/pink-noise/ section "Filtering white noise to make it pink" - */ - b0[i] = 0.99765 * b0[i] + whiteNoise * 0.0990460; - b1[i] = 0.96300 * b1[i] + whiteNoise * 0.2965164; - b2[i] = 0.57000 * b2[i] + whiteNoise * 1.0526913; - sample.channelData[i] = b0[i] + b1[i] + b2[i] + whiteNoise * 0.1848; - } - if (previousSampleNumber === 255) { - sample.sampleNumber = 0; - } else { - sample.sampleNumber = previousSampleNumber + 1; - } - - /** - * Sample rate of accelerometer is 25Hz... when the accelCounter hits the relative sample rate of the accel - * we will output a new accel value. The approach will be to consider that Z should be about 1 and X and Y - * should be somewhere around 0. - */ - if (accelCounter === samplesPerAccelRate) { - // Initialize a new array - var accelArray = [0, 0, 0]; - // Calculate X - accelArray[0] = (Math.random() * 0.1 * (Math.random() > 0.5 ? -1 : 1)); - // Calculate Y - accelArray[1] = (Math.random() * 0.1 * (Math.random() > 0.5 ? -1 : 1)); - // Calculate Z, this is around 1 - accelArray[2] = 1 - ((Math.random() * 0.4) * (Math.random() > 0.5 ? -1 : 1)); - // Store the newly calculated value - sample.auxData = accelArray; - // Reset the counter - accelCounter = 0; - } else { - // Increment counter - accelCounter++; - // Store the default value - sample.auxData = auxData; - } - - return sample; - }; - }, - scaleFactorAux: SCALE_FACTOR_ACCEL, - k, - /** - * Calculate the impedance - * @param sample {Object} - Standard sample - * @param impedanceTest {Object} - Impedance Object from openBCIBoard.js - * @return {null | Object} - Null if not enough samples have passed to calculate an accurate - */ - impedanceCalculateArray: (sample, impedanceTest) => { - impedanceTest.buffer.push(sample.channelData); - impedanceTest.count++; - - if (impedanceTest.count >= impedanceTest.window) { - let output = []; - for (let i = 0; i < sample.channelData.length; i++) { - let max = 0.0; // sumSquared - for (let j = 0; j < impedanceTest.window; j++) { - if (impedanceTest.buffer[i][j] > max) { - max = impedanceTest.buffer[i][j]; - } - } - let min = 0.0; - for (let j = 0; j < impedanceTest.window; j++) { - if (impedanceTest.buffer[i][j] < min) { - min = impedanceTest.buffer[i][j]; - } - } - const vP2P = max - min; // peak to peak - - output.push(vP2P / 2 / k.OBCILeadOffDriveInAmps); - } - impedanceTest.count = 0; - return output; - } - return null; - }, - impedanceTestObjDefault: (impedanceTestObj) => { - let newObj = impedanceTestObj || {}; - newObj['active'] = false; - newObj['buffer'] = []; - newObj['count'] = 0; - newObj['isTestingPInput'] = false; - newObj['isTestingNInput'] = false; - newObj['onChannel'] = 0; - newObj['sampleNumber'] = 0; - newObj['continuousMode'] = false; - newObj['impedanceForChannel'] = 0; - newObj['window'] = 40; - return newObj; - }, - samplePacket: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 0, 1, 0, 2, makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)]); - }, - samplePacketReal: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0x8F, 0xF2, 0x40, 0x8F, 0xDF, 0xF4, 0x90, 0x2B, 0xB6, 0x8F, 0xBF, 0xBF, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x94, 0x25, 0x34, 0x20, 0xB6, 0x7D, 0, 0xE0, 0, 0xE0, 0x0F, 0x70, makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)]); - }, - samplePacketStandardRawAux: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 2, 3, 4, 5, makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux)]); - }, - samplePacketAccelTimeSyncSet: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 0, 0, 0, 1, makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSyncSet)]); - }, - samplePacketAccelTimeSynced: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 0, 0, 0, 1, makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced)]); - }, - samplePacketRawAuxTimeSyncSet: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0x00, 0x01, 0, 0, 0, 1, makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSyncSet)]); - }, - samplePacketRawAuxTimeSynced: sampleNumber => { - return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0x00, 0x01, 0, 0, 0, 1, makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced)]); - }, - samplePacketUserDefined: () => { - return new Buffer([0xA0, 0x00, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, makeTailByteFromPacketType(k.OBCIStreamPacketUserDefinedType)]); - }, - makeDaisySampleObject, - getChannelDataArray, - isEven, - isOdd, - countADSPresent, - doesBufferHaveEOT, - findV2Firmware, - isFailureInBuffer, - isSuccessInBuffer, - isTimeSyncSetConfirmationInBuffer, - makeTailByteFromPacketType, - isStopByte, - newSyncObject, - stripToEOTBuffer, - /** - * @description Checks to make sure the previous sample number is one less - * then the new sample number. Takes into account sample numbers wrapping - * around at 255. - * @param `previousSampleNumber` {Number} - An integer number of the previous - * sample number. - * @param `newSampleNumber` {Number} - An integer number of the new sample - * number. - * @returns {Array} - Returns null if there is no dropped packets, otherwise, - * or on a missed packet, an array of their packet numbers is returned. - */ - droppedPacketCheck: (previousSampleNumber, newSampleNumber) => { - if (previousSampleNumber === k.OBCISampleNumberMax && newSampleNumber === 0) { - return null; - } - - if (newSampleNumber - previousSampleNumber === 1) { - return null; - } - - var missedPacketArray = []; - - if (previousSampleNumber > newSampleNumber) { - var numMised = k.OBCISampleNumberMax - previousSampleNumber; - for (var i = 0; i < numMised; i++) { - missedPacketArray.push(previousSampleNumber + i + 1); - } - previousSampleNumber = -1; - } - - for (var j = 1; j < (newSampleNumber - previousSampleNumber); j++) { - missedPacketArray.push(previousSampleNumber + j); - } - return missedPacketArray; - } -}; - -module.exports = sampleModule; - -function newImpedanceObject (channelNumber) { - return { - channel: channelNumber, - P: { - raw: -1, - text: k.OBCIImpedanceTextInit - }, - N: { - raw: -1, - text: k.OBCIImpedanceTextInit - } - }; -} - -function newSyncObject () { - return { - boardTime: 0, - correctedTransmissionTime: false, - error: null, - timeSyncSent: 0, - timeSyncSentConfirmation: 0, - timeSyncSetPacket: 0, - timeRoundTrip: 0, - timeTransmission: 0, - timeOffset: 0, - timeOffsetMaster: 0, - valid: false - }; -} - -/** -* @description This method parses a 33 byte OpenBCI V3 packet and converts to a sample object -* @param dataBuf - 33 byte packet that has bytes: -* 0:[startByte] | 1:[sampleNumber] | 2:[Channel-1.1] | 3:[Channel-1.2] | 4:[Channel-1.3] | 5:[Channel-2.1] | 6:[Channel-2.2] | 7:[Channel-2.3] | 8:[Channel-3.1] | 9:[Channel-3.2] | 10:[Channel-3.3] | 11:[Channel-4.1] | 12:[Channel-4.2] | 13:[Channel-4.3] | 14:[Channel-5.1] | 15:[Channel-5.2] | 16:[Channel-5.3] | 17:[Channel-6.1] | 18:[Channel-6.2] | 19:[Channel-6.3] | 20:[Channel-7.1] | 21:[Channel-7.2] | 22:[Channel-7.3] | 23:[Channel-8.1] | 24:[Channel-8.2] | 25:[Channel-8.3] | 26:[Aux-1.1] | 27:[Aux-1.2] | 28:[Aux-2.1] | 29:[Aux-2.2] | 30:[Aux-3.1] | 31:[Aux-3.2] | 32:StopByte -* @param channelSettingsArray {Array} - An array of channel settings that is an Array that has shape similar to the one -* calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is -* Array of objects that have key-value pair {gain:NUMBER} -* @returns {object} Sample. - * `sample` Object with: -* { -* channelData: {Array}, // of floats -* accelData: {Array}, // of floats of accel data -* sampleNumber: {Number} // The sample number -* } -*/ -function parsePacketStandardAccel (dataBuf, channelSettingsArray) { - var sampleObject = {}; - - sampleObject.accelData = getDataArrayAccel(dataBuf.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1)); - - sampleObject.channelData = getChannelDataArray(dataBuf, channelSettingsArray); - - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - sampleObject.auxData = Buffer.from(dataBuf.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1)); - } else { - sampleObject.auxData = new Buffer(dataBuf.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1)); - } - // Get the sample number - sampleObject.sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - // Get the start byte - sampleObject.startByte = dataBuf[0]; - // Get the stop byte - sampleObject.stopByte = dataBuf[k.OBCIPacketPositionStopByte]; - - return sampleObject; -} - -/** -* @description This method parses a 33 byte OpenBCI V3 packet and converts to a sample object -* @param dataBuf - 33 byte packet that has bytes: -* 0:[startByte] | 1:[sampleNumber] | 2:[Channel-1.1] | 3:[Channel-1.2] | 4:[Channel-1.3] | 5:[Channel-2.1] | 6:[Channel-2.2] | 7:[Channel-2.3] | 8:[Channel-3.1] | 9:[Channel-3.2] | 10:[Channel-3.3] | 11:[Channel-4.1] | 12:[Channel-4.2] | 13:[Channel-4.3] | 14:[Channel-5.1] | 15:[Channel-5.2] | 16:[Channel-5.3] | 17:[Channel-6.1] | 18:[Channel-6.2] | 19:[Channel-6.3] | 20:[Channel-7.1] | 21:[Channel-7.2] | 22:[Channel-7.3] | 23:[Channel-8.1] | 24:[Channel-8.2] | 25:[Channel-8.3] | 26:[Aux-1.1] | 27:[Aux-1.2] | 28:[Aux-2.1] | 29:[Aux-2.2] | 30:[Aux-3.1] | 31:[Aux-3.2] | 32:StopByte -* @param channelSettingsArray - An array of channel settings that is an Array that has shape similar to the one -* calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is -* Array of objects that have key-value pair {gain:NUMBER} -* @returns {Promise} - Fulfilled with a sample object that has form: -* { -* channelData: Array of floats -* auxData: 6 byte long buffer of raw aux data -* sampleNumber: a Number that is the sample -* } -*/ -function parsePacketStandardRawAux (dataBuf, channelSettingsArray) { - var sampleObject = {}; - - // Store the channel data - sampleObject.channelData = getChannelDataArray(dataBuf, channelSettingsArray); - - // Slice the buffer for the aux data - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - sampleObject.auxData = Buffer.from(dataBuf.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1)); - } else { - sampleObject.auxData = new Buffer(dataBuf.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1)); - } - // Get the sample number - sampleObject.sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - // Get the start byte - sampleObject.startByte = dataBuf[0]; - // Get the stop byte - sampleObject.stopByte = dataBuf[k.OBCIPacketPositionStopByte]; - - return sampleObject; -} - -/** -* @description Grabs an accel value from a raw but time synced packet. Important that this utilizes the fact that: -* X axis data is sent with every sampleNumber % 10 === 0 -* Y axis data is sent with every sampleNumber % 10 === 1 -* Z axis data is sent with every sampleNumber % 10 === 2 -* @param dataBuf {Buffer} - The 33byte raw time synced accel packet -* @param channelSettingsArray {Array} - An array of channel settings that is an Array that has shape similar to the one -* calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is -* Array of objects that have key-value pair {gain:NUMBER} -* @param boardOffsetTime {Number} - The difference between board time and current time calculated with sync methods. -* @param accelArray {Array} - A 3 element array that allows us to have inter packet memory of x and y axis data and emit only on the z axis packets. -* @returns {Object} - A sample object. NOTE: Only has accelData if this is a Z axis packet. -*/ -function parsePacketTimeSyncedAccel (dataBuf, channelSettingsArray, boardOffsetTime, accelArray) { - // Ths packet has 'A0','00'....,'AA','AA','FF','FF','FF','FF','C4' - // where the 'AA's form an accel 16bit num and 'FF's form a 32 bit time in ms - // The sample object we are going to build - var sampleObject = {}; - - // Get the sample number - sampleObject.sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - // Get the start byte - sampleObject.startByte = dataBuf[0]; - // Get the stop byte - sampleObject.stopByte = dataBuf[k.OBCIPacketPositionStopByte]; - - // Get the board time - sampleObject.boardTime = getFromTimePacketTime(dataBuf); - sampleObject.timeStamp = sampleObject.boardTime + boardOffsetTime; - - // Extract the aux data - sampleObject.auxData = getFromTimePacketRawAux(dataBuf); - - // Grab the accelData only if `getFromTimePacketAccel` returns true. - if (getFromTimePacketAccel(dataBuf, accelArray)) { - sampleObject.accelData = accelArray; - } - - // Grab the channel data. - sampleObject.channelData = getChannelDataArray(dataBuf, channelSettingsArray); - - return sampleObject; -} - -/** -* @description Grabs an accel value from a raw but time synced packet. Important that this utilizes the fact that: -* X axis data is sent with every sampleNumber % 10 === 0 -* Y axis data is sent with every sampleNumber % 10 === 1 -* Z axis data is sent with every sampleNumber % 10 === 2 -* @param dataBuf {Buffer} - The 33byte raw time synced accel packet -* @param channelSettingsArray {Array} - An array of channel settings that is an Array that has shape similar to the one -* calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is -* Array of objects that have key-value pair {gain:NUMBER} -* @param boardOffsetTime {Number} - The difference between board time and current time calculated with sync methods. -* @returns {Object} - A sample object. NOTE: The aux data is placed in a 2 byte buffer -*/ -function parsePacketTimeSyncedRawAux (dataBuf, channelSettingsArray, boardOffsetTime) { - // Ths packet has 'A0','00'....,'AA','AA','FF','FF','FF','FF','C4' - // where the 'AA's form an accel 16bit num and 'FF's form a 32 bit time in ms - if (dataBuf.byteLength !== k.OBCIPacketSize) { - throw new Error(k.OBCIErrorInvalidByteLength); - } - - // The sample object we are going to build - var sampleObject = {}; - - // Get the sample number - sampleObject.sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - // Get the start byte - sampleObject.startByte = dataBuf[0]; - // Get the stop byte - sampleObject.stopByte = dataBuf[k.OBCIPacketPositionStopByte]; - - // Get the board time - sampleObject.boardTime = getFromTimePacketTime(dataBuf); - sampleObject.timeStamp = sampleObject.boardTime + boardOffsetTime; - - // Extract the aux data - sampleObject.auxData = getFromTimePacketRawAux(dataBuf); - - // Grab the channel data. - sampleObject.channelData = getChannelDataArray(dataBuf, channelSettingsArray); - - return sampleObject; -} - -/** -* @description Extract a time from a time packet in ms. -* @param dataBuf - A raw packet with 33 bytes of data -* @returns {Number} - Board time in milli seconds -* @author AJ Keller (@pushtheworldllc) -*/ -function getFromTimePacketTime (dataBuf) { - // Ths packet has 'A0','00'....,'00','00','FF','FF','FF','FF','C3' where the 'FF's are times - const lastBytePosition = k.OBCIPacketSize - 1; // This is 33, but 0 indexed would be 32 minus 1 for the stop byte and another two for the aux channel or the - if (dataBuf.byteLength !== k.OBCIPacketSize) { - throw new Error(k.OBCIErrorInvalidByteLength); - } else { - // Grab the time from the packet - return dataBuf.readUInt32BE(lastBytePosition - k.OBCIStreamPacketTimeByteSize); - } -} - -/** -* @description Grabs an accel value from a raw but time synced packet. Important that this utilizes the fact that: -* X axis data is sent with every sampleNumber % 10 === 7 -* Y axis data is sent with every sampleNumber % 10 === 8 -* Z axis data is sent with every sampleNumber % 10 === 9 -* @param dataBuf {Buffer} - The 33byte raw time synced accel packet -* @param accelArray {Array} - A 3 element array that allows us to have inter packet memory of x and y axis data and emit only on the z axis packets. -* @returns {boolean} - A boolean that is true only when the accel array is ready to be emitted... i.e. when this is a Z axis packet -*/ -function getFromTimePacketAccel (dataBuf, accelArray) { - const accelNumBytes = 2; - const lastBytePosition = k.OBCIPacketSize - 1 - k.OBCIStreamPacketTimeByteSize - accelNumBytes; // This is 33, but 0 indexed would be 32 minus - - if (dataBuf.byteLength !== k.OBCIPacketSize) { - throw new Error(k.OBCIErrorInvalidByteLength); - } - - var sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - switch (sampleNumber % 10) { // The accelerometer is on a 25Hz sample rate, so every ten channel samples, we can get new data - case k.OBCIAccelAxisX: - accelArray[0] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right - return false; - case k.OBCIAccelAxisY: - accelArray[1] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right - return false; - case k.OBCIAccelAxisZ: - accelArray[2] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right - return true; - default: - return false; - } -} - -/** -* @description Grabs a raw aux value from a raw but time synced packet. -* @param dataBuf {Buffer} - The 33byte raw time synced raw aux packet -* @returns {Promise} - Fulfills a 2 byte buffer -*/ -function getFromTimePacketRawAux (dataBuf) { - if (dataBuf.byteLength !== k.OBCIPacketSize) { - throw new Error(k.OBCIErrorInvalidByteLength); - } - if (k.getVersionNumber(process.version) >= 6) { - return Buffer.from(dataBuf.slice(k.OBCIPacketPositionTimeSyncAuxStart, k.OBCIPacketPositionTimeSyncAuxStop)); - } else { - return new Buffer(dataBuf.slice(k.OBCIPacketPositionTimeSyncAuxStart, k.OBCIPacketPositionTimeSyncAuxStop)); - } -} - -/** -* @description Takes a buffer filled with 3 16 bit integers from an OpenBCI device and converts based on settings -* of the MPU, values are in ? -* @param dataBuf - Buffer that is 6 bytes long -* @returns {Array} - Array of floats 3 elements long -* @author AJ Keller (@pushtheworldllc) -*/ -function getDataArrayAccel (dataBuf) { - var accelData = []; - for (var i = 0; i < ACCEL_NUMBER_AXIS; i++) { - var index = i * 2; - accelData.push(sampleModule.interpret16bitAsInt32(dataBuf.slice(index, index + 2)) * SCALE_FACTOR_ACCEL); - } - return accelData; -} -/** -* @description Takes a buffer filled with 24 bit signed integers from an OpenBCI device with gain settings in -* channelSettingsArray[index].gain and converts based on settings of ADS1299... spits out an -* array of floats in VOLTS -* @param dataBuf {Buffer} - Buffer with 33 bit signed integers, number of elements is same as channelSettingsArray.length * 3 -* @param channelSettingsArray {Array} - The channel settings array, see OpenBCIConstants.channelSettingsArrayInit() for specs -* @returns {Array} - Array filled with floats for each channel's voltage in VOLTS -* @author AJ Keller (@pushtheworldllc) -*/ -function getChannelDataArray (dataBuf, channelSettingsArray) { - if (!Array.isArray(channelSettingsArray)) { - throw new Error('Error [getChannelDataArray]: Channel Settings must be an array!'); - } - var channelData = []; - // Grab the sample number from the buffer - var sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber]; - var daisy = channelSettingsArray.length > k.OBCINumberOfChannelsDefault; - - // Channel data arrays are always 8 long - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - if (!channelSettingsArray[i].hasOwnProperty('gain')) { - throw new Error(`Error [getChannelDataArray]: Invalid channel settings object at index ${i}`); - } - if (!k.isNumber(channelSettingsArray[i].gain)) { - throw new Error('Error [getChannelDataArray]: Property gain of channelSettingsObject not or type Number'); - } - - var scaleFactor = 0; - if (isEven(sampleNumber) && daisy) { - scaleFactor = ADS1299_VREF / channelSettingsArray[i + k.OBCINumberOfChannelsDefault].gain / (Math.pow(2, 23) - 1); - } else { - scaleFactor = ADS1299_VREF / channelSettingsArray[i].gain / (Math.pow(2, 23) - 1); - } - // Convert the three byte signed integer and convert it - channelData.push(scaleFactor * sampleModule.interpret24bitAsInt32(dataBuf.slice((i * 3) + k.OBCIPacketPositionChannelDataStart, (i * 3) + k.OBCIPacketPositionChannelDataStart + 3))); - } - return channelData; -} - -function getRawPacketType (stopByte) { - return stopByte & 0xF; -} - -/** -* @description This method is useful for normalizing sample numbers for fake sample packets. This is intended to be -* useful for the simulator and automated testing. -* @param sampleNumber {Number} - The sample number you want to assign to the packet -* @returns {Number} - The normalized input `sampleNumber` between 0-255 -*/ -function sampleNumberNormalize (sampleNumber) { - if (sampleNumber || sampleNumber === 0) { - if (sampleNumber > 255) { - sampleNumber = 255; - } - } else { - sampleNumber = 0x45; - } - return sampleNumber; -} - -function newSample (sampleNumber) { - if (sampleNumber || sampleNumber === 0) { - if (sampleNumber > 255) { - sampleNumber = 255; - } - } else { - sampleNumber = 0; - } - return { - startByte: k.OBCIByteStart, - sampleNumber: sampleNumber, - channelData: [], - accelData: [], - auxData: null, - stopByte: k.OBCIByteStop, - boardTime: 0, - timeStamp: 0 - }; -} - -/** -* @description Convert float number into three byte buffer. This is the opposite of .interpret24bitAsInt32() -* @param float - The number you want to convert -* @returns {Buffer} - 3-byte buffer containing the float -*/ -function floatTo3ByteBuffer (float) { - var intBuf = new Buffer(3); // 3 bytes for 24 bits - intBuf.fill(0); // Fill the buffer with 0s - - var temp = float / (ADS1299_VREF / 24 / (Math.pow(2, 23) - 1)); // Convert to counts - - temp = Math.floor(temp); // Truncate counts number - - // Move into buffer - intBuf[2] = temp & 255; - intBuf[1] = (temp & (255 << 8)) >> 8; - intBuf[0] = (temp & (255 << 16)) >> 16; - - return intBuf; -} - -/** -* @description Convert float number into three byte buffer. This is the opposite of .interpret24bitAsInt32() -* @param float - The number you want to convert -* @returns {buffer} - 3-byte buffer containing the float -*/ -function floatTo2ByteBuffer (float) { - var intBuf = new Buffer(2); // 2 bytes for 16 bits - intBuf.fill(0); // Fill the buffer with 0s - - var temp = float / SCALE_FACTOR_ACCEL; // Convert to counts - - temp = Math.floor(temp); // Truncate counts number - - // console.log('Num: ' + temp) - - // Move into buffer - intBuf[1] = temp & 255; - intBuf[0] = (temp & (255 << 8)) >> 8; - - return intBuf; -} - -/** -* @description Used to make one sample object from two sample objects. The sample number of the new daisy sample will -* be the upperSampleObject's sample number divded by 2. This allows us to preserve consecutive sample numbers that -* flip over at 127 instead of 255 for an 8 channel. The daisySampleObject will also have one `channelData` array -* with 16 elements inside it, with the lowerSampleObject in the lower indices and the upperSampleObject in the -* upper set of indices. The auxData from both channels shall be captured in an object called `auxData` which -* contains two arrays referenced by keys `lower` and `upper` for the `lowerSampleObject` and `upperSampleObject`, -* respectively. The timestamps shall be averaged and moved into an object called `timestamp`. Further, the -* un-averaged timestamps from the `lowerSampleObject` and `upperSampleObject` shall be placed into an object called -* `_timestamps` which shall contain two keys `lower` and `upper` which contain the original timestamps for their -* respective sampleObjects. -* @param lowerSampleObject {Object} - Lower 8 channels with odd sample number -* @param upperSampleObject {Object} - Upper 8 channels with even sample number -* @returns {Object} - The new merged daisy sample object -*/ -function makeDaisySampleObject (lowerSampleObject, upperSampleObject) { - var daisySampleObject = {}; - - daisySampleObject['channelData'] = lowerSampleObject.channelData.concat(upperSampleObject.channelData); - - daisySampleObject['sampleNumber'] = Math.floor(upperSampleObject.sampleNumber / 2); - - daisySampleObject['auxData'] = { - 'lower': lowerSampleObject.auxData, - 'upper': upperSampleObject.auxData - }; - - daisySampleObject['timestamp'] = (lowerSampleObject.timestamp + upperSampleObject.timestamp) / 2; - - daisySampleObject['_timestamps'] = { - 'lower': lowerSampleObject.timestamp, - 'upper': upperSampleObject.timestamp - }; - - if (lowerSampleObject.accelData) { - daisySampleObject['accelData'] = lowerSampleObject.accelData; - } else if (upperSampleObject.accelData) { - daisySampleObject['accelData'] = upperSampleObject.accelData; - } - - return daisySampleObject; -} - -/** -* @description Used to test a number to see if it is even -* @param a {Number} - The number to test -* @returns {boolean} - True if `a` is even -*/ -function isEven (a) { - return a % 2 === 0; -} -/** -* @description Used to test a number to see if it is odd -* @param a {Number} - The number to test -* @returns {boolean} - True if `a` is odd -*/ -function isOdd (a) { - return a % 2 === 1; -} - -/** -* @description Since we know exactly what this input will look like (See the hardware firmware) we can program this -* function with prior knowledge. -* @param dataBuffer {Buffer} - The buffer you want to parse. -* @return {Number} - The number of "ADS1299" present in the `dataBuffer` -*/ -function countADSPresent (dataBuffer) { - const s = new StreamSearch(new Buffer('ADS1299')); - - // Clear the buffer - s.reset(); - - // Push the new data buffer. This runs the search. - s.push(dataBuffer); - - // Check and see if there is a match - return s.matches; -} - -/** -* @description Searchs the buffer for a "$$$" or as we call an EOT -* @param dataBuffer - The buffer of some length to parse -* @returns {boolean} - True if the `$$$` was found. -*/ -// TODO: StreamSearch is optimized to search incoming chunks of data, streaming in, -// but a new search is constructed here with every call. This is not making use -// of StreamSearch's optimizations; the object should be preserved between chunks, -// and only fed the new data. TODO: also check other uses of StreamSearch -function doesBufferHaveEOT (dataBuffer) { - const s = new StreamSearch(new Buffer(k.OBCIParseEOT)); - - // Clear the buffer - s.reset(); - - // Push the new data buffer. This runs the search. - s.push(dataBuffer); - - // Check and see if there is a match - return s.matches >= 1; -} - -/** -* @description Used to parse a soft reset response to determine if the board is running the v2 firmware -* @param dataBuffer {Buffer} - The data to parse -* @returns {boolean} - True if `v2`is indeed found in the `dataBuffer` -*/ -function findV2Firmware (dataBuffer) { - const s = new StreamSearch(new Buffer(k.OBCIParseFirmware)); - - // Clear the buffer - s.reset(); - - // Push the new data buffer. This runs the search. - s.push(dataBuffer); - - // Check and see if there is a match - return s.matches >= 1; -} - -/** -* @description Used to parse a buffer for the word `Failure` that is acked back after private radio msg on failure -* @param dataBuffer {Buffer} - The buffer of some length to parse -* @returns {boolean} - True if `Failure` was found. -*/ -function isFailureInBuffer (dataBuffer) { - const s = new StreamSearch(new Buffer(k.OBCIParseFailure)); - - // Clear the buffer - s.reset(); - - // Push the new data buffer. This runs the search. - s.push(dataBuffer); - - // Check and see if there is a match - return s.matches >= 1; -} - -/** -* @description Used to parse a buffer for the word `Success` that is acked back after private radio msg on success -* @param dataBuffer {Buffer} - The buffer of some length to parse -* @returns {boolean} - True if `Success` was found. -*/ -function isSuccessInBuffer (dataBuffer) { - const s = new StreamSearch(new Buffer(k.OBCIParseSuccess)); - - // Clear the buffer - s.reset(); - - // Push the new data buffer. This runs the search. - s.push(dataBuffer); - - // Check and see if there is a match - return s.matches >= 1; -} - -/** - * @description Used to slice a buffer for the EOT '$$$'. - * @param dataBuffer {Buffer} - The buffer of some length to parse - * @returns {Buffer} - The remaining buffer. - */ -function stripToEOTBuffer (dataBuffer) { - let indexOfEOT = dataBuffer.indexOf(k.OBCIParseEOT); - if (indexOfEOT >= 0) { - indexOfEOT += k.OBCIParseEOT.length; - } else { - return dataBuffer; - } - - if (indexOfEOT < dataBuffer.byteLength) { - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - return Buffer.from(dataBuffer.slice(indexOfEOT)); - } else { - return new Buffer(dataBuffer.slice(indexOfEOT)); - } - } else { - return null; - } -} - -/** -* @description Used to parse a buffer for the `,` character that is acked back after a time sync request is sent -* @param dataBuffer {Buffer} - The buffer of some length to parse -* @returns {boolean} - True if the `,` was found. -*/ -function isTimeSyncSetConfirmationInBuffer (dataBuffer) { - if (dataBuffer) { - var bufferLength = dataBuffer.length; - switch (bufferLength) { - case 0: - return false; - case 1: - return dataBuffer[0] === k.OBCISyncTimeSent.charCodeAt(0); - case 2: - // HEAD Byte at End - if (dataBuffer[1] === k.OBCIByteStart) { - return dataBuffer[0] === k.OBCISyncTimeSent.charCodeAt(0); - // TAIL byte in front - } else if (isStopByte((dataBuffer[0]))) { - return dataBuffer[1] === k.OBCISyncTimeSent.charCodeAt(0); - } else { - return false; - } - default: - if (dataBuffer[0] === k.OBCISyncTimeSent.charCodeAt(0) && dataBuffer[1] === k.OBCIByteStart) { - return true; - } - for (var i = 1; i < bufferLength; i++) { - // The base case (last one) - // console.log(i) - if (i === (bufferLength - 1)) { - if (isStopByte((dataBuffer[i - 1]))) { - return dataBuffer[i] === k.OBCISyncTimeSent.charCodeAt(0); - } - } else { - // Wedged - if (isStopByte(dataBuffer[i - 1]) && dataBuffer[i + 1] === k.OBCIByteStart) { - return dataBuffer[i] === k.OBCISyncTimeSent.charCodeAt(0); - // TAIL byte in front - } - } - } - return false; - - } - } -} - -/** -* @description Mainly used by the simulator to convert a randomly generated sample into a std OpenBCI V3 Packet -* @param sample {Object} - A sample object -* @param time {Number} - The time to inject into the sample. -* @param rawAux {Buffer} - 2 byte buffer to inject into sample -* @returns {Buffer} - A time sync raw aux packet -*/ -function convertSampleToPacketRawAuxTimeSynced (sample, time, rawAux) { - var packetBuffer = new Buffer(k.OBCIPacketSize); - packetBuffer.fill(0); - - // start byte - packetBuffer[0] = k.OBCIByteStart; - - // sample number - packetBuffer[1] = sample.sampleNumber; - - // channel data - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - var threeByteBuffer = floatTo3ByteBuffer(sample.channelData[i]); - - threeByteBuffer.copy(packetBuffer, 2 + (i * 3)); - } - - // Write the raw aux bytes - rawAux.copy(packetBuffer, 26); - - // Write the time - packetBuffer.writeInt32BE(time, 28); - - // stop byte - packetBuffer[k.OBCIPacketSize - 1] = makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced); - - return packetBuffer; -} - -/** -* @description Mainly used by the simulator to convert a randomly generated sample into a std OpenBCI V3 Packet -* @param sample {Object} - A sample object -* @param time {Number} - The time to inject into the sample. -* @returns {Buffer} - A time sync accel packet -*/ -function convertSampleToPacketAccelTimeSynced (sample, time) { - var packetBuffer = new Buffer(k.OBCIPacketSize); - packetBuffer.fill(0); - - // start byte - packetBuffer[0] = k.OBCIByteStart; - - // sample number - packetBuffer[1] = sample.sampleNumber; - - // channel data - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - var threeByteBuffer = floatTo3ByteBuffer(sample.channelData[i]); - - threeByteBuffer.copy(packetBuffer, 2 + (i * 3)); - } - - packetBuffer.writeInt32BE(time, 28); - - // stop byte - packetBuffer[k.OBCIPacketSize - 1] = makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced); - - return packetBuffer; -} - -/** -* @description Converts a packet type {Number} into a OpenBCI stop byte -* @param type {Number} - The number to smash on to the stop byte. Must be 0-15, -* out of bounds input will result in a 0 -* @return {Number} - A properly formatted OpenBCI stop byte -*/ -function makeTailByteFromPacketType (type) { - if (type < 0 || type > 15) { - type = 0; - } - return k.OBCIByteStop | type; -} - -/** -* @description Used to check and see if a byte adheres to the stop byte structure of 0xCx where x is the set of -* numbers from 0-F in hex of 0-15 in decimal. -* @param byte {Number} - The number to test -* @returns {boolean} - True if `byte` follows the correct form -* @author AJ Keller (@pushtheworldllc) -*/ -function isStopByte (byte) { - return (byte & 0xF0) === k.OBCIByteStop; -} diff --git a/openBCIUtils.js b/openBCIUtils.js deleted file mode 100644 index a9024d5..0000000 --- a/openBCIUtils.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - debugBytes -}; - -/** - * @description Output passed bytes on the console as a hexdump, if enabled - * @param prefix - label to show to the left of bytes - * @param data - bytes to output, a buffer or string - * @private - */ -function debugBytes (prefix, data) { - if (typeof data === 'string') data = new Buffer(data); - - console.log('Debug bytes:'); - - for (var j = 0; j < data.length;) { - var hexPart = ''; - var ascPart = ''; - for (var end = Math.min(data.length, j + 16); j < end; ++j) { - var byt = data[j]; - - var hex = ('0' + byt.toString(16)).slice(-2); - hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in - hexPart += hex; - - var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.'; - ascPart += asc; - } - - // pad to fixed width for alignment - hexPart = (hexPart + ' ').substring(0, 3 * 17); - - console.log(prefix + ' ' + hexPart + '|' + ascPart + '|'); - } -} diff --git a/package.json b/package.json index 7abbb99..8cc0bed 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.16.6", "mathjs": "^3.3.0", + "openbci-utilities": "0.0.8", "performance-now": "^2.1.0", "serialport": "4.0.7", "sntp": "^2.0.0", diff --git a/test/OpenBCIConstants-test.js b/test/OpenBCIConstants-test.js deleted file mode 100644 index a8800ce..0000000 --- a/test/OpenBCIConstants-test.js +++ /dev/null @@ -1,1481 +0,0 @@ -/** -* Created by ajk on 12/16/15. -*/ -'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var assert = require('assert'); -var k = require('../openBCIConstants'); -var chai = require('chai'); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var chaiAsPromised = require('chai-as-promised'); -chai.use(chaiAsPromised); - -describe('OpenBCIConstants', function () { - afterEach(() => bluebirdChecks.noPendingPromises()); - describe('Turning Channels Off', function () { - it('channel 1', function () { - assert.equal('1', k.OBCIChannelOff1); - }); - it('channel 2', function () { - assert.equal('2', k.OBCIChannelOff2); - }); - it('channel 3', function () { - assert.equal('3', k.OBCIChannelOff3); - }); - it('channel 4', function () { - assert.equal('4', k.OBCIChannelOff4); - }); - it('channel 5', function () { - assert.equal('5', k.OBCIChannelOff5); - }); - it('channel 6', function () { - assert.equal('6', k.OBCIChannelOff6); - }); - it('channel 7', function () { - assert.equal('7', k.OBCIChannelOff7); - }); - it('channel 8', function () { - assert.equal('8', k.OBCIChannelOff8); - }); - it('channel 9', function () { - assert.equal('q', k.OBCIChannelOff9); - }); - it('channel 10', function () { - assert.equal('w', k.OBCIChannelOff10); - }); - it('channel 11', function () { - assert.equal('e', k.OBCIChannelOff11); - }); - it('channel 12', function () { - assert.equal('r', k.OBCIChannelOff12); - }); - it('channel 13', function () { - assert.equal('t', k.OBCIChannelOff13); - }); - it('channel 14', function () { - assert.equal('y', k.OBCIChannelOff14); - }); - it('channel 15', function () { - assert.equal('u', k.OBCIChannelOff15); - }); - it('channel 16', function () { - assert.equal('i', k.OBCIChannelOff16); - }); - }); - describe('Turning Channels On', function () { - it('channel 1', function () { - assert.equal('!', k.OBCIChannelOn1); - }); - it('channel 2', function () { - assert.equal('@', k.OBCIChannelOn2); - }); - it('channel 3', function () { - assert.equal('#', k.OBCIChannelOn3); - }); - it('channel 4', function () { - assert.equal('$', k.OBCIChannelOn4); - }); - it('channel 5', function () { - assert.equal('%', k.OBCIChannelOn5); - }); - it('channel 6', function () { - assert.equal('^', k.OBCIChannelOn6); - }); - it('channel 7', function () { - assert.equal('&', k.OBCIChannelOn7); - }); - it('channel 8', function () { - assert.equal('*', k.OBCIChannelOn8); - }); - it('channel 9', function () { - assert.equal('Q', k.OBCIChannelOn9); - }); - it('channel 10', function () { - assert.equal('W', k.OBCIChannelOn10); - }); - it('channel 11', function () { - assert.equal('E', k.OBCIChannelOn11); - }); - it('channel 12', function () { - assert.equal('R', k.OBCIChannelOn12); - }); - it('channel 13', function () { - assert.equal('T', k.OBCIChannelOn13); - }); - it('channel 14', function () { - assert.equal('Y', k.OBCIChannelOn14); - }); - it('channel 15', function () { - assert.equal('U', k.OBCIChannelOn15); - }); - it('channel 16', function () { - assert.equal('I', k.OBCIChannelOn16); - }); - }); - describe('Test Signal Control Commands', function () { - it('Connect to DC', function () { - assert.equal('p', k.OBCITestSignalConnectToDC); - }); - it('Connect to Ground', function () { - assert.equal('0', k.OBCITestSignalConnectToGround); - }); - it('Connect to Pulse 1x Fast', function () { - assert.equal('=', k.OBCITestSignalConnectToPulse1xFast); - }); - it('Connect to Pulse 1x Slow', function () { - assert.equal('-', k.OBCITestSignalConnectToPulse1xSlow); - }); - it('Connect to Pulse 2x Fast', function () { - assert.equal(']', k.OBCITestSignalConnectToPulse2xFast); - }); - it('Connect to Pulse 2x Slow', function () { - assert.equal('[', k.OBCITestSignalConnectToPulse2xSlow); - }); - }); - describe('Channel Setting Commands', function () { - describe('Channel Selection', function () { - it('Channel 1', function () { - assert.equal('1', k.OBCIChannelCmdChannel1); - }); - it('Channel 2', function () { - assert.equal('2', k.OBCIChannelCmdChannel2); - }); - it('Channel 3', function () { - assert.equal('3', k.OBCIChannelCmdChannel3); - }); - it('Channel 4', function () { - assert.equal('4', k.OBCIChannelCmdChannel4); - }); - it('Channel 5', function () { - assert.equal('5', k.OBCIChannelCmdChannel5); - }); - it('Channel 6', function () { - assert.equal('6', k.OBCIChannelCmdChannel6); - }); - it('Channel 7', function () { - assert.equal('7', k.OBCIChannelCmdChannel7); - }); - it('Channel 8', function () { - assert.equal('8', k.OBCIChannelCmdChannel8); - }); - it('Channel 9', function () { - assert.equal('Q', k.OBCIChannelCmdChannel9); - }); - it('Channel 10', function () { - assert.equal('W', k.OBCIChannelCmdChannel10); - }); - it('Channel 11', function () { - assert.equal('E', k.OBCIChannelCmdChannel11); - }); - it('Channel 12', function () { - assert.equal('R', k.OBCIChannelCmdChannel12); - }); - it('Channel 13', function () { - assert.equal('T', k.OBCIChannelCmdChannel13); - }); - it('Channel 14', function () { - assert.equal('Y', k.OBCIChannelCmdChannel14); - }); - it('Channel 15', function () { - assert.equal('U', k.OBCIChannelCmdChannel15); - }); - it('Channel 16', function () { - assert.equal('I', k.OBCIChannelCmdChannel16); - }); - }); - describe('Power Down', function () { - it('OFF', function () { - assert.equal('1', k.OBCIChannelCmdPowerOff); - }); - it('ON', function () { - assert.equal('0', k.OBCIChannelCmdPowerOn); - }); - }); - describe('Gain Setting', function () { - it('1', function () { - assert.equal('0', k.OBCIChannelCmdGain1); - }); - it('2', function () { - assert.equal('1', k.OBCIChannelCmdGain2); - }); - it('4', function () { - assert.equal('2', k.OBCIChannelCmdGain4); - }); - it('6', function () { - assert.equal('3', k.OBCIChannelCmdGain6); - }); - it('8', function () { - assert.equal('4', k.OBCIChannelCmdGain8); - }); - it('12', function () { - assert.equal('5', k.OBCIChannelCmdGain12); - }); - it('24', function () { - assert.equal('6', k.OBCIChannelCmdGain24); - }); - }); - describe('ADC Channel Input Soruce', function () { - it('Normal', function () { - assert.equal('0', k.OBCIChannelCmdADCNormal); - }); - it('Shorted', function () { - assert.equal('1', k.OBCIChannelCmdADCShorted); - }); - it('Bias Method', function () { - assert.equal('2', k.OBCIChannelCmdADCBiasMethod); - }); - it('MVDD', function () { - assert.equal('3', k.OBCIChannelCmdADCMVDD); - }); - it('Temp', function () { - assert.equal('4', k.OBCIChannelCmdADCTemp); - }); - it('Test Signal', function () { - assert.equal('5', k.OBCIChannelCmdADCTestSig); - }); - it('Bias DRP', function () { - assert.equal('6', k.OBCIChannelCmdADCBiasDRP); - }); - it('Bias DRN', function () { - assert.equal('7', k.OBCIChannelCmdADCBiasDRN); - }); - }); - describe('Bias Set', function () { - it('Include in BIAS', function () { - assert.equal('1', k.OBCIChannelCmdBiasInclude); - }); - it('Remove from BIAS', function () { - assert.equal('0', k.OBCIChannelCmdBiasRemove); - }); - }); - describe('SRB2 Set', function () { - it('Disconnect this input from SRB2', function () { - assert.equal('0', k.OBCIChannelCmdSRB2Diconnect); - }); - it('Connect this input to the SRB2', function () { - assert.equal('1', k.OBCIChannelCmdSRB2Connect); - }); - }); - describe('SRB1 Set', function () { - it('Disconnect this input from SRB1', function () { - assert.equal('0', k.OBCIChannelCmdSRB1Diconnect); - }); - it('Connect this input to the SRB1', function () { - assert.equal('1', k.OBCIChannelCmdSRB1Connect); - }); - }); - it('Command to access channel settings', function () { - assert.equal('x', k.OBCIChannelCmdSet); - }); - it('Command to latch', function () { - assert.equal('X', k.OBCIChannelCmdLatch); - }); - }); - describe('Default Channel Settings', function () { - it('Set all channels to default', function () { - assert.equal('d', k.OBCIChannelDefaultAllSet); - }); - it('Get a report of the default settings card', function () { - assert.equal('D', k.OBCIChannelDefaultAllGet); - }); - }); - describe('LeadOff Impedance Commands', function () { - it('Command to access impedance settings', function () { - assert.equal('z', k.OBCIChannelImpedanceSet); - }); - it('Command to latch', function () { - assert.equal('Z', k.OBCIChannelImpedanceLatch); - }); - it('Test signal not applied', function () { - assert.equal('0', k.OBCIChannelImpedanceTestSignalAppliedNot); - }); - it('Test signal applied', function () { - assert.equal('1', k.OBCIChannelImpedanceTestSignalApplied); - }); - }); - describe('Time Sync Stuff', function () { - it('Can get proper array size', function () { - assert.equal(10, k.OBCITimeSyncArraySize); - }); - it('Get correct time sync with conf', function () { - assert.equal(0.9, k.OBCITimeSyncMultiplierWithSyncConf); - }); - it('Get correct time sync without conf', function () { - assert.equal(0.75, k.OBCITimeSyncMultiplierWithoutSyncConf); - }); - it('Get correct time sync transmission threshold', function () { - assert.equal(10, k.OBCITimeSyncThresholdTransFailureMS); - }); - }); - describe('SD card Commands', function () { - it('logs for 1 hour', function () { - assert.equal('G', k.OBCISDLogForHour1); - }); - it('logs for 2 hours', function () { - assert.equal('H', k.OBCISDLogForHour2); - }); - it('logs for 4 hours', function () { - assert.equal('J', k.OBCISDLogForHour4); - }); - it('logs for 12 hours', function () { - assert.equal('K', k.OBCISDLogForHour12); - }); - it('logs for 24 hours', function () { - assert.equal('L', k.OBCISDLogForHour24); - }); - it('logs for 5 minutes', function () { - assert.equal('A', k.OBCISDLogForMin5); - }); - it('logs for 15 minutes', function () { - assert.equal('S', k.OBCISDLogForMin15); - }); - it('logs for 30 minutes', function () { - assert.equal('F', k.OBCISDLogForMin30); - }); - it('logs for 14 seconds', function () { - assert.equal('a', k.OBCISDLogForSec14); - }); - it('stop logging and close the SD file', function () { - assert.equal('j', k.OBCISDLogStop); - }); - }); - describe('SD card string Commands', function () { - it('logs for 1 hour', function () { - assert.equal('1hour', k.OBCIStringSDHour1); - }); - it('logs for 2 hours', function () { - assert.equal('2hour', k.OBCIStringSDHour2); - }); - it('logs for 4 hours', function () { - assert.equal('4hour', k.OBCIStringSDHour4); - }); - it('logs for 12 hours', function () { - assert.equal('12hour', k.OBCIStringSDHour12); - }); - it('logs for 24 hours', function () { - assert.equal('24hour', k.OBCIStringSDHour24); - }); - it('logs for 5 minutes', function () { - assert.equal('5min', k.OBCIStringSDMin5); - }); - it('logs for 15 minutes', function () { - assert.equal('15min', k.OBCIStringSDMin15); - }); - it('logs for 30 minutes', function () { - assert.equal('30min', k.OBCIStringSDMin30); - }); - it('logs for 14 seconds', function () { - assert.equal('14sec', k.OBCIStringSDSec14); - }); - }); - describe('#sdSettingForString', function () { - it('correct command for 1 hour', function () { - var expectation = k.OBCISDLogForHour1; - var result = k.sdSettingForString('1hour'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 2 hour', function () { - var expectation = k.OBCISDLogForHour2; - var result = k.sdSettingForString('2hour'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 4 hour', function () { - var expectation = k.OBCISDLogForHour4; - var result = k.sdSettingForString('4hour'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 12 hour', function () { - var expectation = k.OBCISDLogForHour12; - var result = k.sdSettingForString('12hour'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 24 hour', function () { - var expectation = k.OBCISDLogForHour24; - var result = k.sdSettingForString('24hour'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 5 min', function () { - var expectation = k.OBCISDLogForMin5; - var result = k.sdSettingForString('5min'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 15 min', function () { - var expectation = k.OBCISDLogForMin15; - var result = k.sdSettingForString('15min'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 30 min', function () { - var expectation = k.OBCISDLogForMin30; - var result = k.sdSettingForString('30min'); - return expect(result).to.eventually.equal(expectation); - }); - it('correct command for 14 seconds', function () { - var expectation = k.OBCISDLogForSec14; - var result = k.sdSettingForString('14sec'); - return expect(result).to.eventually.equal(expectation); - }); - it('Invalid command request', function () { - var result = k.sdSettingForString('taco'); - return expect(result).to.be.rejected; - }); - }); - describe('Stream Data Commands', function () { - it('starts', function () { - assert.equal('b', k.OBCIStreamStart); - }); - it('stops', function () { - assert.equal('s', k.OBCIStreamStop); - }); - }); - describe('Miscellaneous', function () { - it('queries register settings', function () { - assert.equal('?', k.OBCIMiscQueryRegisterSettings); - }); - it('softly resets the board', function () { - assert.equal('v', k.OBCIMiscSoftReset); - }); - }); - describe('Max channel number commands', function () { - it('sets max of 8', function () { - assert.equal('c', k.OBCIChannelMaxNumber8); - }); - it('sets max of 16', function () { - assert.equal('C', k.OBCIChannelMaxNumber16); - }); - it('has correct return messages', function () { - assert.equal('', k.OBCIChannelMaxNumber8NoDaisyToRemove); - assert.equal('daisy removed', k.OBCIChannelMaxNumber8SuccessDaisyRemoved); - assert.equal('16', k.OBCIChannelMaxNumber16DaisyAlreadyAttached); - assert.equal('daisy attached16', k.OBCIChannelMaxNumber16DaisyAttached); - assert.equal('no daisy to attach!8', k.OBCIChannelMaxNumber16NoDaisyAttached); - }); - }); - describe('On board filters', function () { - it('disable', function () { - assert.equal('g', k.OBCIFilterDisable); - }); - it('enable', function () { - assert.equal('f', k.OBCIFilterEnable); - }); - }); - describe('Stream packet types/codes', function () { - it('Standard with Accel', function () { - assert.equal(0, k.OBCIStreamPacketStandardAccel); - }); - it('Standard with Raw Aux', function () { - assert.equal(1, k.OBCIStreamPacketStandardRawAux); - }); - it('User Defined Packet', function () { - assert.equal(2, k.OBCIStreamPacketUserDefinedType); - }); - it('Time Sync Set with accel', function () { - assert.equal(3, k.OBCIStreamPacketAccelTimeSyncSet); - }); - it('Time Synced with Accel', function () { - assert.equal(4, k.OBCIStreamPacketAccelTimeSynced); - }); - it('Time Sync set with Raw Aux', function () { - assert.equal(5, k.OBCIStreamPacketRawAuxTimeSyncSet); - }); - it('Time Synced with Raw Aux', function () { - assert.equal(6, k.OBCIStreamPacketRawAuxTimeSynced); - }); - }); - describe('Time synced with accel packet', function () { - it('X axis', function () { - assert.equal(7, k.OBCIAccelAxisX); - }); - it('Y axis', function () { - assert.equal(8, k.OBCIAccelAxisY); - }); - it('Z axis', function () { - assert.equal(9, k.OBCIAccelAxisZ); - }); - }); - describe('Time sync useful numbers', function () { - it('Time from the board is 4 bytes', function () { - assert.equal(4, k.OBCIStreamPacketTimeByteSize); - }); - }); - describe('should return the right command for each channel', function () { - it('Channel 1', function () { - var expectation = '1'; - var result = k.commandChannelForCmd(1); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 2', function () { - var expectation = '2'; - var result = k.commandChannelForCmd(2); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 3', function () { - var expectation = '3'; - var result = k.commandChannelForCmd(3); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 4', function () { - var expectation = '4'; - var result = k.commandChannelForCmd(4); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 5', function () { - var expectation = '5'; - var result = k.commandChannelForCmd(5); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 6', function () { - var expectation = '6'; - var result = k.commandChannelForCmd(6); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 7', function () { - var expectation = '7'; - var result = k.commandChannelForCmd(7); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 8', function () { - var expectation = '8'; - var result = k.commandChannelForCmd(8); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 9', function () { - var expectation = 'Q'; - var result = k.commandChannelForCmd(9); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 10', function () { - var expectation = 'W'; - var result = k.commandChannelForCmd(10); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 11', function () { - var expectation = 'E'; - var result = k.commandChannelForCmd(11); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 12', function () { - var expectation = 'R'; - var result = k.commandChannelForCmd(12); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 13', function () { - var expectation = 'T'; - var result = k.commandChannelForCmd(13); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 14', function () { - var expectation = 'Y'; - var result = k.commandChannelForCmd(14); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 15', function () { - var expectation = 'U'; - var result = k.commandChannelForCmd(15); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 16', function () { - var expectation = 'I'; - var result = k.commandChannelForCmd(16); - return expect(result).to.eventually.equal(expectation); - }); - it('Invalid channel request', function () { - var result = k.commandChannelForCmd(17); - return expect(result).to.be.rejected; - }); - }); - describe('should return correct channel off command for number', function () { - it('Channel 1', function () { - var expectation = '1'; - var result = k.commandChannelOff(1); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 2', function () { - var expectation = '2'; - var result = k.commandChannelOff(2); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 3', function () { - var expectation = '3'; - var result = k.commandChannelOff(3); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 4', function () { - var expectation = '4'; - var result = k.commandChannelOff(4); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 5', function () { - var expectation = '5'; - var result = k.commandChannelOff(5); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 6', function () { - var expectation = '6'; - var result = k.commandChannelOff(6); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 7', function () { - var expectation = '7'; - var result = k.commandChannelOff(7); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 8', function () { - var expectation = '8'; - var result = k.commandChannelOff(8); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 9', function () { - var expectation = 'q'; - var result = k.commandChannelOff(9); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 10', function () { - var expectation = 'w'; - var result = k.commandChannelOff(10); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 11', function () { - var expectation = 'e'; - var result = k.commandChannelOff(11); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 12', function () { - var expectation = 'r'; - var result = k.commandChannelOff(12); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 13', function () { - var expectation = 't'; - var result = k.commandChannelOff(13); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 14', function () { - var expectation = 'y'; - var result = k.commandChannelOff(14); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 15', function () { - var expectation = 'u'; - var result = k.commandChannelOff(15); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 16', function () { - var expectation = 'i'; - var result = k.commandChannelOff(16); - return expect(result).to.eventually.equal(expectation); - }); - it('Invalid channel request', function () { - var result = k.commandChannelOff(17); - return expect(result).to.be.rejected; - }); - }); - describe('should return correct channel on command for number', function () { - it('Channel 1', function () { - var expectation = '!'; - var result = k.commandChannelOn(1); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 2', function () { - var expectation = '@'; - var result = k.commandChannelOn(2); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 3', function () { - var expectation = '#'; - var result = k.commandChannelOn(3); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 4', function () { - var expectation = '$'; - var result = k.commandChannelOn(4); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 5', function () { - var expectation = '%'; - var result = k.commandChannelOn(5); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 6', function () { - var expectation = '^'; - var result = k.commandChannelOn(6); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 7', function () { - var expectation = '&'; - var result = k.commandChannelOn(7); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 8', function () { - var expectation = '*'; - var result = k.commandChannelOn(8); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 9', function () { - var expectation = 'Q'; - var result = k.commandChannelOn(9); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 10', function () { - var expectation = 'W'; - var result = k.commandChannelOn(10); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 11', function () { - var expectation = 'E'; - var result = k.commandChannelOn(11); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 12', function () { - var expectation = 'R'; - var result = k.commandChannelOn(12); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 13', function () { - var expectation = 'T'; - var result = k.commandChannelOn(13); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 14', function () { - var expectation = 'Y'; - var result = k.commandChannelOn(14); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 15', function () { - var expectation = 'U'; - var result = k.commandChannelOn(15); - return expect(result).to.eventually.equal(expectation); - }); - it('Channel 16', function () { - var expectation = 'I'; - var result = k.commandChannelOn(16); - return expect(result).to.eventually.equal(expectation); - }); - it('Invalid channel request', function () { - var result = k.commandChannelOn(17); - return expect(result).to.be.rejected; - }); - }); - describe('Number of channels', function () { - it('Daisy', function () { - assert.equal(16, k.OBCINumberOfChannelsDaisy); - }); - it('Default', function () { - assert.equal(8, k.OBCINumberOfChannelsDefault); - }); - it('Ganglion', function () { - assert.equal(4, k.OBCINumberOfChannelsGanglion); - }); - }); - describe('Possible Sample Rates', function () { - it('should be 125', function () { - assert.equal(125, k.OBCISampleRate125); - }); - it('should be 250', function () { - assert.equal(200, k.OBCISampleRate200); - }); - it('should be 250', function () { - assert.equal(250, k.OBCISampleRate250); - }); - }); - describe('Max sample number', function () { - it('should be 255', function () { - assert.equal(255, k.OBCISampleNumberMax); - }); - }); - describe('Radio Channel Limits', function () { - it('should get the right channel number max', function () { - expect(k.OBCIRadioChannelMax).to.be.equal(25); - }); - it('should get the right channel number min', function () { - expect(k.OBCIRadioChannelMin).to.be.equal(1); - }); - it('should get the right poll time max', function () { - expect(k.OBCIRadioPollTimeMax).to.be.equal(255); - }); - it('should get the right poll time min', function () { - expect(k.OBCIRadioPollTimeMin).to.be.equal(0); - }); - }); - describe('#getChannelSetter', function () { - // 'channel 1, power on, gain 24, inputType normal, bias include, srb2 connect, srb1 dissconnect' - describe('channel input selection works', function () { - // this.timeout(5000) - it('channel 2', function (done) { - k.getChannelSetter(2, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[1].should.equal('2'); - val.newChannelSettingsObject.channelNumber.should.equal(2); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 5', function (done) { - k.getChannelSetter(5, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[1].should.equal('5'); - val.newChannelSettingsObject.channelNumber.should.equal(5); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 9', function (done) { - k.getChannelSetter(9, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[1].should.equal('Q'); - val.newChannelSettingsObject.channelNumber.should.equal(9); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 15', function (done) { - k.getChannelSetter(15, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[1].should.equal('U'); - val.newChannelSettingsObject.channelNumber.should.equal(15); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid channel selection', function (done) { - k.getChannelSetter(0, false, 24, 'normal', true, true, false).should.be.rejected.and.notify(done); - }); - it('Invalid type', function (done) { - k.getChannelSetter('0', false, 24, 'normal', true, true, false).should.be.rejected.and.notify(done); - }); - }); - describe('power selection works', function () { - it('on', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[2].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('off', function (done) { - k.getChannelSetter(1, true, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[2].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(true); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, 'taco', 24, 'normal', true, true, false).should.be.rejected.and.notify(done); - }); - }); - describe('gain selection works', function () { - it('1x', function (done) { - k.getChannelSetter(1, false, 1, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(1); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('2x', function (done) { - k.getChannelSetter(1, false, 2, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(2); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('4x', function (done) { - k.getChannelSetter(1, false, 4, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('2'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(4); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('6x', function (done) { - k.getChannelSetter(1, false, 6, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('3'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(6); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('8x', function (done) { - k.getChannelSetter(1, false, 8, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('4'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(8); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('12x', function (done) { - k.getChannelSetter(1, false, 12, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('5'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(12); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('24x', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[3].should.equal('6'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, false, '24', 'normal', true, true, false).should.be.rejected.and.notify(done); - }); - it('Invalid gain setting', function (done) { - k.getChannelSetter(1, false, 5, 'normal', true, true, false).should.be.rejected.and.notify(done); - }); - }); - describe('input type', function () { - it('normal', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[4].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('shorted', function (done) { - k.getChannelSetter(1, false, 24, 'shorted', true, true, false).then(function (val) { - val.commandArray[4].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('shorted'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('biasMethod', function (done) { - k.getChannelSetter(1, false, 24, 'biasMethod', true, true, false).then(function (val) { - val.commandArray[4].should.equal('2'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('biasMethod'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('mvdd', function (done) { - k.getChannelSetter(1, false, 24, 'mvdd', true, true, false).then(function (val) { - val.commandArray[4].should.equal('3'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('mvdd'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('temp', function (done) { - k.getChannelSetter(1, false, 24, 'temp', true, true, false).then(function (val) { - val.commandArray[4].should.equal('4'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('temp'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('testsig', function (done) { - k.getChannelSetter(1, false, 24, 'testSig', true, true, false).then(function (val) { - val.commandArray[4].should.equal('5'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('testSig'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('biasDrp', function (done) { - k.getChannelSetter(1, false, 24, 'biasDrp', true, true, false).then(function (val) { - val.commandArray[4].should.equal('6'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('biasDrp'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('biasDrn', function (done) { - k.getChannelSetter(1, false, 24, 'biasDrn', true, true, false).then(function (val) { - val.commandArray[4].should.equal('7'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('biasDrn'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid setting', function (done) { - k.getChannelSetter(1, false, 24, 'taco', true, true, false).should.be.rejected.and.notify(done); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, false, 24, 1, true, true, false).should.be.rejected.and.notify(done); - }); - }); - describe('bias selection works', function () { - it('Include', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[5].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Remove', function (done) { - k.getChannelSetter(1, false, 24, 'normal', false, true, false).then(function (val) { - val.commandArray[5].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(false); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, false, 24, 'normal', 'taco', true, false).should.be.rejected.and.notify(done); - }); - }); - describe('SRB2 selection works', function () { - it('Connect', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[6].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Disconnect', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, false, false).then(function (val) { - val.commandArray[6].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(false); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, 'taco', false).should.be.rejected.and.notify(done); - }); - }); - describe('SRB1 selection works', function () { - it('Connect', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, true).then(function (val) { - val.commandArray[7].should.equal('1'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(true); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Disconnect', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, false).then(function (val) { - val.commandArray[7].should.equal('0'); - val.newChannelSettingsObject.channelNumber.should.equal(1); - val.newChannelSettingsObject.powerDown.should.equal(false); - val.newChannelSettingsObject.gain.should.equal(24); - val.newChannelSettingsObject.inputType.should.equal('normal'); - val.newChannelSettingsObject.bias.should.equal(true); - val.newChannelSettingsObject.srb2.should.equal(true); - val.newChannelSettingsObject.srb1.should.equal(false); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getChannelSetter(1, false, 24, 'normal', true, true, 'taco').should.be.rejected.and.notify(done); - }); - }); - }); - describe('#getTestSignalCommand', function () { - it('ground', function () { - var expectation = '0'; - var result = k.getTestSignalCommand('ground'); - return expect(result).to.eventually.equal(expectation); - }); - it('dc', function () { - var expectation = 'p'; - var result = k.getTestSignalCommand('dc'); - return expect(result).to.eventually.equal(expectation); - }); - it('Pulse 1x Fast', function () { - var expectation = '='; - var result = k.getTestSignalCommand('pulse1xFast'); - return expect(result).to.eventually.equal(expectation); - }); - it('Pulse 1x Slow', function () { - var expectation = '-'; - var result = k.getTestSignalCommand('pulse1xSlow'); - return expect(result).to.eventually.equal(expectation); - }); - it('Pulse 2x Fast', function () { - var expectation = ']'; - var result = k.getTestSignalCommand('pulse2xFast'); - return expect(result).to.eventually.equal(expectation); - }); - it('Pulse 2x Slow', function () { - var expectation = '['; - var result = k.getTestSignalCommand('pulse2xSlow'); - return expect(result).to.eventually.equal(expectation); - }); - it('none', function () { - var expectation = 'd'; - var result = k.getTestSignalCommand('none'); - return expect(result).to.eventually.equal(expectation); - }); - }); - describe('#getVersionNumber', function () { - it('should get the major version number from a github standard version string', () => { - var expectedVersion = 6; - var inputStringVersion = 'v6.0.0'; - - expect(k.getVersionNumber(inputStringVersion)).to.equal(expectedVersion); - }); - }); - describe('#getImpedanceSetter', function () { - describe('channel input selection works', function () { - it('channel 2', function (done) { - k.getImpedanceSetter(2, false, false).then(function (arrayOfCommands) { - arrayOfCommands[1].should.equal('2'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 5', function (done) { - k.getImpedanceSetter(5, false, false).then(function (arrayOfCommands) { - arrayOfCommands[1].should.equal('5'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 9', function (done) { - k.getImpedanceSetter(9, false, false).then(function (arrayOfCommands) { - arrayOfCommands[1].should.equal('Q'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('channel 15', function (done) { - k.getImpedanceSetter(15, false, false).then(function (arrayOfCommands) { - arrayOfCommands[1].should.equal('U'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid channel selection', function (done) { - k.getImpedanceSetter(0, false, false).should.be.rejected.and.notify(done); - }); - it('Invalid type', function (done) { - k.getImpedanceSetter('1', false, false).should.be.rejected.and.notify(done); - }); - }); - describe('P Input selection works', function () { - it('Test Signal Applied', function (done) { - k.getImpedanceSetter(1, true, false).then(function (arrayOfCommands) { - arrayOfCommands[2].should.equal('1'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Test Signal Not Applied', function (done) { - k.getImpedanceSetter(1, false, false).then(function (arrayOfCommands) { - console.log('\n\n\narray: ' + arrayOfCommands + '\n\n\n'); - arrayOfCommands[2].should.equal('0'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getImpedanceSetter(1, 'taco', false).should.be.rejected.and.notify(done); - }); - }); - describe('N Input selection works', function () { - it('Test Signal Applied', function (done) { - k.getImpedanceSetter(1, false, true).then(function (arrayOfCommands) { - arrayOfCommands[3].should.equal('1'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Test Signal Not Applied', function (done) { - k.getImpedanceSetter(1, false, false).then(function (arrayOfCommands) { - arrayOfCommands[3].should.equal('0'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Invalid type', function (done) { - k.getImpedanceSetter(1, false, 'taco').should.be.rejected.and.notify(done); - }); - }); - describe('Prefix and postfix commands work', function () { - it('Set', function (done) { - k.getImpedanceSetter(1, true, true).then(function (arrayOfCommands) { - arrayOfCommands[0].should.equal('z'); - done(); - }).catch(function (err) { - done(err); - }); - }); - it('Latch', function (done) { - k.getImpedanceSetter(1, true, true).then(function (arrayOfCommands) { - arrayOfCommands[4].should.equal('Z'); - done(); - }).catch(function (err) { - done(err); - }); - }); - }); - }); - describe('fun funcs', function () { - describe('#isNumber', function () { - it('should return true for object of named type', function () { - expect(k.isNumber(1)).to.equal(true); - }); - it('should return false for not of named type', function () { - expect(k.isNumber('taco')).to.equal(false); - }); - }); - describe('#isBoolean', function () { - it('should return true for object of named type', function () { - expect(k.isBoolean(true)).to.equal(true); - }); - it('should return false for not of named type', function () { - expect(k.isBoolean('taco')).to.equal(false); - }); - }); - describe('#isString', function () { - it('should return true for object of named type', function () { - expect(k.isString('taco')).to.equal(true); - }); - it('should return false for not of named type', function () { - expect(k.isString(1)).to.equal(false); - }); - }); - describe('#isUndefined', function () { - it('should return true for object of named type', function () { - expect(k.isUndefined()).to.equal(true); - }); - it('should return false for not of named type', function () { - expect(k.isUndefined('im here')).to.equal(false); - }); - }); - describe('#isNull', function () { - it('should return true for object of named type', function () { - expect(k.isNull(null)).to.equal(true); - }); - it('should return false for not of named type', function () { - expect(k.isNull()).to.equal(false); - }); - it('should return false for not of named type', function () { - expect(k.isNull('taco')).to.equal(false); - }); - }); - }); - describe('Emitters', function () { - it('Event Emitter Close', function () { - assert.equal('close', k.OBCIEmitterClose); - }); - it('Event Emitter Dropped Packet', function () { - assert.equal('droppedPacket', k.OBCIEmitterDroppedPacket); - }); - it('Event Emitter EOT', function () { - assert.equal('eot', k.OBCIEmitterEot); - }); - it('Event Emitter Error', function () { - assert.equal('error', k.OBCIEmitterError); - }); - it('Event Emitter Hard Set', function () { - assert.equal('hardSet', k.OBCIEmitterHardSet); - }); - it('Event Emitter Impedance Array', function () { - assert.equal('impedanceArray', k.OBCIEmitterImpedanceArray); - }); - it('Event Emitter Message', function () { - assert.equal('message', k.OBCIEmitterMessage); - }); - it('Event Emitter Query', function () { - assert.equal('query', k.OBCIEmitterQuery); - }); - it('Event Emitter Raw Data Packet', function () { - assert.equal('rawDataPacket', k.OBCIEmitterRawDataPacket); - }); - it('Event Emitter Ready', function () { - assert.equal('ready', k.OBCIEmitterReady); - }); - it('Event Emitter Sample', function () { - assert.equal('sample', k.OBCIEmitterSample); - }); - }); -}); diff --git a/test/OpenBCISample-test.js b/test/OpenBCISample-test.js deleted file mode 100644 index 0e0d178..0000000 --- a/test/OpenBCISample-test.js +++ /dev/null @@ -1,1299 +0,0 @@ -/** -* Created by ajk on 12/15/15. -*/ -'use strict'; -const bluebirdChecks = require('./bluebirdChecks'); -const openBCISample = require('../openBCISample'); -const chai = require('chai'); -const expect = chai.expect; -const assert = chai.assert; -const should = chai.should(); // eslint-disable-line no-unused-vars - -const chaiAsPromised = require('chai-as-promised'); -const sinonChai = require('sinon-chai'); -chai.use(chaiAsPromised); -chai.use(sinonChai); -const bufferEqual = require('buffer-equal'); - -const k = require('../openBCIConstants'); - -const defaultChannelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); - -const sampleBuf = openBCISample.samplePacket(); - -let accelArray; - -const channelScaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); - -describe('openBCISample', function () { - beforeEach(function () { - accelArray = [0, 0, 0]; - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - describe('#parseRawPacketStandard', function () { - this.timeout(4000); - it('should return the packet', function () { - expect(openBCISample.parseRawPacketStandard(sampleBuf)).to.not.be.null; - }); - it('should have the correct sample number', function () { - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - - (sample.sampleNumber).should.equal(0x45); - }); - it('all the channels should have the same number value as their (index + 1) * scaleFactor', function () { - // sampleBuf has its channel number for each 3 byte integer. See line 20... - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - - // So parse the sample we created and each value resulting from the channelData array should - // be its index + 1 (i.e. channel number) multiplied by the channel scale factor set by the - // ADS1299 for a gain of 24 (default) - sample.channelData.forEach((channelValue, index) => { - channelValue.should.equal(channelScaleFactor * (index + 1)); - }); - }); - it('gets the 6-byte raw aux buffer', function () { - let sample = openBCISample.parseRawPacketStandard(sampleBuf, defaultChannelSettingsArray, true); - Buffer.isBuffer(sample.auxData).should.be.equal(true); - }); - it('all the auxs should have the same number value as their index * scaleFactor', function () { - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - - sample.accelData.forEach((accelValue, index) => { - accelValue.should.equal(openBCISample.scaleFactorAux * index); - }); - }); - it('check to see if negative numbers work on channel data', function () { - var temp = openBCISample.samplePacket(); - // console.log(temp) - var taco = new Buffer([0x81]); - taco.copy(temp, 2); - let sample = openBCISample.parseRawPacketStandard(temp); - assert.equal(sample.channelData[0], channelScaleFactor * -8323071, 'Negative numbers not working correctly'); - }); - it('check to see if negative numbers work on aux data', function () { - var temp = openBCISample.samplePacket(); - var taco = new Buffer([0x81]); - taco.copy(temp, 26); - let sample = openBCISample.parseRawPacketStandard(temp); - sample.accelData[0].should.be.approximately(-32512 * openBCISample.scaleFactorAux, 1); - }); - it('should work on 200 samples', function () { - var numberOfSamplesToTest = 200; - var samplesReceived = 0; - - for (var i = 0; i < numberOfSamplesToTest; i++) { - var temp = openBCISample.samplePacket(i); - // console.log(temp) - var taco = new Buffer([i]); - taco.copy(temp, 2); - let sample = openBCISample.parseRawPacketStandard(temp); - expect(sample.sampleNumber).to.equal(samplesReceived); - samplesReceived++; - } - }); - it('has the right sample number', function () { - var expectedSampleNumber = 0x45; - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - sample.sampleNumber.should.equal(expectedSampleNumber); - }); - it('has the right stop byte', function () { - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - sample.stopByte.should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)); - }); - it('has the right start byte', function () { - let sample = openBCISample.parseRawPacketStandard(sampleBuf); - sample.startByte.should.equal(0xA0); - }); - describe('#errorConditions', function () { - it('send non data buffer', function () { - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, 1)).to.throw(k.OBCIErrorInvalidByteLength); - }); - it('bad start byte', function () { - var temp = openBCISample.samplePacket(); - temp[0] = 69; - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, temp)).to.throw(k.OBCIErrorInvalidByteStart); - }); - it('wrong number of bytes', function () { - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, new Buffer(5))).to.throw(k.OBCIErrorInvalidByteLength); - }); - it('undefined', function () { - expect(openBCISample.parseRawPacketStandard.bind(openBCISample)).to.throw(k.OBCIErrorUndefinedOrNullInput); - }); - }); - }); - describe('#parsePacketStandardRawAux', function () { - var packet; - it('gets 6-byte buffer', function () { - // Get a packet - // This packet has aux bytes with the same value as their index - packet = openBCISample.samplePacketStandardRawAux(0); - - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - Buffer.isBuffer(sample.auxData).should.equal(true); - }); - it('gets the correct 6-byte buffer', function () { - // Get a packet - // This packet has aux bytes with the same value as their index - packet = openBCISample.samplePacketStandardRawAux(0); - - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - for (var i = 0; i < 6; i++) { - sample.auxData[i].should.equal(i); - } - }); - it('all the channels should have the same number value as their (index + 1) * scaleFactor', function () { - packet = openBCISample.samplePacketStandardRawAux(0); - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - sample.channelData.forEach((channelValue, index) => { - channelValue.should.equal(channelScaleFactor * (index + 1)); - }); - }); - it('has the right sample number', function () { - var expectedSampleNumber = 69; - packet = openBCISample.samplePacketStandardRawAux(expectedSampleNumber); - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - sample.sampleNumber.should.equal(expectedSampleNumber); - }); - it('has the right stop byte', function () { - packet = openBCISample.samplePacketStandardRawAux(0); - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - sample.stopByte.should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux)); - }); - it('has the right start byte', function () { - packet = openBCISample.samplePacketStandardRawAux(0); - let sample = openBCISample.parseRawPacketStandard(packet, defaultChannelSettingsArray, false); - sample.startByte.should.equal(0xA0); - }); - describe('#errorConditions', function () { - it('send non data buffer', function () { - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, 1)).to.throw(k.OBCIErrorInvalidByteLength); - }); - it('bad start byte', function () { - var temp = openBCISample.samplePacket(); - temp[0] = 69; - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, temp, defaultChannelSettingsArray, false)).to.throw(k.OBCIErrorInvalidByteStart); - }); - it('wrong number of bytes', function () { - expect(openBCISample.parseRawPacketStandard.bind(openBCISample, new Buffer(5), defaultChannelSettingsArray, false)).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#getFromTimePacketTime', function () { - it('should extract the proper time value from packet', function () { - let sampleWithTime = openBCISample.samplePacketAccelTimeSynced(0); - let time = openBCISample.getFromTimePacketTime(sampleWithTime); - time.should.equal(1); - }); - describe('#errorConditions', function () { - it('wrong number of bytes', function () { - expect(openBCISample.getFromTimePacketTime.bind(openBCISample, new Buffer(5))).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#getFromTimePacketAccel', function () { - var packet; - - it('should emit and array if z axis i.e. sampleNumber % 10 === 9', function () { - // Make a packet with a sample number that represents z axis - packet = openBCISample.samplePacketAccelTimeSynced(9); - let isZAxis = openBCISample.getFromTimePacketAccel(packet, accelArray); - isZAxis.should.equal(true); - }); - it(`false if sample number is not sampleNumber % 10 === ${k.OBCIAccelAxisZ}`, function () { - // Make a packet that is anything but the z axis - packet = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX); - let isZAxis = openBCISample.getFromTimePacketAccel(packet, accelArray); - isZAxis.should.equal(false); - - packet = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY); - isZAxis = openBCISample.getFromTimePacketAccel(packet, accelArray); - isZAxis.should.equal(false); - - packet = openBCISample.samplePacketAccelTimeSynced(34); - isZAxis = openBCISample.getFromTimePacketAccel(packet, accelArray); - isZAxis.should.equal(false); - - packet = openBCISample.samplePacketAccelTimeSynced(100); - isZAxis = openBCISample.getFromTimePacketAccel(packet, accelArray); - isZAxis.should.equal(false); - }); - describe('#errorConditions', function () { - it('wrong number of bytes', function () { - expect(openBCISample.getFromTimePacketAccel.bind(openBCISample, new Buffer(5))).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#parsePacketTimeSyncedAccel', function () { - // Global array (at least it's global in practice) to store accel data between packets - var packet1, packet2, packet3; - - it(`should only include accel data array on sampleNumber%10 === ${k.OBCIAccelAxisZ}`, function () { - // Generate three packets, packets only get one axis value per packet - // X axis data is sent with every sampleNumber % 10 === 7 - packet1 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX); - // Y axis data is sent with every sampleNumber % 10 === 8 - packet2 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY); - // Z axis data is sent with every sampleNumber % 10 === 9 - packet3 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisZ); - - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); - sample.should.not.have.property('accelData'); - - sample = openBCISample.parsePacketTimeSyncedAccel(packet2, defaultChannelSettingsArray, 0, accelArray); - sample.should.not.have.property('accelData'); - - sample = openBCISample.parsePacketTimeSyncedAccel(packet3, defaultChannelSettingsArray, 0, accelArray); - sample.should.have.property('accelData'); - }); - it("should convert raw numbers into g's with scale factor", function () { - // Generate three packets, packets only get one axis value per packet - // X axis data is sent with every sampleNumber % 10 === 7 - packet1 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX); - // Y axis data is sent with every sampleNumber % 10 === 8 - packet2 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY); - // Z axis data is sent with every sampleNumber % 10 === 9 - packet3 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisZ); - - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); - sample = openBCISample.parsePacketTimeSyncedAccel(packet2, defaultChannelSettingsArray, 0, accelArray); - sample = openBCISample.parsePacketTimeSyncedAccel(packet3, defaultChannelSettingsArray, 0, accelArray); - sample.accelData.forEach((accelValue, index) => { - accelValue.should.equal(openBCISample.scaleFactorAux); - }); - }); - it('all the channels should have the same number value as their (index + 1) * scaleFactor', function () { - packet1 = openBCISample.samplePacketAccelTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); // sampleBuf has its channel number for each 3 byte integer. See line 20... - sample.channelData.forEach((channelValue, index) => { - channelValue.should.equal(channelScaleFactor * (index + 1)); - }); - }); - it('has the right sample number', function () { - var expectedSampleNumber = 69; - packet1 = openBCISample.samplePacketAccelTimeSynced(expectedSampleNumber); - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); // sampleBuf has its channel number for each 3 byte integer. See line 20... - sample.sampleNumber.should.equal(expectedSampleNumber); - }); - it('has the right stop byte', function () { - packet1 = openBCISample.samplePacketAccelTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); // sampleBuf has its channel number for each 3 byte integer. See line 20... - sample.stopByte.should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced)); - }); - it('has the right start byte', function () { - packet1 = openBCISample.samplePacketAccelTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedAccel(packet1, defaultChannelSettingsArray, 0, accelArray); // sampleBuf has its channel number for each 3 byte integer. See line 20... - sample.startByte.should.equal(0xA0); - }); - describe('#errorConditions', function () { - it('wrong number of bytes', function () { - expect(openBCISample.parsePacketTimeSyncedAccel.bind(openBCISample, new Buffer(5), defaultChannelSettingsArray, 0, accelArray)).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#getFromTimePacketRawAux', function () { - var packet; - it('should put the two aux bytes into a buffer', function () { - // Get a packet - packet = openBCISample.samplePacketRawAuxTimeSynced(0); - - let rawAuxBuffer = openBCISample.getFromTimePacketRawAux(packet); - Buffer.isBuffer(rawAuxBuffer).should.equal(true); - }); - describe('#errorConditions', function () { - it('wrong number of bytes', function () { - expect(openBCISample.getFromTimePacketRawAux.bind(openBCISample, new Buffer(5))).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#parsePacketTimeSyncedRawAux', function () { - var packet; - it('should put the two aux bytes into a buffer', function () { - // Generate three packets, packets only get one axis value per packet - packet = openBCISample.samplePacketRawAuxTimeSynced(0); - - let sample = openBCISample.parsePacketTimeSyncedRawAux(packet, defaultChannelSettingsArray, 0); - sample.should.have.property('auxData'); - sample.auxData[0].should.equal(0); - sample.auxData[1].should.equal(1); - sample.auxData.byteLength.should.equal(2); - }); - it('all the channels should have the same number value as their (index + 1) * scaleFactor', function () { - packet = openBCISample.samplePacketRawAuxTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedRawAux(packet, defaultChannelSettingsArray, 0); - sample.channelData.forEach((channelValue, index) => { - channelValue.should.equal(channelScaleFactor * (index + 1)); - }); - }); - it('has the right sample number', function () { - var expectedSampleNumber = 69; - packet = openBCISample.samplePacketRawAuxTimeSynced(expectedSampleNumber); - let sample = openBCISample.parsePacketTimeSyncedRawAux(packet, defaultChannelSettingsArray, 0); - sample.sampleNumber.should.equal(expectedSampleNumber); - }); - it('has the right stop byte', function () { - packet = openBCISample.samplePacketRawAuxTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedRawAux(packet, defaultChannelSettingsArray, 0); - sample.stopByte.should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced)); - }); - it('has the right start byte', function () { - packet = openBCISample.samplePacketRawAuxTimeSynced(0); - let sample = openBCISample.parsePacketTimeSyncedRawAux(packet, defaultChannelSettingsArray, 0); - sample.startByte.should.equal(0xA0); - }); - describe('#errorConditions', function () { - it('wrong number of bytes', function () { - expect(openBCISample.parsePacketTimeSyncedRawAux.bind(openBCISample, new Buffer(5), defaultChannelSettingsArray, 0, accelArray)).to.throw(k.OBCIErrorInvalidByteLength); - }); - }); - }); - describe('#convertSampleToPacketStandard', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketStandard(newSample); - - it('should have correct start byte', function () { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', function () { - packetBuffer[k.OBCIPacketSize - 1].should.equal(k.OBCIByteStop, 'confirming stop byte'); - }); - it('should have correct sample number', function () { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parseRawPacketStandard(packetBuffer); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should convert aux data to binary', function () { - let sample = openBCISample.parseRawPacketStandard(packetBuffer); - for (var i = 0; i < 3; i++) { - sample.accelData[i].should.be.approximately(newSample.auxData[i], 0.001); - } - }); - }); - describe('#convertSampleToPacketRawAux', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // Make a fake 6 byte buffer - var rawBuffer = new Buffer([0, 1, 2, 3, 4, 5]); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketRawAux(newSample, rawBuffer); - - it('should have correct start byte', function () { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', function () { - packetBuffer[k.OBCIPacketSize - 1].should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux), 'confirming stop byte'); - }); - it('should have correct sample number', function () { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parseRawPacketStandard(packetBuffer, defaultChannelSettingsArray, false); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should get raw aux buffer', function () { - let sample = openBCISample.parseRawPacketStandard(packetBuffer, defaultChannelSettingsArray, false); - expect(sample.auxData).to.exist; - expect(bufferEqual(rawBuffer, sample.auxData)).to.be.true; - }); - }); - describe('#convertSampleToPacketAccelTimeSyncSet', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // Make a time - var time = 1010101; - - // Accel array - var accelArray = [0, 0, 0]; - - // Channel Settings - var channelSettingsArray = k.channelSettingsArrayInit(8); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketAccelTimeSyncSet(newSample, time); - - it('should have correct start byte', () => { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', () => { - packetBuffer[k.OBCIPacketSize - 1].should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSyncSet), 'confirming stop byte'); - }); - it('should have correct sample number', () => { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, 0, accelArray); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should get board time', function () { - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, 0, accelArray); - expect(sample.boardTime).to.exist; - expect(sample.boardTime).to.equal(time); - }); - it('should get time stamp with offset', function () { - var timeOffset = 80; - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, timeOffset, accelArray); - expect(sample.timeStamp).to.exist; - expect(sample.timeStamp).to.equal(time + timeOffset); - }); - }); - describe('#convertSampleToPacketAccelTimeSynced', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // Make a time - var time = 1010101; - - // Accel array - var accelArray = [0, 0, 0]; - - // Channel Settings - var channelSettingsArray = k.channelSettingsArrayInit(8); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketAccelTimeSynced(newSample, time); - - it('should have correct start byte', () => { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', () => { - packetBuffer[k.OBCIPacketSize - 1].should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced), 'confirming stop byte'); - }); - it('should have correct sample number', () => { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, 0, accelArray); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should get board time', function () { - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, 0, accelArray); - expect(sample.boardTime).to.exist; - expect(sample.boardTime).to.equal(time); - }); - it('should get time stamp with offset', function () { - var timeOffset = 80; - let sample = openBCISample.parsePacketTimeSyncedAccel(packetBuffer, channelSettingsArray, timeOffset, accelArray); - expect(sample.timeStamp).to.exist; - expect(sample.timeStamp).to.equal(time + timeOffset); - }); - }); - describe('#convertSampleToPacketRawAuxTimeSyncSet', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // Make a time - var time = 1010101; - - // Raw Aux - var rawAux = new Buffer([0, 1]); - - // Channel Settings - var channelSettingsArray = k.channelSettingsArrayInit(8); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketRawAuxTimeSyncSet(newSample, time, rawAux); - - it('should have correct start byte', () => { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', () => { - packetBuffer[k.OBCIPacketSize - 1].should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSyncSet), 'confirming stop byte'); - }); - it('should have correct sample number', () => { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should get raw aux buffer', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - expect(sample.auxData).to.exist; - expect(bufferEqual(rawAux, sample.auxData)).to.be.true; - }); - it('should get board time', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - expect(sample.boardTime).to.exist; - expect(sample.boardTime).to.equal(time); - }); - it('should get time stamp with offset', function () { - var timeOffset = 80; - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, timeOffset); - expect(sample.timeStamp).to.exist; - expect(sample.timeStamp).to.equal(time + timeOffset); - }); - }); - describe('#convertSampleToPacketRawAuxTimeSynced', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - - // get new sample - var newSample = generateSample(0); - - // Make a time - var time = 1010101; - - // Raw Aux - var rawAux = new Buffer([0, 1]); - - // Channel Settings - var channelSettingsArray = k.channelSettingsArrayInit(8); - - // try to convert to packet - var packetBuffer = openBCISample.convertSampleToPacketRawAuxTimeSynced(newSample, time, rawAux); - - it('should have correct start byte', () => { - packetBuffer[0].should.equal(k.OBCIByteStart, 'confirming start byte'); - }); - it('should have correct stop byte', () => { - packetBuffer[k.OBCIPacketSize - 1].should.equal(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced), 'confirming stop byte'); - }); - it('should have correct sample number', () => { - packetBuffer[1].should.equal(1, 'confirming sample number is 1 more than 0'); - }); - it('should convert channel data to binary', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - sample.channelData[i].should.be.approximately(newSample.channelData[i], 0.001); - } - }); - it('should get raw aux buffer', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - expect(sample.auxData).to.exist; - expect(bufferEqual(rawAux, sample.auxData)).to.be.true; - }); - it('should get board time', function () { - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, 0); - expect(sample.boardTime).to.exist; - expect(sample.boardTime).to.equal(time); - }); - it('should get time stamp with offset', function () { - var timeOffset = 80; - let sample = openBCISample.parsePacketTimeSyncedRawAux(packetBuffer, channelSettingsArray, timeOffset); - expect(sample.timeStamp).to.exist; - expect(sample.timeStamp).to.equal(time + timeOffset); - }); - }); - describe('#interpret16bitAsInt32', function () { - it('converts a small positive number', function () { - var buf1 = new Buffer([0x06, 0x90]); // 0x0690 === 1680 - var num = openBCISample.interpret16bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a large positive number', function () { - var buf1 = new Buffer([0x02, 0xC0]); // 0x02C0 === 704 - var num = openBCISample.interpret16bitAsInt32(buf1); - assert.equal(num, 704); - }); - it('converts a small negative number', function () { - var buf1 = new Buffer([0xFF, 0xFF]); // 0xFFFF === -1 - var num = openBCISample.interpret16bitAsInt32(buf1); - assert.equal(num, -1); - }); - it('converts a large negative number', function () { - var buf1 = new Buffer([0x81, 0xA1]); // 0x81A1 === -32351 - var num = openBCISample.interpret16bitAsInt32(buf1); - assert.equal(num, -32351); - }); - }); - describe('#interpret24bitAsInt32', function () { - it('converts a small positive number', function () { - var buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a large positive number', function () { - var buf1 = new Buffer([0x02, 0xC0, 0x01]); // 0x02C001 === 180225 - var num = openBCISample.interpret24bitAsInt32(buf1); - assert.equal(num, 180225); - }); - it('converts a small negative number', function () { - var buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-1, 1); - }); - it('converts a large negative number', function () { - var buf1 = new Buffer([0x81, 0xA1, 0x01]); // 0x81A101 === -8281855 - var num = openBCISample.interpret24bitAsInt32(buf1); - num.should.be.approximately(-8281855, 1); - }); - }); - describe('#floatTo3ByteBuffer', function () { - it('converts random floats to a 3-byte buffer', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - var newSample = generateSample(0); - - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - var buff = openBCISample.floatTo3ByteBuffer(newSample.channelData[i]); - - var num = openBCISample.interpret24bitAsInt32(buff); - - num = num * channelScaleFactor; - - num.should.be.approximately(newSample.channelData[i], 0.00002); - } - }); - }); - describe('#floatTo2ByteBuffer', function () { - it('converts random floats to a 2-byte buffer', function () { - var auxData = [0.001, 1, -0.00892]; - - for (var i = 0; i < 3; i++) { - var buff = openBCISample.floatTo2ByteBuffer(auxData[i]); - - var num = openBCISample.interpret16bitAsInt32(buff); - - num = num * openBCISample.scaleFactorAux; - - num.should.be.approximately(auxData[i], 0.001); - } - }); - }); - describe('#randomSample', function () { - it('should generate a random sample', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - var oldSampleNumber = 0; - var newSample = generateSample(oldSampleNumber); - assert(newSample.sampleNumber, oldSampleNumber + 1); - describe('#debugPrettyPrint', function () { - it('works with a good sample', function () { - openBCISample.debugPrettyPrint(newSample); - }); - it('does not with a undefined sample', function () { - openBCISample.debugPrettyPrint(); - }); - }); - }); - it('should generate a sample with accel data every 25Hz', function () { - var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250); - var newSample = generateSample(0); - - var passed = false; - // Should get one non-zero auxData array (on the 10th sample) - for (var i = 0; i < 10; i++) { - newSample = generateSample(newSample.sampleNumber); - if (newSample.auxData[0] !== 0 || newSample.auxData[1] !== 0 || newSample.auxData[2] !== 0) { - passed = true; - newSample.auxData[0].should.be.approximately(0, 0.1); - newSample.auxData[1].should.be.approximately(0, 0.1); - newSample.auxData[2].should.be.approximately(1, 0.4); - } - } - assert(passed, 'a sample with accel data was produced'); - }); - }); - describe('#impedanceSummarize', function () { - var impedanceArray = []; - var numberOfChannels = 8; - beforeEach(() => { - impedanceArray = openBCISample.impedanceArray(numberOfChannels); - }); - it('should find impedance good', function () { - impedanceArray[0].N.raw = 2201.84; - - openBCISample.impedanceSummarize(impedanceArray[0].N); - - impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextGood); // Check the text - }); - it('should find impedance ok', function () { - impedanceArray[0].N.raw = 5201.84; - - openBCISample.impedanceSummarize(impedanceArray[0].N); - - impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextOk); // Check the text - }); - it('should find impedance bad', function () { - impedanceArray[0].N.raw = 10201.84; - - openBCISample.impedanceSummarize(impedanceArray[0].N); - - impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextBad); // Check the text - }); - it('should find impedance none', function () { - impedanceArray[0].N.data = 44194179.09; // A huge number that would be seen if there was no electrode connected - - openBCISample.impedanceSummarize(impedanceArray[0].N); - - impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextNone); // Check the text - }); - }); - describe('#makeDaisySampleObject', function () { - var lowerSampleObject, upperSampleObject, daisySampleObject; - before(() => { - // Make the lower sample (channels 1-8) - lowerSampleObject = openBCISample.newSample(1); - lowerSampleObject.channelData = [1, 2, 3, 4, 5, 6, 7, 8]; - lowerSampleObject.auxData = [0, 1, 2]; - lowerSampleObject.timestamp = 4; - lowerSampleObject.accelData = [0.5, -0.5, 1]; - // Make the upper sample (channels 9-16) - upperSampleObject = openBCISample.newSample(2); - upperSampleObject.channelData = [9, 10, 11, 12, 13, 14, 15, 16]; - upperSampleObject.auxData = [3, 4, 5]; - upperSampleObject.timestamp = 8; - - // Call the function under test - daisySampleObject = openBCISample.makeDaisySampleObject(lowerSampleObject, upperSampleObject); - }); - it('should make a channelData array 16 elements long', function () { - daisySampleObject.channelData.length.should.equal(k.OBCINumberOfChannelsDaisy); - }); - it('should make a channelData array with lower array in front of upper array', function () { - for (var i = 0; i < 16; i++) { - expect(daisySampleObject.channelData[i]).to.equal(i + 1); - } - }); - it('should make the sample number equal to the second packet divided by two', function () { - daisySampleObject.sampleNumber.should.equal(upperSampleObject.sampleNumber / 2); - }); - it('should put the aux packets in an object', function () { - daisySampleObject.auxData.hasOwnProperty('lower').should.be.true; - daisySampleObject.auxData.hasOwnProperty('upper').should.be.true; - }); - it('should put the aux packets in an object in the right order', function () { - for (var i = 0; i < 3; i++) { - expect(daisySampleObject.auxData['lower'][i]).to.equal(i); - expect(daisySampleObject.auxData['upper'][i]).to.equal(i + 3); - } - }); - it('should average the two timestamps together', function () { - var expectedAverage = (upperSampleObject.timestamp + lowerSampleObject.timestamp) / 2; - daisySampleObject.timestamp.should.equal(expectedAverage); - }); - it('should place the old timestamps in an object', function () { - daisySampleObject._timestamps.lower.should.equal(lowerSampleObject.timestamp); - daisySampleObject._timestamps.upper.should.equal(upperSampleObject.timestamp); - }); - it('should store an accelerometer value if present', function () { - daisySampleObject.should.have.property('accelData'); - }); - }); - describe('#isEven', function () { - it('should return true for even number', function () { - openBCISample.isEven(2).should.be.true; - }); - it('should return false for odd number', function () { - openBCISample.isEven(1).should.be.false; - }); - }); - describe('#isOdd', function () { - it('should return true for odd number', function () { - openBCISample.isOdd(1).should.be.true; - }); - it('should return false for even number', function () { - openBCISample.isOdd(2).should.be.false; - }); - }); - describe('#getChannelDataArray', function () { - var sampleBuf, badChanArray; - beforeEach(() => { - sampleBuf = openBCISample.samplePacket(0); - }); - it('should multiply each channel by the proper scale value', function () { - var chanArr = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); // Not in daisy mode - var scaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); - // Call the function under test - let valueArray = openBCISample.getChannelDataArray(sampleBuf, chanArr); - for (var j = 0; j < k.OBCINumberOfChannelsDefault; j++) { - console.log(`channel data ${j + 1}: ${valueArray[j]} : actual ${scaleFactor * (j + 1)}`); - expect(valueArray[j]).to.be.closeTo(scaleFactor * (j + 1), 0.0001); - } - }); - it('in daisy mode, on odd samples should use gains from index 0-7 of channel settings array', function () { - // Overwrite the default - sampleBuf = openBCISample.samplePacket(1); // even's are the daisy channels - // Make a 16 element long channel settings array - var chanArr = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); - // Set the upper (8-15) of channel settings array. If the function under test uses the 1 gain, then the test - // will fail. - for (var i = k.OBCINumberOfChannelsDefault; i < k.OBCINumberOfChannelsDaisy; i++) { - chanArr[i].gain = 1; - } - var scaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); - // Call the function under test - let valueArray = openBCISample.getChannelDataArray(sampleBuf, chanArr); - for (var j = 0; j < k.OBCINumberOfChannelsDefault; j++) { - // console.log(`channel data ${j + 1}: ${valueArray[j]} : actual ${scaleFactor * (j + 1)}`) - expect(valueArray[j]).to.be.closeTo(scaleFactor * (j + 1), 0.0001); - } - }); - it('in daisy mode, on even samples should use gains from index 8-15 of channel settings array', function () { - // Overwrite the default - sampleBuf = openBCISample.samplePacket(2); // even's are the daisy channels - // Make a 16 element long channel settings array - var chanArr = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); - // Set the lower (0-7) of channel settings array. If the function under test uses the 1 gain, then the test - // will fail. - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - chanArr[i].gain = 1; - } - // gain here is 24, the same as in the channel settings array - var scaleFactor = 4.5 / 24 / (Math.pow(2, 23) - 1); - // Call the function under test - let valueArray = openBCISample.getChannelDataArray(sampleBuf, chanArr); - for (var j = 0; j < k.OBCINumberOfChannelsDefault; j++) { - // console.log(`channel data ${j + 1}: ${valueArray[j]} : actual ${scaleFactor * (j + 1)}`) - expect(valueArray[j]).to.be.closeTo(scaleFactor * (j + 1), 0.0001); - } - }); - it('in default mode, should reject when empty channel setting array', function () { - badChanArray = new Array(k.OBCINumberOfChannelsDefault).fill(0); - expect(openBCISample.getChannelDataArray.bind(openBCISample, sampleBuf, badChanArray)).to.throw('Error [getChannelDataArray]: Invalid channel settings object at index 0'); - }); - it('in daisy mode, should reject when empty channel setting array', function () { - badChanArray = new Array(k.OBCINumberOfChannelsDaisy).fill(0); - expect(openBCISample.getChannelDataArray.bind(openBCISample, sampleBuf, badChanArray)).to.throw('Error [getChannelDataArray]: Invalid channel settings object at index 0'); - }); - it('in default mode, should reject if not numbers in gain position', function () { - badChanArray = []; - for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) { - badChanArray.push({ - gain: 'taco' - }); - } - expect(openBCISample.getChannelDataArray.bind(openBCISample, sampleBuf, badChanArray)).to.throw('Error [getChannelDataArray]: Property gain of channelSettingsObject not or type Number'); - }); - it('in daisy mode, should reject if not numbers in gain position', function () { - badChanArray = []; - for (var i = 0; i < k.OBCINumberOfChannelsDaisy; i++) { - badChanArray.push({ - gain: 'taco' - }); - } - expect(openBCISample.getChannelDataArray.bind(openBCISample, sampleBuf, badChanArray)).to.throw('Error [getChannelDataArray]: Property gain of channelSettingsObject not or type Number'); - }); - it('should reject when channelSettingsArray is not in fact an array', function () { - expect(openBCISample.getChannelDataArray.bind(openBCISample, sampleBuf, {})).to.throw('Error [getChannelDataArray]: Channel Settings must be an array!'); - }); - }); - describe('#countADSPresent', function () { - it('should not crash on small buff', function () { - var buf = new Buffer('AJ!'); - - openBCISample.countADSPresent(buf).should.equal(0); - }); - it('should not find any ADS1299 present', function () { - var buf = new Buffer('AJ Keller is an awesome programmer!\n I know right!'); - - openBCISample.countADSPresent(buf).should.equal(0); - }); - it('should find one ads present', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -LIS3DH Device ID: 0x38422$$$`); - - openBCISample.countADSPresent(buf).should.equal(1); - }); - it('should find two ads1299 present', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -$$$`); - - openBCISample.countADSPresent(buf).should.equal(2); - }); - }); - describe('#doesBufferHaveEOT', function () { - it('should not crash on small buff', function () { - var buf = new Buffer('AJ!'); - - openBCISample.doesBufferHaveEOT(buf).should.equal(false); - }); - it('should not find any $$$', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -Firmware: v2 -`); - - openBCISample.doesBufferHaveEOT(buf).should.equal(false); - - buf = Buffer.concat([buf, new Buffer(k.OBCIParseEOT)], buf.length + 3); - - openBCISample.doesBufferHaveEOT(buf).should.equal(true); - }); - it('should find a $$$', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -Firmware: v2 -$$$`); - - openBCISample.doesBufferHaveEOT(buf).should.equal(true); - }); - }); - describe('#findV2Firmware', function () { - it('should not crash on small buff', function () { - var buf = new Buffer('AJ!'); - - openBCISample.findV2Firmware(buf).should.equal(false); - }); - it('should not find any v2', function () { - var buf = new Buffer('AJ Keller is an awesome programmer!\n I know right!'); - - openBCISample.findV2Firmware(buf).should.equal(false); - }); - it('should not find a v2', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -LIS3DH Device ID: 0x38422$$$`); - - openBCISample.findV2Firmware(buf).should.equal(false); - }); - it('should find a v2', function () { - var buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -Firmware: v2 -$$$`); - - openBCISample.findV2Firmware(buf).should.equal(true); - }); - }); - describe('#isFailureInBuffer', function () { - it('should not crash on small buff', function () { - var buf = new Buffer('AJ!'); - - openBCISample.isFailureInBuffer(buf).should.equal(false); - }); - it('should not find any failure in a success message', function () { - var buf = new Buffer('Success: Poll time set$$$'); - - openBCISample.isFailureInBuffer(buf).should.equal(false); - }); - it('should find failure in a failure message', function () { - var buf = new Buffer('Failure: Could not change Dongle channel number$$$'); - - openBCISample.isFailureInBuffer(buf).should.equal(true); - }); - }); - describe('#isSuccessInBuffer', function () { - it('should not crash on small buff', function () { - var buf = new Buffer('AJ!'); - - openBCISample.isSuccessInBuffer(buf).should.equal(false); - }); - it('should not find any success in a failure message', function () { - var buf = new Buffer('Failure: Could not change Dongle channel number'); - - openBCISample.isSuccessInBuffer(buf).should.equal(false); - }); - it('should find success in a success message', function () { - var buf = new Buffer('Success: Poll time set$$$'); - - openBCISample.isSuccessInBuffer(buf).should.equal(true); - }); - }); - - describe('#isStopByte', function () { - it('should return true for a normal stop byte', () => { - expect(openBCISample.isStopByte(0xC0)).to.be.true; - }); - it('should return true for a good stop byte with a different end nibble', () => { - expect(openBCISample.isStopByte(0xCF)).to.be.true; - }); - it('should return false for a bad stop byte', () => { - expect(openBCISample.isStopByte(0xF0)).to.be.false; - }); - it('should return false for a bad stop byte', () => { - expect(openBCISample.isStopByte(0x00)).to.be.false; - }); - }); - - describe('#isTimeSyncSetConfirmationInBuffer', function () { - // Attn: 0x2C is ASCII for ',' - var comma = 0x2C; - it('should not find the character in a buffer without the character', function () { - openBCISample.isTimeSyncSetConfirmationInBuffer(openBCISample.samplePacket()).should.equal(false); - }); - it('should find with just 0x2C', function () { - var buffer = new Buffer([comma]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'just comma').to.be.true; - }); - it('should find even at start of buffer', function () { - // Start of buffer - var buffer = new Buffer([comma, k.OBCIByteStart]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'before packet').to.be.true; - }); - it('should find even at back of buffer', function () { - // Back of buffer - var buffer = new Buffer([0xC0, comma]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'after packet').to.be.true; - }); - it('should find wedged beween two packets', function () { - // / wedged - var buffer = new Buffer([0xC0, comma, 0xA0]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'wedged between packets').to.be.true; - }); - it('should not find if no comma present', function () { - // / wedged - var buffer = new Buffer([0x2D]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'not comma').to.be.false; - }); - it('should not find if comma at the front of bad block', function () { - // Start of buffer - var buffer = new Buffer([comma, 0xCC]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'front of buffer').to.be.false; - }); - it('should not find if comma at the back of bad block', function () { - // Back of buffer - var buffer = new Buffer([0xD3, comma]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'end of buffer').to.be.false; - }); - it('should not find is not the comma', function () { - // Wedged - var buffer = new Buffer([comma, comma, comma]); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer), 'strictly commas').to.be.false; - }); - it('should find the character in a buffer packed with samples', function () { - var buf1 = openBCISample.samplePacket(1); - var buf2 = openBCISample.samplePacket(2); - var buf3 = new Buffer([0x2C]); - var buf4 = openBCISample.samplePacket(3); - - var bufferLength = buf1.length + buf2.length + buf3.length + buf4.length; - /* eslint new-cap: ["error", { "properties": false }] */ - var buffer = new Buffer.concat([buf1, buf2, buf3, buf4], bufferLength); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer)).to.be.true; - }); - it('should find the character in a buffer packed with samples with comma at end', function () { - var buf1 = openBCISample.samplePacket(1); - var buf2 = openBCISample.samplePacket(2); - var buf3 = openBCISample.samplePacket(3); - var buf4 = new Buffer([0x2C]); - - var bufferLength = buf1.length + buf2.length + buf3.length + buf4.length; - /* eslint new-cap: ["error", { "properties": false }] */ - var buffer = new Buffer.concat([buf1, buf2, buf3, buf4], bufferLength); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer)).to.be.true; - }); - it('should not find the character in a buffer packed with samples', function () { - var buf1 = openBCISample.samplePacket(1); - var buf2 = openBCISample.samplePacket(2); - var buf3 = openBCISample.samplePacket(3); - - var bufferLength = buf1.length + buf2.length + buf3.length; - /* eslint new-cap: ["error", { "properties": false }] */ - var buffer = new Buffer.concat([buf1, buf2, buf3], bufferLength); - expect(openBCISample.isTimeSyncSetConfirmationInBuffer(buffer)).to.be.false; - }); - }); - describe('#makeTailByteFromPacketType', function () { - it('should convert 0 to 0xC0', function () { - expect(openBCISample.makeTailByteFromPacketType(0)).to.equal(0xC0); - }); - it('should convert 5 to 0xC5', function () { - expect(openBCISample.makeTailByteFromPacketType(5)).to.equal(0xC5); - }); - it('should convert 15 to 0xCF', function () { - expect(openBCISample.makeTailByteFromPacketType(15)).to.equal(0xCF); - }); - it('should convert 16 to 0xC0', function () { - expect(openBCISample.makeTailByteFromPacketType(16)).to.equal(0xC0); - }); - it('should convert 30 to 0xC0', function () { - expect(openBCISample.makeTailByteFromPacketType(30)).to.equal(0xC0); - }); - it('should convert -2 to 0xC0', function () { - expect(openBCISample.makeTailByteFromPacketType(-2)).to.equal(0xC0); - }); - }); - describe('#newSyncObject', function () { - var syncObj = openBCISample.newSyncObject(); - it('should have property timeSyncSent', function () { - expect(syncObj).to.have.property('timeSyncSent', 0); - }); - it('should have property timeOffset', function () { - expect(syncObj).to.have.property('timeOffset', 0); - }); - it('should have property timeOffsetMaster', function () { - expect(syncObj).to.have.property('timeOffsetMaster', 0); - }); - it('should have property timeRoundTrip', function () { - expect(syncObj).to.have.property('timeRoundTrip', 0); - }); - it('should have property timeTransmission', function () { - expect(syncObj).to.have.property('timeTransmission', 0); - }); - it('should have property timeSyncSentConfirmation', function () { - expect(syncObj).to.have.property('timeSyncSentConfirmation', 0); - }); - it('should have property timeSyncSetPacket', function () { - expect(syncObj).to.have.property('timeSyncSetPacket', 0); - }); - it('should have property valid', function () { - expect(syncObj).to.have.property('valid', false); - }); - it('should have property correctedTransmissionTime', function () { - expect(syncObj).to.have.property('correctedTransmissionTime', false); - }); - it('should have property boardTime', function () { - expect(syncObj).to.have.property('boardTime', 0); - }); - it('should have property error', function () { - expect(syncObj).to.have.property('error', null); - }); - }); - describe('#droppedPacketCheck', function () { - it('should return an array of missed packet numbers', function () { - var previous = 0; - var current = previous + 2; - assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [1], 'dropped one packet'); - - previous = 0; - current = previous + 4; - assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [1, 2, 3], 'dropped three packets'); - - previous = 255; - current = 2; - assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [0, 1], 'dropped two packets on wrap edge!'); - - previous = 254; - current = 2; - assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [255, 0, 1], 'dropped three packets on wrap!'); - - previous = 250; - current = 1; - assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [251, 252, 253, 254, 255, 0], 'dropped a bunch of packets on wrap!'); - }); - it('should roll over when 255 was previous and current is 0', function () { - var previous = 255; - var current = 0; - expect(openBCISample.droppedPacketCheck(previous, current)).to.be.null; - }); - it('should return null when previous is one less then new sample number', function () { - var previous = 0; - var current = previous + 1; - expect(openBCISample.droppedPacketCheck(previous, current)).to.be.null; - }); - }); - describe('#stripToEOTBuffer', function () { - it('should return the buffer if no EOT', function () { - let buf = null; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - buf = Buffer.from('tacos are delicious'); - } else { - buf = new Buffer('tacos are delicious'); - } - expect(openBCISample.stripToEOTBuffer(buf).toString()).to.equal(buf.toString()); - }); - it('should slice the buffer after just eot $$$', function () { - let eotBuf = null; - let bufPost = null; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - eotBuf = Buffer.from(k.OBCIParseEOT); - bufPost = Buffer.from('tacos'); - } else { - eotBuf = new Buffer(k.OBCIParseEOT); - bufPost = new Buffer('tacos'); - } - - let totalBuf = Buffer.concat([eotBuf, bufPost]); - expect(openBCISample.stripToEOTBuffer(totalBuf).toString()).to.equal(bufPost.toString()); - }); - it('should slice the buffer after eot $$$', function () { - let bufPre = null; - let eotBuf = null; - let bufPost = null; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - bufPre = Buffer.from('tacos are delicious'); - eotBuf = Buffer.from(k.OBCIParseEOT); - bufPost = Buffer.from('tacos'); - } else { - bufPre = new Buffer('tacos are delicious'); - eotBuf = new Buffer(k.OBCIParseEOT); - bufPost = new Buffer('tacos'); - } - - let totalBuf = Buffer.concat([bufPre, eotBuf, bufPost]); - expect(openBCISample.stripToEOTBuffer(totalBuf).toString()).to.equal(bufPost.toString()); - }); - it('should return null if nothing left', function () { - let bufPre = null; - let eotBuf = null; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - bufPre = Buffer.from('tacos are delicious'); - eotBuf = Buffer.from(k.OBCIParseEOT); - } else { - bufPre = new Buffer('tacos are delicious'); - eotBuf = new Buffer(k.OBCIParseEOT); - } - - let totalBuf = Buffer.concat([bufPre, eotBuf]); - expect(openBCISample.stripToEOTBuffer(totalBuf)).to.equal(null); - }); - }); - describe('#impedanceTestObjDefault', function () { - it('should give a new impedance object', function () { - const expectedImpedanceObj = { - active: false, - buffer: [], - count: 0, - isTestingPInput: false, - isTestingNInput: false, - onChannel: 0, - sampleNumber: 0, - continuousMode: false, - impedanceForChannel: 0, - window: 40 - }; - expect(openBCISample.impedanceTestObjDefault()).to.deep.equal(expectedImpedanceObj); - }); - }); - describe('#impedanceCalculateArray', function () { - const numberOfChannels = k.OBCINumberOfChannelsDefault; - const newRandomSample = openBCISample.randomSample(numberOfChannels, k.OBCISampleRate250, false, 'none'); - - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not produce an array of impedances till window', function () { - const impTestObj = openBCISample.impedanceTestObjDefault(); - for (let i = 0; i < impTestObj.window - 1; i++) { - expect(openBCISample.impedanceCalculateArray(newRandomSample(i), impTestObj)).to.equal(null); - } - expect(impTestObj.buffer.length).to.equal(impTestObj.window - 1); - }); - it('should produce and array of impedances at window', function () { - const impTestObj = openBCISample.impedanceTestObjDefault(); - let impedanceArray = null; - for (let i = 0; i < impTestObj.window; i++) { - impedanceArray = openBCISample.impedanceCalculateArray(newRandomSample(i), impTestObj); - } - expect(impedanceArray.length).to.equal(numberOfChannels); - }); - }); -}); From c45ddf851618fc05196491f10feb185b8cc765c7 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Sun, 16 Jul 2017 21:00:03 -0400 Subject: [PATCH 12/42] Almost done with rewrite --- openBCICyton.js | 231 ++++++++---------------------------------------- package.json | 4 +- 2 files changed, 40 insertions(+), 195 deletions(-) diff --git a/openBCICyton.js b/openBCICyton.js index 5eb94d4..f23e122 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -11,6 +11,7 @@ const openBCISimulator = require('./openBCISimulator'); const Sntp = require('sntp'); const bufferEqual = require('buffer-equal'); const math = require('mathjs'); +const _ = require('lodash'); /** * @typedef {Object} InitializationObject Board optional configurations. @@ -90,6 +91,9 @@ const math = require('mathjs'); * @property {Boolean} verbose Print out useful debugging events. (Default `false`) * * @property {Boolean} debug Print out a raw dump of bytes sent and received. (Default `false`) + * + * @property {Boolean} sendCounts - Send integer raw counts instead of scaled floats. + * (Default `false`) */ /** @@ -101,6 +105,7 @@ var _options = { boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], baudRate: 115200, hardSet: false, + sendCounts: false, simulate: false, simulatorBoardFailure: false, simulatorDaisyModuleAttached: false, @@ -170,10 +175,17 @@ function OpenBCICyton (options) { */ this.options = opts; + /** + * @type {RawDataToSample} + * @private + */ + this._rawDataPacketToSample = k.rawDataToSampleObjectDefault(8); + this._rawDataPacketToSample.scale = !this.options.sendCounts; + this._rawDataPacketToSample.protocol = k.OBCIProtocolSerial; + this._rawDataPacketToSample.verbose = this.options.verbose; + /** Properties (keep alphabetical) */ // Arrays - this.accelArray = [0, 0, 0]; // X, Y, Z - this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); this.writeOutArray = []; // Booleans this._streaming = false; @@ -184,9 +196,9 @@ function OpenBCICyton (options) { this.impedanceTest = obciUtils.impedanceTestObjDefault(); this.info = { boardType: this.options.boardType, - sampleRate: k.OBCISampleRate125, + sampleRate: k.OBCISampleRate250, firmware: k.OBCIFirmwareV1, - numberOfChannels: k.OBCINumberOfChannelsDefault, + numberOfChannels: k.OBCINumberOfChannelsCyton, missedPackets: 0 }; if (this.options.boardType === k.OBCIBoardDefault) { @@ -1841,58 +1853,22 @@ OpenBCICyton.prototype._processBytes = function (data) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { - if (!dataBuffer) return null; - var bytesToParse = dataBuffer.length; - // Exit if we have a buffer with less data than a packet - if (bytesToParse < k.OBCIPacketSize) return dataBuffer; - - var parsePosition = 0; - // Begin parseing - while (parsePosition <= bytesToParse - k.OBCIPacketSize) { - // Is the current byte a head byte that looks like 0xA0 - if (dataBuffer[parsePosition] === k.OBCIByteStart) { - // Now that we know the first is a head byte, let's see if the last one is a - // tail byte 0xCx where x is the set of numbers from 0-F (hex) - if (obciUtils.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { - /** We just qualified a raw packet */ - // This could be a time set packet! - this.timeOfPacketArrival = this.time(); - // Grab the raw packet, make a copy of it. - var rawPacket; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } else { - rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } + if (_.isNull(dataBuffer) || _.isUndefined(dataBuffer)) return; + const output = obciUtils.extractRawDataPackets(dataBuffer); - // Emit that buffer - this.emit('rawDataPacket', rawPacket); - // Submit the packet for processing - this._processQualifiedPacket(rawPacket); - // Overwrite the dataBuffer with a new buffer - var tempBuf; - if (parsePosition > 0) { - tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); - } else { - tempBuf = dataBuffer.slice(k.OBCIPacketSize); - } - if (tempBuf.length === 0) { - dataBuffer = null; - } else { - if (k.getVersionNumber(process.version) >= 6) { - dataBuffer = Buffer.from(tempBuf); - } else { - dataBuffer = new Buffer(tempBuf); - } - } - // Move the parse position up one packet - parsePosition = -1; - bytesToParse -= k.OBCIPacketSize; - } - } - parsePosition++; - } + dataBuffer = output.buffer; + + this.timeOfPacketArrival = this.time(); + + _.forEach(output.rawDataPackets, (rawDataPacket) => { + // Emit that buffer + this.emit('rawDataPacket', rawPacket); + // Submit the packet for processing + this._processQualifiedPacket(rawPacket); + this._rawDataPacketToSample.rawDataPacket = rawDataPacket; + const sample = obciUtils.transformRawDataPacketToSample(this._rawDataPacketToSample); + this._finalizeNewSample(sample); + }); return dataBuffer; }; @@ -1936,29 +1912,9 @@ OpenBCICyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; var packetType = obciUtils.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); switch (packetType) { - case k.OBCIStreamPacketStandardAccel: - this._processPacketStandardAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketStandardRawAux: - this._processPacketStandardRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketUserDefinedType: - // Do nothing for User Defined Packets - break; case k.OBCIStreamPacketAccelTimeSyncSet: - // Don't waste any time! - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketAccelTimeSynced: - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; case k.OBCIStreamPacketRawAuxTimeSyncSet: this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSynced: - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); break; default: // Don't do anything if the packet is not defined @@ -1990,41 +1946,9 @@ OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { } }; -/** - * @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ -OpenBCICyton.prototype._processPacketStandardAccel = function (rawPacket) { - try { - let sample = obciUtils.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; -/** - * @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ -OpenBCICyton.prototype._processPacketStandardRawAux = function (rawPacket) { - try { - let sample = obciUtils.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; + + /** * @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp @@ -2160,46 +2084,6 @@ OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPa return syncObj; }; -/** - * @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets - * an accelerometer value. - * @param rawPacket - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ -OpenBCICyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = obciUtils.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - -/** - * @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that - * shall be emitted as a raw buffer and not scaled. - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @private - * @author AJ Keller (@pushtheworldllc) - */ -OpenBCICyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = obciUtils.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - /** * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are * being tested. @@ -2209,7 +2093,10 @@ OpenBCICyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { */ OpenBCICyton.prototype._finalizeNewSample = function (sampleObject) { sampleObject._count = this.sampleCount++; - if (this.impedanceTest.active) { + if (!sampleObject.valid) { + this.badPackets++; + this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); + } else if (this.impedanceTest.active) { this._processImpedanceTest(sampleObject); } else { // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper @@ -2420,52 +2307,10 @@ OpenBCICyton.prototype.channelIsOnFromChannelSettingsObject = function (channelS return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; }; -util.inherits(Cyton, EventEmitter); +util.inherits(OpenBCICyton, EventEmitter); module.exports = OpenBCICyton; -/** -* @description To parse a given channel given output from a print registers query -* @param rawChannelBuffer -* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -// function getChannelSettingsObj (rawChannelBuffer) { -// return new Promise(function (resolve, reject) { -// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { -// reject('Undefined or null channel buffer'); -// } -// -// var channelSettingsObject = { -// CHANNEL: '0', -// POWER_DOWN: '0', -// GAIN_SET: '0', -// INPUT_TYPE_SET: '0', -// BIAS_SET: '0', -// SRB2_SET: '0', -// SRB1_SET: '0' -// }; -// -// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits -// var sizeOfData = rawChannelBuffer.byteLength; -// -// var objIndex = 0; -// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data -// switch (objIndex) { -// case 0: -// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); -// break; -// default: -// break; -// } -// -// objIndex++; -// } -// resolve(channelSettingsObject); -// }); -// } - function masterBufferMaker () { var masterBuf = new Buffer(k.OBCIMasterBufferSize); masterBuf.fill(0); diff --git a/package.json b/package.json index 8cc0bed..eeba646 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ "buffer-equal": "^1.0.0", "clone": "^2.0.0", "gaussian": "^1.0.0", - "lodash": "^4.16.6", + "lodash": "^4.17.4", "mathjs": "^3.3.0", - "openbci-utilities": "0.0.8", + "openbci-utilities": "0.0.9", "performance-now": "^2.1.0", "serialport": "4.0.7", "sntp": "^2.0.0", From 99ecd5dd8ad0056e661fdab0fbdf8fbd271882fc Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Sun, 16 Jul 2017 22:49:30 -0400 Subject: [PATCH 13/42] Ready to start testing --- changelog.md | 4 +- index.js | 1 - openBCICyton-old.js | 4 +- openBCICyton.js | 35 +- openBCISimulator.js | 2 +- package.json | 1 + test/openBCICyton-Impedance-test.js | 2 +- ...dio-test.js => openBCICyton-radio-test.js} | 0 test/openBCICyton-test.js | 483 +++++------------- test/openBCISimulator-test.js | 2 +- 10 files changed, 146 insertions(+), 388 deletions(-) rename test/{openBCIBoard-radio-test.js => openBCICyton-radio-test.js} (100%) diff --git a/changelog.md b/changelog.md index 59508d9..47d4716 100644 --- a/changelog.md +++ b/changelog.md @@ -91,7 +91,7 @@ ### Bug Fixes * Fixes bug where extra data after EOT (`$$$`) was dumped by preserving the poriton after the EOT for further decomposition. -* Fixes bug where any calls to channel set would actually break the openBCISample code as the channelSettingsArray contained an undefined. +* Fixes bug where any calls to channel set would actually break the openBCIUtilities code as the channelSettingsArray contained an undefined. * Writes promises resolve when they are actually sent over the serial port. # 1.4.0 @@ -237,7 +237,7 @@ The second major release for the OpenBCI Node.js SDK brings major changes, impro * NPM package is not called `openbci-sdk` anymore, now called `openbci` * Accelerometer data now goes into `.accelData` array instead of `.auxData` array. -* In openBCISample.js +* In openBCIUtilities.js * `parseRawPacket()` is now called `parseRawPacketStandard()` * `ready` event only triggered after soft reset. `eot` event emitted in all other conditions resulting in the board sending EOT ("$$$") * Must use camel case on the OpenBCISimulator object. diff --git a/index.js b/index.js index c6cbbaa..9202f29 100644 --- a/index.js +++ b/index.js @@ -1,2 +1 @@ module.exports.Cyton = require('./openBCICyton'); -module.exports.Constants = require('./openBCIConstants'); diff --git a/openBCICyton-old.js b/openBCICyton-old.js index f80fa9a..6e80c67 100644 --- a/openBCICyton-old.js +++ b/openBCICyton-old.js @@ -4,7 +4,7 @@ const EventEmitter = require('events').EventEmitter; const math = require('mathjs'); const util = require('util'); const SerialPort = require('serialport'); -const openBCISample = require('./openBCISample'); +const openBCISample = require('./openBCIUtilities'); const k = openBCISample.k; const openBCISimulator = require('./openBCISimulator'); const Sntp = require('sntp'); @@ -1838,7 +1838,7 @@ Cyton.prototype._processImpedanceTest = function (sampleObject) { var impedanceArray; if (this.impedanceTest.continuousMode) { // console.log('running in continuous mode...') - // openBCISample.debugPrettyPrint(sampleObject) + // openBCIUtilities.debugPrettyPrint(sampleObject) impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); if (impedanceArray) { this.emit('impedanceArray', impedanceArray); diff --git a/openBCICyton.js b/openBCICyton.js index f23e122..3f76fec 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -130,11 +130,14 @@ var _options = { /** * @description The initialization method to call first, before any other method. - * @param options {InitializationObject} (optional) - Board optional configurations. + * @param options {* | InitializationObject} (optional) - Board optional configurations. * @constructor * @author AJ Keller (@pushtheworldllc) */ function OpenBCICyton (options) { + if (!(this instanceof OpenBCICyton)) { + return new OpenBCICyton(options); + } options = (typeof options !== 'function') && options || {}; var opts = {}; @@ -191,7 +194,6 @@ function OpenBCICyton (options) { this._streaming = false; // Buffers this.buffer = null; - this.masterBuffer = masterBufferMaker(); // Objects this.impedanceTest = obciUtils.impedanceTestObjDefault(); this.info = { @@ -2257,21 +2259,6 @@ OpenBCICyton.prototype.printBytesIn = function () { } }; -/** - * @description This prints the total number of packets that have been read - * @author AJ Keller (@pushtheworldllc) - */ -/* istanbul ignore next */ -OpenBCICyton.prototype.printPacketsRead = function () { - if (this.masterBuffer.packetsRead > 1) { - console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); - } else if (this.masterBuffer.packetsIn === 1) { - console.log('Read 1 packet.'); - } else { - console.log('No packets read.'); - } -}; - /** * @description Nice convenience method to print some session details * @author AJ Keller (@pushtheworldllc) @@ -2279,7 +2266,6 @@ OpenBCICyton.prototype.printPacketsRead = function () { /* istanbul ignore next */ OpenBCICyton.prototype.debugSession = function () { this.printBytesIn(); - this.printPacketsRead(); this.printPacketsBad(); }; @@ -2310,16 +2296,3 @@ OpenBCICyton.prototype.channelIsOnFromChannelSettingsObject = function (channelS util.inherits(OpenBCICyton, EventEmitter); module.exports = OpenBCICyton; - -function masterBufferMaker () { - var masterBuf = new Buffer(k.OBCIMasterBufferSize); - masterBuf.fill(0); - return { // Buffer used to store bytes in and read packets from - buffer: masterBuf, - positionRead: 0, - positionWrite: 0, - packetsIn: 0, - packetsRead: 0, - looseBytes: 0 - }; -} diff --git a/openBCISimulator.js b/openBCISimulator.js index 91189d2..e7f6be2 100644 --- a/openBCISimulator.js +++ b/openBCISimulator.js @@ -3,7 +3,7 @@ var EventEmitter = require('events').EventEmitter; var util = require('util'); var stream = require('stream'); -var openBCISample = require('./openBCISample'); +var openBCISample = require('./openBCIUtilities'); var k = openBCISample.k; var now = require('performance-now'); diff --git a/package.json b/package.json index eeba646..deeb931 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "chai": "^3.4.1", "chai-as-promised": "^6.0.0", "codecov": "^2.1.0", + "dirty-chai": "^2.0.1", "istanbul": "^0.4.4", "mocha": "^3.0.2", "sandboxed-module": "^2.0.3", diff --git a/test/openBCICyton-Impedance-test.js b/test/openBCICyton-Impedance-test.js index 5155122..fa148a9 100644 --- a/test/openBCICyton-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -3,7 +3,7 @@ const bluebirdChecks = require('./bluebirdChecks'); const chai = require('chai'); const should = chai.should(); // eslint-disable-line no-unused-vars const Cyton = require('../openBCICyton'); -const openBCISample = require('../openBCISample'); +const openBCISample = require('../openBCIUtilities'); const k = openBCISample.k; const chaiAsPromised = require('chai-as-promised'); diff --git a/test/openBCIBoard-radio-test.js b/test/openBCICyton-radio-test.js similarity index 100% rename from test/openBCIBoard-radio-test.js rename to test/openBCICyton-radio-test.js diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 773d855..3377227 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -5,16 +5,20 @@ const chai = require('chai'); const expect = chai.expect; const should = chai.should(); // eslint-disable-line no-unused-vars const Cyton = require('../openBCICyton'); -const openBCISample = require('../openBCISample'); -const k = require('../openBCIConstants'); +const OpenBCIUtilities = require('openbci-utilities'); +const openBCIUtilities = OpenBCIUtilities.Utilities; +const k = OpenBCIUtilities.Constants; const chaiAsPromised = require('chai-as-promised'); const sinonChai = require('sinon-chai'); const bufferEqual = require('buffer-equal'); const fs = require('fs'); const math = require('mathjs'); +const dirtyChai = require('dirty-chai'); + chai.use(chaiAsPromised); chai.use(sinonChai); +chai.use(dirtyChai); describe('openbci-sdk', function () { this.timeout(2000); @@ -69,25 +73,26 @@ describe('openbci-sdk', function () { var board = new Cyton(); expect(board.options.boardType).to.equal(k.OBCIBoardDefault); expect(board.options.baudRate).to.equal(115200); - expect(board.options.hardSet).to.be.false; - expect(board.options.simulate).to.be.false; - expect(board.options.simulatorBoardFailure).to.be.false; - expect(board.options.simulatorDaisyModuleAttached).to.be.false; - expect(board.options.simulatorDaisyModuleCanBeAttached).to.be.true; + expect(board.options.hardSet).to.be.false(); + expect(board.options.sendCounts).to.be.false(); + expect(board.options.simulate).to.be.false(); + expect(board.options.simulatorBoardFailure).to.be.false(); + expect(board.options.simulatorDaisyModuleAttached).to.be.false(); + expect(board.options.simulatorDaisyModuleCanBeAttached).to.be.true(); expect(board.options.simulatorFirmwareVersion).to.equal(k.OBCIFirmwareV1); - expect(board.options.simulatorHasAccelerometer).to.be.true; + expect(board.options.simulatorHasAccelerometer).to.be.true(); expect(board.options.simulatorInternalClockDrift).to.equal(0); - expect(board.options.simulatorInjectAlpha).to.be.true; + expect(board.options.simulatorInjectAlpha).to.be.true(); expect(board.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz60); expect(board.options.simulatorSampleRate).to.equal(k.OBCISampleRate250); - expect(board.options.simulatorSerialPortFailure).to.be.false; - expect(board.options.sntpTimeSync).to.be.false; + expect(board.options.simulatorSerialPortFailure).to.be.false(); + expect(board.options.sntpTimeSync).to.be.false(); expect(board.options.sntpTimeSyncHost).to.equal('pool.ntp.org'); - expect(board.options.verbose).to.be.false; + expect(board.options.verbose).to.be.false(); expect(board.sampleRate()).to.equal(250); expect(board.numberOfChannels()).to.equal(8); - expect(board.isConnected()).to.be.false; - expect(board.isStreaming()).to.be.false; + expect(board.isConnected()).to.be.false(); + expect(board.isStreaming()).to.be.false(); it('should still get proper values if no info object', function () { board.info = null; expect(board.sampleRate()).to.equal(250); @@ -131,28 +136,28 @@ describe('openbci-sdk', function () { var board = new Cyton({ simulate: true }); - expect(board.options.simulate).to.be.true; + expect(board.options.simulate).to.be.true(); }); it('should be able to set the simulator to board failure mode', () => { var ourBoard1 = new Cyton({ simulatorBoardFailure: true }); - expect(ourBoard1.options.simulatorBoardFailure).to.be.true; + expect(ourBoard1.options.simulatorBoardFailure).to.be.true(); var ourBoard2 = new Cyton({ simulatorboardfailure: true }); - expect(ourBoard2.options.simulatorBoardFailure).to.be.true; + expect(ourBoard2.options.simulatorBoardFailure).to.be.true(); }); it('should be able to attach the daisy board in the simulator', () => { var ourBoard1 = new Cyton({ simulatorDaisyModuleAttached: true }); - expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true; + expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true(); // Verify multi case support var ourBoard2 = new Cyton({ simulatordaisymoduleattached: true }); - expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true; + expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true(); }); it('should be able to start the simulator with firmware version 2', () => { var ourBoard1 = new Cyton({ @@ -169,12 +174,12 @@ describe('openbci-sdk', function () { var ourBoard1 = new Cyton({ simulatorHasAccelerometer: false }); - expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false; + expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false(); // Verify multi case support var ourBoard2 = new Cyton({ simulatorhasaccelerometer: false }); - expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false; + expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false(); }); it('should be able to make the internal clock of the simulator run slow', () => { var ourBoard1 = new Cyton({ @@ -202,12 +207,12 @@ describe('openbci-sdk', function () { var ourBoard1 = new Cyton({ simulatorInjectAlpha: false }); - expect(ourBoard1.options.simulatorInjectAlpha).to.be.false; + expect(ourBoard1.options.simulatorInjectAlpha).to.be.false(); // Verify multi case support var ourBoard2 = new Cyton({ simulatorinjectalpha: false }); - expect(ourBoard2.options.simulatorInjectAlpha).to.be.false; + expect(ourBoard2.options.simulatorInjectAlpha).to.be.false(); }); it('can turn 50Hz line noise on', function () { var ourBoard1 = new Cyton({ @@ -245,18 +250,18 @@ describe('openbci-sdk', function () { var ourBoard1 = new Cyton({ simulatorSerialPortFailure: true }); - expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true; + expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true(); // Verify multi case support var ourBoard2 = new Cyton({ simulatorserialportfailure: true }); - expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true; + expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true(); }); it('should be able to enter sync mode', function () { var ourBoard = new Cyton({ sntpTimeSync: true }); - expect(ourBoard.options.sntpTimeSync).to.be.true; + expect(ourBoard.options.sntpTimeSync).to.be.true(); return new Promise((resolve, reject) => { ourBoard.once('sntpTimeLock', resolve); @@ -326,8 +331,8 @@ describe('openbci-sdk', function () { }); it('configures sync object correctly', function () { ourBoard = new Cyton(); - expect(ourBoard.sync.curSyncObj).to.be.null; - expect(ourBoard.sync.eventEmitter).to.be.null; + expect(ourBoard.sync.curSyncObj).to.be.null(); + expect(ourBoard.sync.eventEmitter).to.be.null(); expect(ourBoard.sync.objArray.length).to.equal(0); (ourBoard.sync.sntpActive).should.equal(false); (ourBoard.sync.timeOffsetMaster).should.equal(0); @@ -375,7 +380,7 @@ describe('openbci-sdk', function () { var isConnectedStub = sinon.stub(ourBoard, 'isConnected').returns(true); ourBoard.options.simulate.should.equal(false); ourBoard.simulatorEnable().then(() => { - disconnectStub.should.have.been.calledOnce; + disconnectStub.should.have.been.calledOnce(); disconnectStub.restore(); isConnectedStub.restore(); ourBoard.options.simulate.should.equal(true); @@ -413,7 +418,7 @@ describe('openbci-sdk', function () { var disconnectSpy = sinon.spy(ourBoard, 'disconnect'); ourBoard.options.simulate.should.equal(true); ourBoard.simulatorDisable().then(() => { - disconnectSpy.should.have.been.calledOnce; + disconnectSpy.should.have.been.calledOnce(); disconnectSpy.restore(); ourBoard.options.simulate.should.equal(false); done(); @@ -444,11 +449,11 @@ describe('openbci-sdk', function () { ourBoard.once('ready', () => { var simOptions = ourBoard.serial.options; expect(simOptions).to.be.an('object'); - expect(simOptions.accel).to.be.false; - expect(simOptions.alpha).to.be.false; - expect(simOptions.boardFailure).to.be.true; - expect(simOptions.daisy).to.be.true; - expect(simOptions.daisyCanBeAttached).to.be.false; + expect(simOptions.accel).to.be.false(); + expect(simOptions.alpha).to.be.false(); + expect(simOptions.boardFailure).to.be.true(); + expect(simOptions.daisy).to.be.true(); + expect(simOptions.daisyCanBeAttached).to.be.false(); expect(simOptions.drift).to.be.below(0); expect(simOptions.firmwareVersion).to.be.equal(k.OBCIFirmwareV2); expect(simOptions.fragmentation).to.be.equal(k.OBCISimulatorFragmentationOneByOne); @@ -456,8 +461,8 @@ describe('openbci-sdk', function () { expect(simOptions.bufferSize).to.be.equal(2718); expect(simOptions.lineNoise).to.be.equal(k.OBCISimulatorLineNoiseNone); expect(simOptions.sampleRate).to.be.equal(16); - expect(simOptions.serialPortFailure).to.be.true; - expect(simOptions.verbose).to.be.true; + expect(simOptions.serialPortFailure).to.be.true(); + expect(simOptions.verbose).to.be.true(); ourBoard.disconnect().then(done).catch(done); }); }).catch(err => done(err)); @@ -873,17 +878,17 @@ describe('openbci-sdk', function () { } }); it('is connected after connection', function () { - expect(ourBoard.isConnected()).to.be.true; + expect(ourBoard.isConnected()).to.be.true(); }); it('is no longer connected after clean disconnection', function (done) { ourBoard.disconnect().then(() => { - expect(ourBoard.isConnected()).to.be.false; + expect(ourBoard.isConnected()).to.be.false(); done(); }, done); }); it('is no longer connected if stream closes itself', function (done) { ourBoard.serial.close(() => { - expect(ourBoard.isConnected()).to.be.false; + expect(ourBoard.isConnected()).to.be.false(); done(); }); }); @@ -891,7 +896,7 @@ describe('openbci-sdk', function () { var errorDamper = () => true; ourBoard.on('error', errorDamper); ourBoard.serial.emit('error', new Error('test error')); - expect(ourBoard.isConnected()).to.be.false; + expect(ourBoard.isConnected()).to.be.false(); ourBoard.removeListener('error', errorDamper); }); }); @@ -1394,7 +1399,7 @@ describe('openbci-sdk', function () { // Test the function buffer = ourBoard._processDataBuffer(buffer); - expect(buffer).to.be.null; + expect(buffer).to.be.null(); }); it('should return an unaltered buffer if there is less than a packets worth of data in it', () => { var expectedString = 'AJ'; @@ -1410,10 +1415,10 @@ describe('openbci-sdk', function () { buffer.toString().should.equal(expectedString); // Make sure that the spy was not infact called. - _processQualifiedPacketSpy.should.not.have.been.called; + _processQualifiedPacketSpy.should.not.have.been.called(); }); it('should identify a packet', () => { - var buffer = openBCISample.samplePacketReal(0); + var buffer = openBCIUtilities.samplePacketReal(0); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1422,10 +1427,10 @@ describe('openbci-sdk', function () { buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledOnce; + _processQualifiedPacketSpy.should.have.been.calledOnce(); // The buffer should not have anything in it any more - expect(buffer).to.be.null; + expect(buffer).to.be.null(); }); it('should extract a buffer and preserve the remaining data in the buffer', () => { var expectedString = 'AJ'; @@ -1433,14 +1438,14 @@ describe('openbci-sdk', function () { // declare the big buffer var buffer = new Buffer(k.OBCIPacketSize + extraBuffer.length); // Fill that new big buffer with buffers - openBCISample.samplePacketReal(0).copy(buffer, 0); + openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); // Call the function under test buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.called; + _processQualifiedPacketSpy.should.have.been.called(); // The buffer should have the epxected number of bytes left buffer.length.should.equal(expectedString.length); // Convert the buffer to a string and ensure that it equals the expected string @@ -1453,9 +1458,9 @@ describe('openbci-sdk', function () { // declare the big buffer var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers); // Fill that new big buffer with buffers - openBCISample.samplePacketReal(0).copy(buffer, 0); - openBCISample.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); - openBCISample.samplePacketReal(2).copy(buffer, k.OBCIPacketSize * 2); + openBCIUtilities.samplePacketReal(0).copy(buffer, 0); + openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); + openBCIUtilities.samplePacketReal(2).copy(buffer, k.OBCIPacketSize * 2); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); // Call the function under test @@ -1463,7 +1468,7 @@ describe('openbci-sdk', function () { // Ensure that we extracted only one buffer _processQualifiedPacketSpy.should.have.been.calledThrice; // The buffer should not have anything in it any more - expect(buffer).to.be.null; + expect(buffer).to.be.null(); }); it('should be able to get multiple packets and keep extra data on the end', () => { @@ -1474,8 +1479,8 @@ describe('openbci-sdk', function () { // declare the big buffer var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); // Fill that new big buffer with buffers - openBCISample.samplePacketReal(0).copy(buffer, 0); - openBCISample.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); + openBCIUtilities.samplePacketReal(0).copy(buffer, 0); + openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); extraBuffer.copy(buffer, k.OBCIPacketSize * 2); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1495,9 +1500,9 @@ describe('openbci-sdk', function () { // declare the big buffer var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); // Fill that new big buffer with buffers - openBCISample.samplePacketReal(0).copy(buffer, 0); + openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); - openBCISample.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); + openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1518,9 +1523,9 @@ describe('openbci-sdk', function () { // declare the big buffer var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length * 2); // Fill that new big buffer with buffers - openBCISample.samplePacketReal(0).copy(buffer, 0); + openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); - openBCISample.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); + openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); extraBuffer.copy(buffer, k.OBCIPacketSize * 2 + extraBuffer.byteLength); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1539,110 +1544,47 @@ describe('openbci-sdk', function () { */ describe('#_processQualifiedPacket', function () { var ourBoard; - var funcSpyTimeSyncSet, funcSpyTimeSyncedAccel, funcSpyTimeSyncedRawAux, funcSpyStandardRawAux, funcSpyStandardAccel; + var funcSpyTimeSyncSet; before(function () { ourBoard = new Cyton({ verbose: true }); // Put watchers on all functions - funcSpyStandardAccel = sinon.spy(ourBoard, '_processPacketStandardAccel'); - funcSpyStandardRawAux = sinon.spy(ourBoard, '_processPacketStandardRawAux'); funcSpyTimeSyncSet = sinon.spy(ourBoard, '_processPacketTimeSyncSet'); - funcSpyTimeSyncedAccel = sinon.spy(ourBoard, '_processPacketTimeSyncedAccel'); - funcSpyTimeSyncedRawAux = sinon.spy(ourBoard, '_processPacketTimeSyncedRawAux'); }); beforeEach(function () { - funcSpyStandardAccel.reset(); - funcSpyStandardRawAux.reset(); funcSpyTimeSyncSet.reset(); - funcSpyTimeSyncedAccel.reset(); - funcSpyTimeSyncedRawAux.reset(); - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); }); after(function () { // ourBoard = null }); after(() => bluebirdChecks.noPendingPromises()); - it('should process a standard packet', function () { - var buffer = openBCISample.samplePacket(0); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // Ensure that we extracted only one buffer - funcSpyStandardAccel.should.have.been.calledOnce; - }); - it('should process a standard packet with raw aux', function () { - var buffer = openBCISample.samplePacketStandardRawAux(0); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // Ensure that we extracted only one buffer - funcSpyStandardRawAux.should.have.been.calledOnce; - }); - it('should call nothing for a user defined packet type ', function () { - var buffer = openBCISample.samplePacketUserDefined(); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // Nothing should be called - funcSpyStandardAccel.should.not.have.been.called; - funcSpyStandardRawAux.should.not.have.been.called; - funcSpyTimeSyncSet.should.not.have.been.called; - funcSpyTimeSyncedAccel.should.not.have.been.called; - funcSpyTimeSyncedRawAux.should.not.have.been.called; - }); it('should process a time sync set packet with accel', function () { - var buffer = openBCISample.samplePacketAccelTimeSyncSet(); + var buffer = openBCIUtilities.samplePacketAccelTimeSyncSet(); // Call the function under test ourBoard._processQualifiedPacket(buffer); // We should call to sync up - funcSpyTimeSyncSet.should.have.been.calledOnce; + funcSpyTimeSyncSet.should.have.been.calledOnce(); funcSpyTimeSyncSet.should.have.been.calledWith(buffer); - // we should call to get a packet - funcSpyTimeSyncedAccel.should.have.been.calledOnce; - funcSpyTimeSyncedAccel.should.have.been.calledWith(buffer); - }); - it('should process a time synced packet with accel', function () { - var buffer = openBCISample.samplePacketAccelTimeSynced(0); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // Ensure that we extracted only one buffer - funcSpyTimeSyncedAccel.should.have.been.calledOnce; }); it('should process a time sync set packet with raw aux', function () { - var buffer = openBCISample.samplePacketRawAuxTimeSyncSet(0); + var buffer = openBCIUtilities.samplePacketRawAuxTimeSyncSet(0); // Call the function under test ourBoard._processQualifiedPacket(buffer); // We should call to sync up - funcSpyTimeSyncSet.should.have.been.calledOnce; + funcSpyTimeSyncSet.should.have.been.calledOnce(); funcSpyTimeSyncSet.should.have.been.calledWith(buffer); - // we should call to get a packet - funcSpyTimeSyncedRawAux.should.have.been.calledOnce; - funcSpyTimeSyncedRawAux.should.have.been.calledWith(buffer); - }); - it('should process a time synced packet with raw aux', function () { - var buffer = openBCISample.samplePacketRawAuxTimeSynced(0); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // Ensure that we extracted only one buffer - funcSpyTimeSyncedRawAux.should.have.been.calledOnce; }); it('should not identify any packet', function () { - var buffer = openBCISample.samplePacket(0); + var buffer = openBCIUtilities.samplePacket(0); // Set the stop byte to some number not yet defined buffer[k.OBCIPacketPositionStopByte] = 0xCF; @@ -1651,20 +1593,16 @@ describe('openbci-sdk', function () { ourBoard._processDataBuffer(buffer); // Nothing should be called - funcSpyStandardAccel.should.not.have.been.called; - funcSpyStandardRawAux.should.not.have.been.called; - funcSpyTimeSyncSet.should.not.have.been.called; - funcSpyTimeSyncedAccel.should.not.have.been.called; - funcSpyTimeSyncedRawAux.should.not.have.been.called; + funcSpyTimeSyncSet.should.not.have.been.called(); }); it('should emit a dropped packet on dropped packet', function (done) { // Set to default state ourBoard.previousSampleNumber = -1; - var sampleNumber0 = openBCISample.samplePacket(0); + var sampleNumber0 = openBCIUtilities.samplePacket(0); ourBoard.once('droppedPacket', () => { done(); }); - var sampleNumber2 = openBCISample.samplePacket(2); + var sampleNumber2 = openBCIUtilities.samplePacket(2); // Call the function under test ourBoard._processDataBuffer(sampleNumber0); ourBoard._processDataBuffer(sampleNumber2); @@ -1673,13 +1611,13 @@ describe('openbci-sdk', function () { // Set to default state var count = 0; ourBoard.previousSampleNumber = 253; - var buf1 = openBCISample.samplePacket(254); + var buf1 = openBCIUtilities.samplePacket(254); var countFunc = arr => { count++; }; ourBoard.on('droppedPacket', countFunc); - var buf2 = openBCISample.samplePacket(0); - var buf3 = openBCISample.samplePacket(1); + var buf2 = openBCIUtilities.samplePacket(0); + var buf3 = openBCIUtilities.samplePacket(1); // Call the function under test ourBoard._processDataBuffer(buf1); ourBoard._processDataBuffer(buf2); @@ -1701,7 +1639,7 @@ describe('openbci-sdk', function () { }); }); beforeEach(() => { - timeSyncSetPacket = openBCISample.samplePacketRawAuxTimeSyncSet(); + timeSyncSetPacket = openBCIUtilities.samplePacketRawAuxTimeSyncSet(); ourBoard.sync.timeOffsetArray = []; }); afterEach(() => { @@ -1734,7 +1672,7 @@ describe('openbci-sdk', function () { }); ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); let syncObject = ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); expect(syncObject).to.have.property('valid', false); @@ -1751,7 +1689,7 @@ describe('openbci-sdk', function () { } else { badPacket = new Buffer(timeSyncSetPacket.slice(0, 30)); } - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; ourBoard.once('synced', (syncObj) => { expect(syncObj).to.have.property('valid', false); @@ -1769,7 +1707,7 @@ describe('openbci-sdk', function () { var expectedRoundTripTime = 20; // ms ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // Set the sent time ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; @@ -1786,7 +1724,7 @@ describe('openbci-sdk', function () { // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // Set the sent time ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; @@ -1804,7 +1742,7 @@ describe('openbci-sdk', function () { // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // Set the sent time ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; @@ -1826,7 +1764,7 @@ describe('openbci-sdk', function () { // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // Set the sent time ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; @@ -1853,7 +1791,7 @@ describe('openbci-sdk', function () { // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // Set the sent time ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; @@ -1932,7 +1870,7 @@ describe('openbci-sdk', function () { board.on('sntpTimeLock', function () { var funcSpySntpNow = sinon.spy(board, '_sntpNow'); board.time(); - funcSpySntpNow.should.have.been.calledOnce; + funcSpySntpNow.should.have.been.calledOnce(); funcSpySntpNow.restore(); board.sntpStop(); done(); @@ -1946,7 +1884,7 @@ describe('openbci-sdk', function () { board.time(); - funcSpySntpNow.should.not.have.been.called; + funcSpySntpNow.should.not.have.been.called(); funcSpySntpNow.reset(); @@ -1968,11 +1906,11 @@ describe('openbci-sdk', function () { after(() => bluebirdChecks.noPendingPromises()); it('should be able to start ntp server', () => { var board = new Cyton(); - expect(board.sntp.isLive()).to.be.false; + expect(board.sntp.isLive()).to.be.false(); return Promise.all([ board.sntpStart() .then(() => { - expect(board.sntp.isLive()).to.be.true; + expect(board.sntp.isLive()).to.be.true(); }), new Promise(resolve => { board.once('sntpTimeLock', resolve); @@ -1999,17 +1937,17 @@ describe('openbci-sdk', function () { after(() => bluebirdChecks.noPendingPromises()); it('should be able to stop the ntp server and set the globals correctly', function () { // Verify the before condition is correct - expect(board.options.sntpTimeSync).to.be.true; - expect(board.sync.sntpActive).to.be.true; - expect(board.sntp.isLive()).to.be.true; + expect(board.options.sntpTimeSync).to.be.true(); + expect(board.sync.sntpActive).to.be.true(); + expect(board.sntp.isLive()).to.be.true(); // Call the function under test board.sntpStop(); // Ensure the globals were set off - expect(board.options.sntpTimeSync).to.be.false; - expect(board.sync.sntpActive).to.be.false; - expect(board.sntp.isLive()).to.be.false; + expect(board.options.sntpTimeSync).to.be.false(); + expect(board.sync.sntpActive).to.be.false(); + expect(board.sntp.isLive()).to.be.false(); }); }); describe('#sntpGetOffset', function () { @@ -2112,7 +2050,7 @@ $$$`); }); }); beforeEach(() => { - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); }); afterEach(() => { ourBoard.buffer = null; @@ -2141,32 +2079,32 @@ $$$`); // Send the first buffer ourBoard._processBytes(buf1); // Verify the parse function was not called - _processParseBufferForResetSpy.should.not.have.been.called; + _processParseBufferForResetSpy.should.not.have.been.called(); // Verify the global buffer has the first buf in it bufferEqual(ourBoard.buffer, buf1); // Send another buffer without EOT ourBoard._processBytes(buf2); // Verify the parse function was not called - _processParseBufferForResetSpy.should.not.have.been.called; + _processParseBufferForResetSpy.should.not.have.been.called(); // Verify the global buffer has the first and second buf in it bufferEqual(ourBoard.buffer, Buffer.concat([buf1, buf2])); // Send another buffer without EOT ourBoard._processBytes(buf3); // Verify the parse function was called - _processParseBufferForResetSpy.should.have.been.calledOnce; + _processParseBufferForResetSpy.should.have.been.calledOnce(); // Verify the global buffer is empty - expect(ourBoard.buffer).to.be.null; + expect(ourBoard.buffer).to.be.null(); }); }); describe('#OBCIParsingTimeSyncSent', function () { // var spy before(() => { - // spy = sinon.spy(ourBoard.openBCISample,"isTimeSyncSetConfirmationInBuffer") + // spy = sinon.spy(ourBoard.openBCIUtilities,"isTimeSyncSetConfirmationInBuffer") }); beforeEach(() => { ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; - ourBoard.sync.curSyncObj = openBCISample.newSyncObject(); + ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); // spy.reset() }); it('should call to find the time sync set character in the buffer', function () { @@ -2183,11 +2121,11 @@ $$$`); emitted = true; }); // Make a new buffer - var buf1 = openBCISample.samplePacketReal(1); + var buf1 = openBCIUtilities.samplePacketReal(1); // Send the buffer in ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null; - expect(emitted).to.be.true; + expect(ourBoard.buffer).to.be.null(); + expect(emitted).to.be.true(); }); it('should clear the buffer after a time sync set packet', function () { let emitted = false; @@ -2204,14 +2142,14 @@ $$$`); emitted = true; }); // Make a new buffer - var buf1 = openBCISample.samplePacketAccelTimeSyncSet(1); + var buf1 = openBCIUtilities.samplePacketAccelTimeSyncSet(1); // Send the buffer in ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null; - expect(emitted).to.be.true; + expect(ourBoard.buffer).to.be.null(); + expect(emitted).to.be.true(); }); it('should call to find the time sync set character in the buffer after packet', function () { - var buf1 = openBCISample.samplePacket(); + var buf1 = openBCIUtilities.samplePacket(); var buf2 = new Buffer(','); // Call the processBytes function @@ -2223,9 +2161,9 @@ $$$`); // ourBoard.buffer.length.should.equal(0) }); it('should find time sync and emit two samples', function (done) { - var buf1 = openBCISample.samplePacket(250); + var buf1 = openBCIUtilities.samplePacket(250); var buf2 = new Buffer([0x2C]); - var buf3 = openBCISample.samplePacket(251); + var buf3 = openBCIUtilities.samplePacket(251); var inputBuf = Buffer.concat([buf1, buf2, buf3], buf1.byteLength + 1 + buf3.byteLength); @@ -2252,9 +2190,9 @@ $$$`); ourBoard._processBytes(inputBuf); }); it('should not find the packet if in packet', () => { - var buf1 = openBCISample.samplePacket(250); + var buf1 = openBCIUtilities.samplePacket(250); buf1[4] = 0x2C; // Inject a false packet - var buf2 = openBCISample.samplePacket(251); + var buf2 = openBCIUtilities.samplePacket(251); // Call the processBytes function ourBoard._processBytes(Buffer.concat([buf1, buf2], buf1.length + buf2.length)); @@ -2269,7 +2207,7 @@ $$$`); }); it('should emit a sample when inserted', function (done) { var expectedSampleNumber = 0; - var buf1 = openBCISample.samplePacketReal(expectedSampleNumber); + var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); // Declare the event emitter prior to calling function ourBoard.once('sample', sample => { @@ -2278,14 +2216,14 @@ $$$`); // Now call the function which should call the "sample" event ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null; + expect(ourBoard.buffer).to.be.null(); done(); }); it('should get three packets even if one was sent in the last data emit', function () { var expectedSampleNumber = 0; - var buf1 = openBCISample.samplePacketReal(expectedSampleNumber); - var buf2 = openBCISample.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCISample.samplePacketReal(expectedSampleNumber + 2); + var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // Pretend that half of buf1 got sent in the first serial flush // and that the last half of it will arrive a lil later var splitPoint = 15; @@ -2312,13 +2250,13 @@ $$$`); ourBoard.on('sample', newSample); // Now call the function which should call the "sample" event ourBoard._processBytes(dataBuf); - expect(ourBoard.buffer).to.be.null; + expect(ourBoard.buffer).to.be.null(); }); it('should keep extra data in the buffer', function () { var expectedSampleNumber = 0; - var buf1 = openBCISample.samplePacketReal(expectedSampleNumber); - var buf2 = openBCISample.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCISample.samplePacketReal(expectedSampleNumber + 2); + var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // Pretend that half of buf1 got sent in the first serial flush // and that the last half of it will arrive a lil later var splitPoint = 15; @@ -2351,7 +2289,7 @@ $$$`); ourBoard._processBytes(Buffer.concat([buf1, buf2, bufFirstHalf])); // Now verify there is data still in the global buffer by calling _processBytes on the last half ourBoard._processBytes(bufLastHalf); - expect(ourBoard.buffer).to.be.null; + expect(ourBoard.buffer).to.be.null(); }); it('should throw out old data if it is incomplete and add to badPackets count', function () { // Some how this packet go messed up and lodged in... This is the worst case, that the buffer has @@ -2360,9 +2298,9 @@ $$$`); // New buffer incoming var expectedSampleNumber = 1; - var buf1 = openBCISample.samplePacketReal(expectedSampleNumber); - var buf2 = openBCISample.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCISample.samplePacketReal(expectedSampleNumber + 2); + var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // New data incoming! var dataBuf = Buffer.concat([buf1, buf2, buf3]); @@ -2383,7 +2321,7 @@ $$$`); // Now call the function which should call the "sample" event ourBoard._processBytes(dataBuf); // Verify that the old data was rejected - expect(ourBoard.buffer).to.be.null; + expect(ourBoard.buffer).to.be.null(); }); }); @@ -2395,7 +2333,7 @@ $$$`); var buf = new Buffer('Tacos are amazing af$$$'); var eotEvent = data => { - expect(bufferEqual(data, buf)).to.be.true; + expect(bufferEqual(data, buf)).to.be.true(); ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); done(); }; @@ -2428,7 +2366,7 @@ $$$`); ourBoard = new Cyton({ verbose: true }); - randomSampleGenerator = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, false, 'none'); + randomSampleGenerator = openBCIUtilities.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, false, 'none'); }); beforeEach(() => { // Clear the global var @@ -2538,12 +2476,12 @@ $$$`); ourBoard = new Cyton(); ourBoard.info.firmware = 'v2'; - expect(ourBoard.usingVersionTwoFirmware()).to.be.true; + expect(ourBoard.usingVersionTwoFirmware()).to.be.true(); }); it('should return false if not firmware version 2', () => { ourBoard = new Cyton(); - expect(ourBoard.usingVersionTwoFirmware()).to.be.false; + expect(ourBoard.usingVersionTwoFirmware()).to.be.false(); }); }); @@ -3265,7 +3203,7 @@ $$$`); .then(() => { ourBoard.once('ready', () => { ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true; + expect(isUp).to.be.true(); done(); }).catch(err => done(err)); }); @@ -3282,7 +3220,7 @@ $$$`); .then(() => { ourBoard.once('ready', () => { ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.false; + expect(isUp).to.be.false(); done(); }).catch(err => done(err)); }); @@ -3290,160 +3228,7 @@ $$$`); }); }); - describe('#radioTests', function () { - this.timeout(0); - before(function (done) { - ourBoard = new Cyton({ - verbose: true, - simulatorFirmwareVersion: 'v2', - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - ourBoard.connect(masterPortName).catch(err => done(err)); - - ourBoard.once('ready', () => { - done(); - }); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - it('should be able to get the channel number', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - // The channel number should be between 0 and 25. Those are hard limits. - ourBoard.radioChannelGet().then(res => { - expect(res.channelNumber).to.be.within(0, 25); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 1', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(1).then(channelNumber => { - expect(channelNumber).to.equal(1); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 2', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(2).then(channelNumber => { - expect(channelNumber).to.equal(2); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 25', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(25).then(channelNumber => { - expect(channelNumber).to.equal(25); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 5', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(5).then(channelNumber => { - expect(channelNumber).to.equal(5); - done(); - }).catch(err => done(err)); - }); - it('should be able to override host channel number, verify system is down, set the host back and verify system is up', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - var systemChanNumber = 0; - var newChanNum = 0; - // var timey = Date.now() - // Get the current system channel - ourBoard.radioChannelGet() - .then(res => { - // Store it - systemChanNumber = res.channelNumber; - // console.log(`system channel number: ${res.channelNumber}`) - if (systemChanNumber === 25) { - newChanNum = 24; - } else { - newChanNum = systemChanNumber + 1; - } - // Call to change just the host - return ourBoard.radioChannelSetHostOverride(newChanNum); - }) - .then(newChanNumActual => { - expect(newChanNumActual).to.equal(newChanNum); - // timey = Date.now() - // console.log(`new chan ${newChanNumActual} got`, timey, '0ms') - return new Promise((resolve, reject) => { - setTimeout(function () { - // console.log(`get status`, Date.now(), `${Date.now() - timey}ms`) - ourBoard.radioSystemStatusGet().then(isUp => { - // console.log('resolving', Date.now(), `${Date.now() - timey}ms`) - resolve(isUp); - }) - .catch(err => { - reject(err); - }); - }, 270); // Should be accurate after 270 seconds - }); - }) - .then(isUp => { - // console.log(`isUp test`, Date.now(), `${Date.now() - timey}ms`) - expect(isUp).to.be.false; - return ourBoard.radioChannelSetHostOverride(systemChanNumber); // Set back to good - }) - .then(newChanNumActual => { - // Verify we set it back to normal - expect(newChanNumActual).to.equal(systemChanNumber); - return ourBoard.radioSystemStatusGet(); - }) - .then(isUp => { - expect(isUp).to.be.true; - done(); - }) - .catch(err => done(err)); - }); - it('should be able to get the poll time', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioPollTimeGet().should.eventually.be.greaterThan(0).and.notify(done); - }); - it('should be able to set the poll time', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioPollTimeSet(80).should.become(80).and.notify(done); - }); - it('should be able to change to default baud rate', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioBaudRateSet('default').should.become(115200).and.notify(done); - }); - it('should be able to change to fast baud rate', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioBaudRateSet('fast').then(newBaudRate => { - expect(newBaudRate).to.equal(230400); - return ourBoard.radioBaudRateSet('default'); - }).then(() => { - done(); - }).catch(err => done(err)); - }); - it('should be able to set the system status', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true; - done(); - }).catch(err => done(err)); - }); - }); - - describe('#hardwareValidation', function () { + xdescribe('#hardwareValidation', function () { this.timeout(20000); // long timeout for pleanty of stream time :) var runHardwareValidation = true; var wstream; @@ -3515,7 +3300,7 @@ $$$`); }, 15000); board.on('sample', sample => { - openBCISample.samplePrintLine(sample) + openBCIUtilities.samplePrintLine(sample) .then(line => { wstream.write(line); }); diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js index fcd5428..db64159 100644 --- a/test/openBCISimulator-test.js +++ b/test/openBCISimulator-test.js @@ -6,7 +6,7 @@ const chaiAsPromised = require(`chai-as-promised`); const expect = chai.expect; const should = chai.should(); // eslint-disable-line no-unused-vars const openBCISimulator = require('../openBCISimulator'); -const openBCISample = require('../openBCISample'); +const openBCISample = require('../openBCIUtilities'); const k = require('../openBCIConstants'); chai.use(chaiAsPromised); From 7c31cf2c251dce65f5ea5a155c8bdc3e1ca431d5 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Sun, 16 Jul 2017 23:46:15 -0400 Subject: [PATCH 14/42] making good but slow progress --- openBCICyton.js | 9 +- openBCISimulator.js | 488 --------------- test/openBCICyton-Impedance-test.js | 6 +- test/openBCICyton-radio-test.js | 103 ++-- test/openBCICyton-test.js | 777 +----------------------- test/openBCISimulator-test.js | 894 ---------------------------- 6 files changed, 88 insertions(+), 2189 deletions(-) delete mode 100644 openBCISimulator.js delete mode 100644 test/openBCISimulator-test.js diff --git a/openBCICyton.js b/openBCICyton.js index 3f76fec..8a9e1ae 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -7,7 +7,7 @@ const OpenBCIUtilities = require('openbci-utilities'); const obciUtils = OpenBCIUtilities.Utilities; const k = OpenBCIUtilities.Constants; const obciDebug = OpenBCIUtilities.Debug; -const openBCISimulator = require('./openBCISimulator'); +const OpenBCISimulator = OpenBCIUtilities.Simulator; const Sntp = require('sntp'); const bufferEqual = require('buffer-equal'); const math = require('mathjs'); @@ -270,7 +270,7 @@ OpenBCICyton.prototype.connect = function (portName) { // If we are simulating, set portName to fake name this.portName = k.OBCISimulatorPortName; if (this.options.verbose) console.log('using faux board ' + portName); - this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { + this.serial = new OpenBCISimulator(this.portName, { accel: this.options.simulatorHasAccelerometer, alpha: this.options.simulatorInjectAlpha, boardFailure: this.options.simulatorBoardFailure, @@ -428,7 +428,6 @@ OpenBCICyton.prototype.streamStart = function () { return new Promise((resolve, reject) => { if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); this._streaming = true; - this._reset_ABANDONED(); // framework is incomplete but looks useful this.write(k.OBCIStreamStart).then(resolve, reject); }); }; @@ -1864,9 +1863,9 @@ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { _.forEach(output.rawDataPackets, (rawDataPacket) => { // Emit that buffer - this.emit('rawDataPacket', rawPacket); + this.emit('rawDataPacket', rawDataPacket); // Submit the packet for processing - this._processQualifiedPacket(rawPacket); + this._processQualifiedPacket(rawDataPacket); this._rawDataPacketToSample.rawDataPacket = rawDataPacket; const sample = obciUtils.transformRawDataPacketToSample(this._rawDataPacketToSample); this._finalizeNewSample(sample); diff --git a/openBCISimulator.js b/openBCISimulator.js deleted file mode 100644 index e7f6be2..0000000 --- a/openBCISimulator.js +++ /dev/null @@ -1,488 +0,0 @@ -'use strict'; -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var stream = require('stream'); - -var openBCISample = require('./openBCIUtilities'); -var k = openBCISample.k; -var now = require('performance-now'); - -function OpenBCISimulatorFactory () { - var factory = this; - - var _options = { - accel: true, - alpha: true, - boardFailure: false, - daisy: false, - daisyCanBeAttached: true, - drift: 0, - firmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], - 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 OpenBCISimulator (portName, options) { - options = (typeof options !== 'function') && options || {}; - var opts = {}; - - stream.Stream.call(this); - - /** Configuring Options */ - var o; - for (o in _options) { - var 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 = openBCISample.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 - // TODO: upgrade from old-style streams to stream.Duplex or stream.Transform - util.inherits(OpenBCISimulator, stream.Stream); - - OpenBCISimulator.prototype.flush = function (callback) { - this.outputBuffered = 0; - - clearTimeout(this.outputLoopHandle); - this.outputLoopHandle = null; - - if (callback) callback(); - }; - - OpenBCISimulator.prototype.isOpen = function () { - return this.connected; - }; - - // output only size bytes of the output buffer - OpenBCISimulator.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 - var 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 - OpenBCISimulator.prototype._output = function (dataBuffer) { - // drain full buffers until there is no overflow - while (this.outputBuffered + dataBuffer.length > this.outputBuffer.length) { - var 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) { - var 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; - } - var outputLoop = () => { - var 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); - } - } - }; - - OpenBCISimulator.prototype.write = function (data, callback) { - if (!this.connected) { - if (callback) callback('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; - default: - break; - } - - /** Handle Callback */ - if (callback) { - callback(null, 'Success!'); - } - }; - - OpenBCISimulator.prototype.drain = function (callback) { - if (callback) callback(); - }; - - OpenBCISimulator.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('not connected'); - } - }; - - OpenBCISimulator.prototype._startStream = function () { - var intervalInMS = 1000 / this.options.sampleRate; - - if (intervalInMS < 2) intervalInMS = 2; - - var getNewPacket = sampNumber => { - if (this.options.accel) { - if (this.synced) { - if (this.sendSyncSetPacket) { - this.sendSyncSetPacket = false; - return openBCISample.convertSampleToPacketAccelTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0)); - } else { - return openBCISample.convertSampleToPacketAccelTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0)); - } - } else { - return openBCISample.convertSampleToPacketStandard(this.sampleGenerator(sampNumber)); - } - } else { - if (this.synced) { - if (this.sendSyncSetPacket) { - this.sendSyncSetPacket = false; - return openBCISample.convertSampleToPacketRawAuxTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); - } else { - return openBCISample.convertSampleToPacketRawAuxTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); - } - } else { - return openBCISample.convertSampleToPacketRawAux(this.sampleGenerator(sampNumber), new Buffer([0, 0, 0, 0, 0, 0])); - } - } - }; - - this.stream = setInterval(() => { - this._output(getNewPacket(this.sampleNumber)); - this.sampleNumber++; - }, intervalInMS); - }; - - OpenBCISimulator.prototype._syncUp = function () { - setTimeout(() => { - this.sendSyncSetPacket = true; - }, 12); // 3 packets later - }; - - OpenBCISimulator.prototype._printEOT = function () { - this._output(new Buffer('$$$')); - }; - - OpenBCISimulator.prototype._printFailure = function () { - this._output(new Buffer('Failure: ')); - }; - - OpenBCISimulator.prototype._printSuccess = function () { - this._output(new Buffer('Success: ')); - }; - - OpenBCISimulator.prototype._printValidatedCommsTimeout = function () { - this._printFailure(); - this._output(new Buffer('Communications timeout - Device failed to poll Host')); - this._printEOT(); - }; - - OpenBCISimulator.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; - } - }; - - factory.OpenBCISimulator = OpenBCISimulator; -} - -util.inherits(OpenBCISimulatorFactory, EventEmitter); - -module.exports = new OpenBCISimulatorFactory(); diff --git a/test/openBCICyton-Impedance-test.js b/test/openBCICyton-Impedance-test.js index fa148a9..2166ea1 100644 --- a/test/openBCICyton-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -3,8 +3,8 @@ const bluebirdChecks = require('./bluebirdChecks'); const chai = require('chai'); const should = chai.should(); // eslint-disable-line no-unused-vars const Cyton = require('../openBCICyton'); -const openBCISample = require('../openBCIUtilities'); -const k = openBCISample.k; +const openBCIUtilities = require('openbci-utilities').Utilities; +const k = require('openbci-utilities').Constants; const chaiAsPromised = require('chai-as-promised'); const sinonChai = require('sinon-chai'); @@ -264,7 +264,7 @@ describe('#impedanceTesting', function () { impedanceArray = arr; done(); }); - ourBoard.impedanceArray[0] = openBCISample.impedanceObject(1); + ourBoard.impedanceArray[0] = openBCIUtilities.impedanceObject(1); ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', 'p', 'b', 'B']).catch(err => done(err)); }); describe('#channel1', function () { diff --git a/test/openBCICyton-radio-test.js b/test/openBCICyton-radio-test.js index 5f5905d..136fea3 100644 --- a/test/openBCICyton-radio-test.js +++ b/test/openBCICyton-radio-test.js @@ -4,9 +4,8 @@ var sinon = require('sinon'); // eslint-disable-line no-unused-vars var chai = require('chai'); var expect = chai.expect; var should = chai.should(); // eslint-disable-line no-unused-vars -var openBCIBoard = require('../openBCIBoard'); -var openBCISample = openBCIBoard.OpenBCISample; -var k = openBCISample.k; +var OpenBCICyton = require('../openBCICyton'); +var k = require('openbci-utilities').Constants; var chaiAsPromised = require('chai-as-promised'); var sinonChai = require('sinon-chai'); @@ -18,7 +17,7 @@ describe('openbci-radios', function () { var ourBoard, masterPortName; before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard(); + ourBoard = new OpenBCICyton(); ourBoard.autoFindOpenBCIBoard() .then(portName => { ourBoard = null; @@ -62,7 +61,7 @@ describe('openbci-radios', function () { afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -71,7 +70,7 @@ describe('openbci-radios', function () { }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -91,7 +90,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -103,7 +102,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -117,7 +116,7 @@ describe('openbci-radios', function () { }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -131,7 +130,7 @@ describe('openbci-radios', function () { }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -145,7 +144,7 @@ describe('openbci-radios', function () { }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -159,7 +158,7 @@ describe('openbci-radios', function () { }); it('should not change the channel if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -175,7 +174,7 @@ describe('openbci-radios', function () { it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -204,7 +203,7 @@ describe('openbci-radios', function () { }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -212,7 +211,7 @@ describe('openbci-radios', function () { ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -232,7 +231,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -245,7 +244,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -258,7 +257,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -271,7 +270,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -285,7 +284,7 @@ describe('openbci-radios', function () { }); it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { var newChannelNumber = 2; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -315,14 +314,14 @@ describe('openbci-radios', function () { afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioChannelGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -341,7 +340,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -353,7 +352,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -369,7 +368,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should get message even if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -396,7 +395,7 @@ describe('openbci-radios', function () { }); afterEach(() => bluebirdChecks.noPendingPromises()); it('should not change the channel number if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -405,7 +404,7 @@ describe('openbci-radios', function () { }); it('should reject if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -426,7 +425,7 @@ describe('openbci-radios', function () { }); it('should reject if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -439,7 +438,7 @@ describe('openbci-radios', function () { }); it('should reject if a number is not sent as input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -453,7 +452,7 @@ describe('openbci-radios', function () { }); it('should reject if no poll time is presented as arg', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -467,7 +466,7 @@ describe('openbci-radios', function () { }); it('should reject if the requested new poll time is lower than 0', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -481,7 +480,7 @@ describe('openbci-radios', function () { }); it('should reject if the requested new poll time is higher than 255', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -495,7 +494,7 @@ describe('openbci-radios', function () { }); it('should not change the poll time if the board is not communicating with the host', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -511,7 +510,7 @@ describe('openbci-radios', function () { it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { var newPollTime = 69; - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -542,14 +541,14 @@ describe('openbci-radios', function () { afterEach(() => bluebirdChecks.noPendingPromises()); it('should not query if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); }); it('should not query if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -568,7 +567,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should not query if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -580,7 +579,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should query if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -596,7 +595,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should get failure message if the board is not communicating with dongle', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorBoardFailure: true, @@ -624,28 +623,28 @@ describe('openbci-radios', function () { afterEach(() => bluebirdChecks.noPendingPromises()); it('should not try to set baud rate if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); }); it('should reject if no input', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); }); it('should be rejected if input type incorrect', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); }); it('should not try to change the baud rate if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -664,7 +663,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should not try to change the baud rate if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -676,7 +675,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should set the baud rate to default if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -692,7 +691,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should set the baud rate to fast if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -722,14 +721,14 @@ describe('openbci-radios', function () { afterEach(() => bluebirdChecks.noPendingPromises()); it('should not get system status if not connected', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); }); it('should not get system status if streaming', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -748,7 +747,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should not get system status if not firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true }); @@ -760,7 +759,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should get up system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2' @@ -776,7 +775,7 @@ describe('openbci-radios', function () { }).catch(err => done(err)); }); it('should get down system status if firmware version 2', function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulate: true, simulatorFirmwareVersion: 'v2', @@ -797,7 +796,7 @@ describe('openbci-radios', function () { describe('#radioTests', function () { this.timeout(0); before(function (done) { - ourBoard = new openBCIBoard.OpenBCIBoard({ + ourBoard = new OpenBCICyton({ verbose: true, simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 3377227..bb17e08 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -550,6 +550,9 @@ describe('openbci-sdk', function () { after(() => bluebirdChecks.noPendingPromises()); afterEach(function () { if (spy) spy.reset(); + ourBoard.removeAllListeners('sample'); + ourBoard.removeAllListeners('ready'); + ourBoard.removeAllListeners('rawDataPacket'); }); describe('#connect/disconnect/streamStart/streamStop', function () { it('rejects if already disconnected', function () { @@ -559,9 +562,26 @@ describe('openbci-sdk', function () { ourBoard.connect(masterPortName).catch(err => done(err)); ourBoard.once('ready', () => { - ourBoard.connect(masterPortName).should.be.rejected - .then(() => ourBoard.disconnect()) - .should.notify(done); + ourBoard.connect(masterPortName) + .then(() => { + return ourBoard.disconnect(); + }) + .then(() => { + done("should have failed to connect"); + }) + .catch((err) => { + if (err === 'already connected!') { + return ourBoard.disconnect(); + } else { + return Promise.reject(err); + } + }) + .then(() => { + done(); + }) + .catch((err) => { + done(err); + }) }); }); it('gets the ready signal from the board and sends a stop streaming command before disconnecting', function (done) { @@ -908,7 +928,13 @@ describe('openbci-sdk', function () { }); afterEach(function (done) { if (ourBoard.isConnected()) { - ourBoard.disconnect().then(done, done); + ourBoard.disconnect() + .then(() => { + done(); + }) + .catch(() => { + done(); + }) } else { done(); } @@ -2485,749 +2511,6 @@ $$$`); }); }); - describe('#radioChannelSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not change the channel number if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - - it('should reject if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelSet(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should reject if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if a number is not sent as input', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(26).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should not change the channel if the board is not communicating with the host', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { - var newChannelNumber = 2; - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(newChannelNumber).then(channelNumber => { - expect(channelNumber).to.be.equal(newChannelNumber); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - describe('#radioChannelSetHostOverride', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - it('should not change the channel number if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); - }); - it('should reject if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelSetHostOverride(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should reject if a number is not sent as input', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(26).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { - var newChannelNumber = 2; - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(newChannelNumber).then(channelNumber => { - expect(channelNumber).to.be.equal(newChannelNumber); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - describe('#radioChannelGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not query if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - it('should not query if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not query if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should query if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().then(res => { - expect(res.channelNumber).to.be.within(k.OBCIRadioChannelMin, k.OBCIRadioChannelMax); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get message even if the board is not communicating with dongle', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioPollTimeSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - it('should not change the channel number if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); - }); - - it('should reject if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioPollTimeSet(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - - it('should reject if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if a number is not sent as input', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if no poll time is presented as arg', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new poll time is lower than 0', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new poll time is higher than 255', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(256).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should not change the poll time if the board is not communicating with the host', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { - var newPollTime = 69; - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(newPollTime).then(() => { - done(); - }).catch(err => { - done(err); - }); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioPollTimeGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not query if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - it('should not query if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioPollTimeGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not query if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should query if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().then(pollTime => { - expect(pollTime).to.be.greaterThan(0); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get failure message if the board is not communicating with dongle', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioBaudRateSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not try to set baud rate if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); - }); - it('should reject if no input', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); - }); - it('should be rejected if input type incorrect', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); - }); - it('should not try to change the baud rate if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioBaudRateSet('default').then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not try to change the baud rate if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should set the baud rate to default if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('default').then(baudrate => { - expect(baudrate).to.be.equal(115200); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should set the baud rate to fast if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('fast').then(baudrate => { - expect(baudrate).to.be.equal(230400); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioSystemStatusGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not get system status if not connected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); - }); - it('should not get system status if streaming', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioSystemStatusGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not get system status if not firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should get up system status if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true(); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get down system status if firmware version 2', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2', - simulatorBoardFailure: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.false(); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - xdescribe('#hardwareValidation', function () { this.timeout(20000); // long timeout for pleanty of stream time :) var runHardwareValidation = true; diff --git a/test/openBCISimulator-test.js b/test/openBCISimulator-test.js deleted file mode 100644 index db64159..0000000 --- a/test/openBCISimulator-test.js +++ /dev/null @@ -1,894 +0,0 @@ -'use strict'; -const bluebirdChecks = require('./bluebirdChecks'); -const bufferEqual = require('buffer-equal'); -const chai = require('chai'); -const chaiAsPromised = require(`chai-as-promised`); -const expect = chai.expect; -const should = chai.should(); // eslint-disable-line no-unused-vars -const openBCISimulator = require('../openBCISimulator'); -const openBCISample = require('../openBCIUtilities'); -const k = require('../openBCIConstants'); - -chai.use(chaiAsPromised); - -describe('openBCISimulator', function () { - this.timeout(4000); - var portName = k.OBCISimulatorPortName; - afterEach(() => bluebirdChecks.noPendingPromises(200)); - describe('#constructor', function () { - var 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.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.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.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.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.OpenBCISimulator(portName, { - firmwareVersion: 'v2' - }); - expect(simulator.options.firmwareVersion).to.equal(k.OBCIFirmwareV2); - }); - it('should be able to simulate board failure', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - boardFailure: true - }); - expect(simulator.options.boardFailure).to.be.true; - }); - it('should be able to simulate serial port failure', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - serialPortFailure: true - }); - expect(simulator.options.serialPortFailure).to.be.true; - }); - it('can turn 50Hz line noise on', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - lineNoise: '50Hz' - }); - (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseHz50); - }); - it('can turn no line noise on', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - lineNoise: 'none' - }); - (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseNone); - }); - it('should not inject alpha if desired', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - alpha: false - }); - expect(simulator.options.alpha).to.be.false; - }); - it('should be able to not use the accel', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - accel: false - }); - expect(simulator.options.accel).to.be.false; - }); - it('should be able to set positive drift', function () { - simulator = new openBCISimulator.OpenBCISimulator(portName, { - drift: 1 - }); - expect(simulator.options.drift).to.be.greaterThan(0); - }); - it('should be able to set negative drift', function () { - simulator = new openBCISimulator.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.OpenBCISimulator(portName, { - foo: 'bar' - }); - done('did not throw'); - } catch (e) { done(); } - }); - }); - describe('#write', function () { - it('should refuse to write when not connected', function (done) { - var simulator = new openBCISimulator.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) { - var simulator = new openBCISimulator.OpenBCISimulator(k.OBCISimulatorPortName); - simulator.close(err => { - if (err) { - done(); - } else { - done('closed successfully but had no time to open'); - } - }); - }); - }); - describe(`_startStream`, function () { - it('should return a packet with sample data in it', function (done) { - var simulator = new openBCISimulator.OpenBCISimulator(k.OBCISimulatorPortName); - var sampleCounter = 0; - var sampleTestSize = 5; - - var newDataFunc = data => { - if (sampleCounter > sampleTestSize) { - simulator.write(k.OBCIStreamStop); - simulator.removeListener('data', newDataFunc); - let sample = openBCISample.parseRawPacketStandard(data, k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault), true); - 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) { - var simulator = new openBCISimulator.OpenBCISimulator(k.OBCISimulatorPortName); - var sampleCounter = 0; - var sampleTestSize = 5; - - var 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(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)); - } else if (sampleCounter === 1) { - // Now this data should be the time sync up packet - expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCISample.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(openBCISample.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) { - var simulator = new openBCISimulator.OpenBCISimulator(k.OBCISimulatorPortName, { - accel: false - }); - var sampleCounter = 0; - var sampleTestSize = 5; - - var 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(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux)); - } else if (sampleCounter === 1) { - // Now this data should be the time sync up packet - expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCISample.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(openBCISample.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced)); - } - sampleCounter++; - }; - - simulator.on('data', newDataFunc); - simulator.once('open', () => simulator.write(k.OBCIStreamStart)); - }); - }); - describe(`firmwareVersion1`, function () { - var simulator; - beforeEach((done) => { - simulator = new openBCISimulator.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 () { - var simulator; - beforeEach((done) => { - simulator = new openBCISimulator.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)) { - 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(data.toString().match(`daisy removed${k.OBCIParseEOT}`)).to.not.equal(null); - if (data.toString().match(k.OBCIParseEOT)) { - expect(simulator.options.daisy).to.equal(false); - 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)) { - 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); - 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)) { - 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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 => { - var newChanNum = 20; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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 => { - var newChanNum = 26; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 => { - var systemChannelNumber = 0; - var newHostChannelNumber = 1; - simulator.channelNumber = systemChannelNumber; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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 => { - var systemChannelNumber = 0; - var oldHostChannelNumber = 1; - simulator.channelNumber = systemChannelNumber; - simulator.hostChannelNumber = oldHostChannelNumber; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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 => { - var newChanNum = 26; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 => { - var expectedPollTime = 80; - simulator.pollTime = expectedPollTime; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 => { - var newPollTime = 20; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 => { - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.isFailureInBuffer(buf)).to.be.false; - var eotBuf = new Buffer('$$$'); - var newBaudRateBuf; - for (var i = buf.length; i > 3; i--) { - if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = buf.slice(i - 9, i - 3); - break; - } - } - var 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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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 => { - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.isFailureInBuffer(buf)).to.be.false; - var eotBuf = new Buffer(`$$$`); - var newBaudRateBuf; - for (var i = buf.length; i > 3; i--) { - if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = buf.slice(i - 9, i - 3); - break; - } - } - var 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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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 => { - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.true; - expect(openBCISample.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; - var buf = new Buffer(0); - var dataEmit = data => { - buf = Buffer.concat([buf, data]); - if (openBCISample.doesBufferHaveEOT(buf)) { - expect(openBCISample.isSuccessInBuffer(buf)).to.be.false; - expect(openBCISample.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 () { - var simulator; - afterEach(done => { - simulator.removeAllListeners(); - simulator.write(k.OBCIStreamStop); - simulator.close(done); - }); - it('Should accumulate packets if set to FullBuffers', function (done) { - var bufferSize = 64; - simulator = new openBCISimulator.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) { - var bufferSize = 4096; - simulator = new openBCISimulator.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.OpenBCISimulator(portName, { - fragmentation: k.OBCISimulatorFragmentationOneByOne - }); - var 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.OpenBCISimulator(portName, { - fragmentation: k.OBCISimulatorFragmentationRandom - }); - var buffer = new Buffer(0); - var counter = 0; - simulator.on('data', function (data) { - buffer = Buffer.concat([buffer, data], buffer.length + data.length); - if (buffer.length >= 33) { - openBCISample.parseRawPacketStandard(buffer.slice(0, 33)); - 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); - var simulator; - beforeEach(function (done) { - simulator = new openBCISimulator.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(openBCISample.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; - var 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; - var emitCounter = 0; - var maxPacketsBetweenSetPacket = 5; - var 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 (openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketAccelTimeSyncSet) { - simulator.removeListener('data', newData); - simulator.write(k.OBCIStreamStop, err => { - if (err) done(err); - done(); - }); - } else { - expect(openBCISample.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; - var emitCounter = 0; - var maxPacketsBetweenSetPacket = 5; - var 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 (openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketRawAuxTimeSyncSet) { - simulator.removeListener('data', newData); - simulator.write(k.OBCIStreamStop, err => { - if (err) done(err); - done(); - }); - } else { - expect(openBCISample.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 5088d58192258063c2eda5b1826e078fb9e5f51f Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 00:24:50 -0400 Subject: [PATCH 15/42] All tests passing --- openBCICyton.js | 6 ++-- test/openBCICyton-test.js | 70 +++++++++------------------------------ 2 files changed, 19 insertions(+), 57 deletions(-) diff --git a/openBCICyton.js b/openBCICyton.js index 8a9e1ae..3ad552d 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -352,7 +352,9 @@ OpenBCICyton.prototype._disconnected = function (err) { clearTimeout(this.writer); this.writer = null; - this.serial.removeAllListeners(); + this.serial.removeAllListeners('close'); + this.serial.removeAllListeners('error'); + this.serial.removeAllListeners('data'); this.serial = null; this.emit('close'); @@ -1854,7 +1856,7 @@ OpenBCICyton.prototype._processBytes = function (data) { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { - if (_.isNull(dataBuffer) || _.isUndefined(dataBuffer)) return; + if (_.isNull(dataBuffer) || _.isUndefined(dataBuffer)) return null; const output = obciUtils.extractRawDataPackets(dataBuffer); dataBuffer = output.buffer; diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index bb17e08..e07336f 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -989,18 +989,22 @@ describe('openbci-sdk', function () { writeWhileConnected(); ourBoard.disconnect().catch(done); }); - it('disconnects immediately, rejecting all buffered writes', function () { + it('disconnects immediately, rejecting all buffered writes', function (done) { var writeSpy = sinon.spy(ourBoard.serial, 'write'); - return Promise.all([ - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected, - ourBoard.disconnect() - ]).then(() => { - writeSpy.should.have.not.been.called; - writeSpy.restore(); - }); + ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); + ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); + ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); + ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); + + ourBoard.disconnect() + .then(() => { + writeSpy.should.have.not.been.called(); + writeSpy.restore(); + done(); + }) + .catch((err) => { + done(err); + }) }); }); describe('#listPorts', function () { @@ -1842,50 +1846,6 @@ describe('openbci-sdk', function () { }); }); - describe('#_processPacket Errors', function () { - var ourBoard; - before(() => { - ourBoard = new Cyton({ - verbose: false - }); - }); - beforeEach(() => { - ourBoard.badPackets = 0; - ourBoard.previousSampleNumber = 0; - }); - afterEach(() => { - ourBoard.sync.curSyncObj = null; - }); - after(() => bluebirdChecks.noPendingPromises()); - it('_processPacketStandardAccel', function () { - ourBoard.once('droppedPacket', (droppedPacketArray) => { - expect(droppedPacketArray[0]).to.equal(1); - }); - ourBoard._processPacketStandardAccel(new Buffer(5)); - expect(ourBoard.badPackets).to.equal(1); - }); - it('_processPacketStandardRawAux', function () { - ourBoard.once('droppedPacket', (droppedPacketArray) => { - expect(droppedPacketArray[0]).to.equal(1); - }); - ourBoard._processPacketStandardRawAux(new Buffer(5)); - expect(ourBoard.badPackets).to.equal(1); - }); - it('_processPacketTimeSyncedAccel', function () { - ourBoard.once('droppedPacket', (droppedPacketArray) => { - expect(droppedPacketArray[0]).to.equal(1); - }); - ourBoard._processPacketTimeSyncedAccel(new Buffer(5)); - expect(ourBoard.badPackets).to.equal(1); - }); - it('_processPacketTimeSyncedRawAux', function () { - ourBoard.once('droppedPacket', (droppedPacketArray) => { - expect(droppedPacketArray[0]).to.equal(1); - }); - ourBoard._processPacketTimeSyncedRawAux(new Buffer(5)); - expect(ourBoard.badPackets).to.equal(1); - }); - }); describe('#time', function () { after(() => bluebirdChecks.noPendingPromises()); it('should use sntp time when sntpTimeSync specified in options', function (done) { From 9592e564b15e7851f797be9de000ae62129c4b08 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 10:56:46 -0400 Subject: [PATCH 16/42] Saving --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 47d4716..0c9a8b5 100644 --- a/changelog.md +++ b/changelog.md @@ -15,7 +15,7 @@ const ourBoard = new Cyton(); ``` * Major change to how board is initialized with removal of `factory` paradigm. -* Drop support for Node 4 and 5 due to lack of EMACS 6 +* New dependency called `openbci-utilities`. ### Bug Fixes * Documentation error with `testSignal` function. From cbd8343ac30b57ef21ece83cda2b7e743ebcc83a Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 11:03:37 -0400 Subject: [PATCH 17/42] Fix linting issues --- openBCICyton.js | 23 +++++++++-------------- test/openBCICyton-test.js | 9 ++++----- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/openBCICyton.js b/openBCICyton.js index 3ad552d..1757a29 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -127,7 +127,6 @@ var _options = { debug: false }; - /** * @description The initialization method to call first, before any other method. * @param options {* | InitializationObject} (optional) - Board optional configurations. @@ -610,16 +609,16 @@ OpenBCICyton.prototype.autoFindOpenBCIBoard = function () { } // This is one big if statement if (ports.some(port => { - return serialPatterns.some(patterns => { - for (var attribute in patterns) { - if (!String(port[attribute]).match(patterns[attribute])) { - return false; - } + return serialPatterns.some(patterns => { + for (var attribute in patterns) { + if (!String(port[attribute]).match(patterns[attribute])) { + return false; } - this.portName = port.comName; - return true; - }); - })) { + } + this.portName = port.comName; + return true; + }); + })) { if (this.options.verbose) console.log('auto found board'); resolve(this.portName); } else { @@ -1949,10 +1948,6 @@ OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { } }; - - - - /** * @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index e07336f..f463e96 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -15,7 +15,6 @@ const fs = require('fs'); const math = require('mathjs'); const dirtyChai = require('dirty-chai'); - chai.use(chaiAsPromised); chai.use(sinonChai); chai.use(dirtyChai); @@ -567,7 +566,7 @@ describe('openbci-sdk', function () { return ourBoard.disconnect(); }) .then(() => { - done("should have failed to connect"); + done('should have failed to connect'); }) .catch((err) => { if (err === 'already connected!') { @@ -581,7 +580,7 @@ describe('openbci-sdk', function () { }) .catch((err) => { done(err); - }) + }); }); }); it('gets the ready signal from the board and sends a stop streaming command before disconnecting', function (done) { @@ -934,7 +933,7 @@ describe('openbci-sdk', function () { }) .catch(() => { done(); - }) + }); } else { done(); } @@ -1004,7 +1003,7 @@ describe('openbci-sdk', function () { }) .catch((err) => { done(err); - }) + }); }); }); describe('#listPorts', function () { From bec094a8b6f4b7909853641d8e3b9140cdf7a6d0 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 12:29:49 -0400 Subject: [PATCH 18/42] Reworked examples for new 2.0.0 and updated readme --- README.md | 13 +++---- examples/debug/debug.js | 11 +++--- examples/debug/package.json | 2 +- examples/getStreaming/getStreaming.js | 8 ++--- examples/getStreaming/package.json | 2 +- .../getStreamingDaisy/getStreamingDaisy.js | 8 ++--- examples/getStreamingDaisy/package.json | 2 +- examples/impedance/impedance.js | 11 +++--- examples/impedance/package.json | 3 +- examples/labstreaminglayer/index.js | 31 ++++++++-------- examples/labstreaminglayer/package.json | 2 +- examples/python/index.js | 35 ++++++++++--------- examples/python/package.json | 4 +-- examples/timeSync/package.json | 4 +-- examples/timeSync/timeSync.js | 20 ++++++----- package.json | 20 +++++------ 16 files changed, 91 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index ce6163c..ce8ab28 100644 --- a/README.md +++ b/README.md @@ -127,8 +127,8 @@ const ourBoard = new Cyton({ Have a daisy?: ```js -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ +var Cyton = require('openbci').Cyton; +var ourBoard = new Cyton({ boardType: `daisy`, hardSet: true }); @@ -138,7 +138,7 @@ Go [checkout out the get streaming with daisy example](examples/getStreamingDais Another useful way to start the simulator: ```js const Cyton = require('openbci').Cyton; -const k = require('openbci').Constants; +const k = require('openbci-utilities').Constants; const ourBoard = new Cyton(); ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true .then((boardSerial) => { @@ -152,7 +152,8 @@ ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true or if you are using ES6: ```js -import { Cyton, Constants } from 'openbci'; +import { Cyton } from 'openbci'; +import { Constants } from 'openbci-utilities'; const ourBoard = new Cyton(); ourBoard.connect(Constants.OBCISimulatorPortName); ``` @@ -237,7 +238,7 @@ Keep your resync interval above 50ms. While it's important to resync every coupl Using local computer time: ```js const Cyton = require('openbci').Cyton; -const k = require('openbci').Constants; +const k = require('openbci-utilities').Constants; const ourBoard = new Cyton({ verbose:true }); @@ -1178,7 +1179,7 @@ Emitted when there is a new sample available. To use the constants file simply: ```js -const k = require('openbci').Constants; +const k = require('openbci-utilities').Constants; console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console. ``` diff --git a/examples/debug/debug.js b/examples/debug/debug.js index 822e185..9fa2ec3 100644 --- a/examples/debug/debug.js +++ b/examples/debug/debug.js @@ -10,12 +10,11 @@ * then `npm start` */ -var stream = true; -var debug = true; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions -var OpenBCIBoard = require('openbci').OpenBCIBoard; - -var ourBoard = new OpenBCIBoard({ +const stream = true; +const debug = true; // Pretty print any bytes in and out... it's amazing... +const verbose = true; // Adds verbosity to functions +const Cyton = require('openbci').Cyton; +let ourBoard = new Cyton({ debug: debug, verbose: verbose }); diff --git a/examples/debug/package.json b/examples/debug/package.json index cfa64a9..04a4710 100644 --- a/examples/debug/package.json +++ b/examples/debug/package.json @@ -13,6 +13,6 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.5.0" + "openbci": "^2.0.0" } } diff --git a/examples/getStreaming/getStreaming.js b/examples/getStreaming/getStreaming.js index 9d9f56f..2bc7584 100644 --- a/examples/getStreaming/getStreaming.js +++ b/examples/getStreaming/getStreaming.js @@ -9,11 +9,11 @@ * do `npm install` * then `npm start` */ -var debug = false; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions +const debug = false; // Pretty print any bytes in and out... it's amazing... +const verbose = true; // Adds verbosity to functions -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ +const Cyton = require('openbci').Cyton; +let ourBoard = new Cyton({ debug: debug, verbose: verbose }); diff --git a/examples/getStreaming/package.json b/examples/getStreaming/package.json index 04f23df..68edb1b 100644 --- a/examples/getStreaming/package.json +++ b/examples/getStreaming/package.json @@ -13,6 +13,6 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.5.0" + "openbci": "^2.0.0" } } diff --git a/examples/getStreamingDaisy/getStreamingDaisy.js b/examples/getStreamingDaisy/getStreamingDaisy.js index 0611534..973be10 100644 --- a/examples/getStreamingDaisy/getStreamingDaisy.js +++ b/examples/getStreamingDaisy/getStreamingDaisy.js @@ -9,11 +9,11 @@ * do `npm install` * then `npm start` */ -var debug = false; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions +const debug = false; // Pretty print any bytes in and out... it's amazing... +const verbose = true; // Adds verbosity to functions -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ +const Cyton = require('openbci').Cyton; +let ourBoard = new Cyton({ boardType: 'daisy', debug: debug, hardSet: true, diff --git a/examples/getStreamingDaisy/package.json b/examples/getStreamingDaisy/package.json index a84fa7a..f27ace9 100644 --- a/examples/getStreamingDaisy/package.json +++ b/examples/getStreamingDaisy/package.json @@ -13,6 +13,6 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.5.0" + "openbci": "^2.0.0" } } diff --git a/examples/impedance/impedance.js b/examples/impedance/impedance.js index 137439d..d9c29ec 100644 --- a/examples/impedance/impedance.js +++ b/examples/impedance/impedance.js @@ -9,15 +9,16 @@ * do `npm install` * then `npm start` */ -var debug = false; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions +const debug = false; // Pretty print any bytes in and out... it's amazing... +const verbose = true; // Adds verbosity to functions -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var ourBoard = new OpenBCIBoard({ +const Cyton = require('openbci').Cyton; +let ourBoard = new Cyton({ debug: debug, verbose: verbose }); -var k = require('openbci').OpenBCIConstants; + +const k = require('openbci-utilities').Constants; let startedImpedance = false; let iBuffer = []; diff --git a/examples/impedance/package.json b/examples/impedance/package.json index 3b00667..58fffef 100644 --- a/examples/impedance/package.json +++ b/examples/impedance/package.json @@ -13,6 +13,7 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.5.0" + "openbci": "^2.0.0", + "openbci-utilities": "0.0.9" } } diff --git a/examples/labstreaminglayer/index.js b/examples/labstreaminglayer/index.js index 719c75d..4fb2517 100644 --- a/examples/labstreaminglayer/index.js +++ b/examples/labstreaminglayer/index.js @@ -9,24 +9,24 @@ * do `npm install` * then `npm start` */ -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var portPub = 'tcp://127.0.0.1:3004'; -var zmq = require('zmq-prebuilt'); -var socket = zmq.socket('pair'); -var debug = false; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions - -var ourBoard = new OpenBCIBoard({ +const portPub = 'tcp://127.0.0.1:3004'; +const zmq = require('zmq-prebuilt'); +const socket = zmq.socket('pair'); +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; +let ourBoard = new Cyton({ simulatorFirmwareVersion: 'v2', debug: debug, verbose: verbose }); -var timeSyncPossible = false; -var resyncPeriodMin = 1; -var secondsInMinute = 60; -var numChans = 8; -var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; +let timeSyncPossible = false; +let resyncPeriodMin = 1; +let secondsInMinute = 60; +let numChans = 8; +let resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; ourBoard.autoFindOpenBCIBoard().then(portName => { if (portName) { @@ -67,7 +67,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { } }); -var sampleFunc = sample => { +const sampleFunc = sample => { if (sample._count % resyncPeriod === 0) { ourBoard.syncClocksFull() .then(syncObj => { @@ -107,9 +107,10 @@ socket.bind(portPub, function (err) { /** * Used to send a message to the Python process. * @param {Object} interProcessObject The standard inter-process object. + * @param {Boolean} verbose Should we do a verbose print out * @return {None} */ -var sendToPython = (interProcessObject, verbose) => { +const sendToPython = (interProcessObject, verbose) => { if (verbose) { console.log(`<- out ${JSON.stringify(interProcessObject)}`); } diff --git a/examples/labstreaminglayer/package.json b/examples/labstreaminglayer/package.json index 1aa759e..6d6d53d 100644 --- a/examples/labstreaminglayer/package.json +++ b/examples/labstreaminglayer/package.json @@ -18,7 +18,7 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.4.2", + "openbci": "^2.0.0", "zmq-prebuilt": "^2.1.0" }, "devEngines": { diff --git a/examples/python/index.js b/examples/python/index.js index 80cd638..2ae7750 100644 --- a/examples/python/index.js +++ b/examples/python/index.js @@ -1,3 +1,4 @@ +'use strict'; /** * This is an example from the readme.md * On windows you should run with PowerShell not git bash. @@ -9,25 +10,25 @@ * do `npm install` * then `npm start` */ -var OpenBCIBoard = require('openbci').OpenBCIBoard; -var portPub = 'tcp://127.0.0.1:3004'; -var zmq = require('zmq-prebuilt'); -var socket = zmq.socket('pair'); -var simulate = false; // Sends synthetic data -var debug = false; // Pretty print any bytes in and out... it's amazing... -var verbose = true; // Adds verbosity to functions - -var ourBoard = new OpenBCIBoard({ +const portPub = 'tcp://127.0.0.1:3004'; +const zmq = require('zmq-prebuilt'); +const socket = zmq.socket('pair'); +const simulate = false; // 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; +let ourBoard = new Cyton({ simulate: simulate, // Uncomment to see how it works with simulator! simulatorFirmwareVersion: 'v2', debug: debug, verbose: verbose }); -var timeSyncPossible = false; -var resyncPeriodMin = 1; -var secondsInMinute = 60; -var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; +let timeSyncPossible = false; +let resyncPeriodMin = 1; +let secondsInMinute = 60; +let resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; ourBoard.autoFindOpenBCIBoard().then(portName => { if (portName) { @@ -61,7 +62,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { } }); -var sampleFunc = sample => { +const sampleFunc = sample => { if (sample._count % resyncPeriod === 0) { ourBoard.syncClocksFull() .then(syncObj => { @@ -124,7 +125,7 @@ var receiveFromPython = (rawData) => { socket.on('message', receiveFromPython); -var sendStatus = () => { +const sendStatus = () => { sendToPython({'action': 'active', 'message': 'ready', 'command': 'status'}, true); }; @@ -135,7 +136,7 @@ sendStatus(); * @param {String} body A stringify JSON object that shall be parsed. * @return {None} */ -var processInterfaceObject = (body) => { +const processInterfaceObject = (body) => { switch (body.command) { case 'status': processStatus(body); @@ -151,7 +152,7 @@ var processInterfaceObject = (body) => { * @param {Object} body * @return {None} */ -var processStatus = (body) => { +const processStatus = (body) => { switch (body.action) { case 'started': console.log(`python started @ ${body.message}ms`); diff --git a/examples/python/package.json b/examples/python/package.json index 69ba1f3..4ebb27a 100644 --- a/examples/python/package.json +++ b/examples/python/package.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "1.0.0", + "version": "1.1.0", "description": "node to python example", "main": "index.js", "scripts": { @@ -16,7 +16,7 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.4.2", + "openbci": "^2.0.0", "zmq-prebuilt": "^2.1.0" }, "devEngines": { diff --git a/examples/timeSync/package.json b/examples/timeSync/package.json index fd06409..b1992e5 100644 --- a/examples/timeSync/package.json +++ b/examples/timeSync/package.json @@ -1,6 +1,6 @@ { "name": "timesync", - "version": "1.0.0", + "version": "1.1.0", "description": "Time sync example", "main": "timeSync.js", "scripts": { @@ -14,6 +14,6 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^1.3.1" + "openbci": "^2.0.0" } } diff --git a/examples/timeSync/timeSync.js b/examples/timeSync/timeSync.js index 4bfbee0..1dbd6f1 100644 --- a/examples/timeSync/timeSync.js +++ b/examples/timeSync/timeSync.js @@ -6,14 +6,16 @@ * do `npm install` * then `npm start` */ +const verbose = true; // Adds verbosity to functions -var OpenBCIBoard = require('openbci').OpenBCIBoard; - -var ourBoard = new OpenBCIBoard({}); -var verbose = true; // Adds verbosity to functions +const Cyton = require('openbci').Cyton; +let ourBoard = new Cyton({ + simulatorFirmwareVersion: 'v2', + verbose: verbose +}); -var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! -var timeSyncPossible = false; +let sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! +let timeSyncPossible = false; ourBoard.autoFindOpenBCIBoard().then(portName => { if (portName) { @@ -34,7 +36,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { } }); -var readyFunc = () => { +const readyFunc = () => { // Get the sample rate after 'ready' sampleRate = ourBoard.sampleRate(); // 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. @@ -50,14 +52,14 @@ var readyFunc = () => { } }; -var killFunc = () => { +const killFunc = () => { ourBoard.disconnect() .then(() => { process.kill(); }); }; -var sampleFunc = sample => { +const sampleFunc = sample => { // Resynchronize every every second if (sample._count % (sampleRate * 1) === 0) { ourBoard.syncClocksFull() diff --git a/package.json b/package.json index deeb931..a8c85c9 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,11 @@ "clone": "^2.0.0", "gaussian": "^1.0.0", "lodash": "^4.17.4", - "mathjs": "^3.3.0", + "mathjs": "^3.14.2", "openbci-utilities": "0.0.9", "performance-now": "^2.1.0", "serialport": "4.0.7", - "sntp": "^2.0.0", + "sntp": "^2.0.2", "streamsearch": "^0.1.2" }, "directories": { @@ -32,18 +32,18 @@ }, "devDependencies": { "bluebird": "3.5.0", - "chai": "^3.4.1", - "chai-as-promised": "^6.0.0", + "chai": "^4.1.0", + "chai-as-promised": "^7.1.1", "codecov": "^2.1.0", "dirty-chai": "^2.0.1", "istanbul": "^0.4.4", - "mocha": "^3.0.2", + "mocha": "^3.4.2", "sandboxed-module": "^2.0.3", - "semistandard": "^10.0.0", - "sinon": "^2.1.0", - "sinon-as-promised": "^4.0.2", - "sinon-chai": "^2.8.0", - "snazzy": "^6.0.0" + "semistandard": "^11.0.0", + "sinon": "^2.3.8", + "sinon-as-promised": "^4.0.3", + "sinon-chai": "^2.11.0", + "snazzy": "^7.0.0" }, "repository": { "type": "git", From 489249e45cb94db11ec1592161f8638583e9b1d1 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 12:34:16 -0400 Subject: [PATCH 19/42] Working on travis issues sinon --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8c85c9..1cda7b7 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "mocha": "^3.4.2", "sandboxed-module": "^2.0.3", "semistandard": "^11.0.0", - "sinon": "^2.3.8", + "sinon": "^1.17.7", "sinon-as-promised": "^4.0.3", "sinon-chai": "^2.11.0", "snazzy": "^7.0.0" From fd82c277d545afdfe41608e87b407984724ed81f Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 12:49:24 -0400 Subject: [PATCH 20/42] fixing --- openBCICyton-old.js | 2352 ------------------------------------------- 1 file changed, 2352 deletions(-) delete mode 100644 openBCICyton-old.js diff --git a/openBCICyton-old.js b/openBCICyton-old.js deleted file mode 100644 index 6e80c67..0000000 --- a/openBCICyton-old.js +++ /dev/null @@ -1,2352 +0,0 @@ -'use strict'; -const bufferEqual = require('buffer-equal'); -const EventEmitter = require('events').EventEmitter; -const math = require('mathjs'); -const util = require('util'); -const SerialPort = require('serialport'); -const openBCISample = require('./openBCIUtilities'); -const k = openBCISample.k; -const openBCISimulator = require('./openBCISimulator'); -const Sntp = require('sntp'); -const openBCIUtils = require('./openBCIUtils'); -/** -* @description SDK for OpenBCI Board {@link www.openbci.com} -* @module 'openbci-sdk' -*/ - -const _options = { - boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], - baudRate: 115200, - hardSet: false, - simulate: false, - simulatorBoardFailure: false, - simulatorDaisyModuleAttached: false, - simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], - simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], - simulatorLatencyTime: 16, - simulatorBufferSize: 4096, - simulatorHasAccelerometer: true, - simulatorInternalClockDrift: 0, - simulatorInjectAlpha: true, - simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], - simulatorSampleRate: 250, - simulatorSerialPortFailure: false, - sntpTimeSync: false, - sntpTimeSyncHost: 'pool.ntp.org', - sntpTimeSyncPort: 123, - verbose: false, - debug: false -}; - -/** -* @description The initialization method to call first, before any other method. -* @param options (optional) - Board optional configurations. -* - `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if -* firmware on board has been previously configured. -* -* - `boardType` {String} - Specifies type of OpenBCI board. -* 3 Possible Boards: -* `default` - 8 Channel OpenBCI board (Default) -* `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. -* `ganglion` - 4 Channel board -* (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) -* -* - `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting -* `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) -* -* - `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on -* the board is not polling the RFduino on the dongle. (Default `false`) -* -* - `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. -* This is useful if you want to test how your application reacts to a user requesting 16 channels -* but there is no daisy module actually attached, or vice versa, where there is a daisy module -* attached and the user only wants to use 8 channels. (Default `false`) -* -* - `simulatorFirmwareVersion` {String} - Allows simulator to be started with firmware version 2 features -* 2 Possible Options: -* `v1` - Firmware Version 1 (Default) -* `v2` - Firmware Version 2 -* -* - `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which -* occurs commonly in real devices. It is recommended to test code with this enabled. -* 4 Possible Options: -* `none` - do not fragment packets; output complete chunks immediately when produced (Default) -* `random` - output random small chunks of data interspersed with full buffers -* `fullBuffers` - allow buffers to fill up until the latency timer has expired -* `oneByOne` - output each byte separately -* -* - `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers, - if `simulatorFragmentation` is specified. (Default `16`) -* -* - `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is -* specified. (Default `4096`) -* -* - `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) -* -* - `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) -* -* - `simulatorInjectLineNoise` {String} - Injects line noise on channels. -* 3 Possible Options: -* `60Hz` - 60Hz line noise (Default) [America] -* `50Hz` - 50Hz line noise [Europe] -* `none` - Do not inject line noise. -* -* - `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if -* `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that -* setting and this sample rate will be used. (Default is `250`) -* -* - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely -* due to a OpenBCI dongle not being plugged in. -* -* - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source -* of truth instead of local computer time. If you are running experiements on your local -* computer, keep this `false`. (Default `false`) -* -* - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). -* -* - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`) -* -* - `verbose` {Boolean} - Print out useful debugging events. (Default `false`) -* -* - `debug` {Boolean} - Print out a raw dump of bytes sent and received. (Default `false`) -* -* @constructor -* @author AJ Keller (@pushtheworldllc) -*/ -function Cyton (options) { - options = (typeof options !== 'function') && options || {}; - var opts = {}; - - /** Configuring Options */ - var o; - for (o in _options) { - var userOption = (o in options) ? o : o.toLowerCase(); - var userValue = options[userOption]; - delete options[userOption]; - - 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'); - - // Set to global options object - this.options = opts; - - /** Properties (keep alphabetical) */ - // Arrays - this.accelArray = [0, 0, 0]; // X, Y, Z - this.channelSettingsArray = k.channelSettingsArrayInit(k.numberOfChannelsForBoardType(this.options.boardType)); - this.writeOutArray = []; - // Booleans - this._streaming = false; - // Buffers - this.buffer = null; - this.masterBuffer = masterBufferMaker(); - // Objects - this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType)); - this.impedanceTest = { - active: false, - isTestingPInput: false, - isTestingNInput: false, - onChannel: 0, - sampleNumber: 0, - continuousMode: false, - impedanceForChannel: 0 - }; - this.info = { - boardType: this.options.boardType, - sampleRate: k.OBCISampleRate125, - firmware: k.OBCIFirmwareV1, - numberOfChannels: k.OBCINumberOfChannelsDefault, - missedPackets: 0 - }; - if (this.options.boardType === k.OBCIBoardDefault) { - this.info.sampleRate = k.OBCISampleRate250; - } - - this._lowerChannelsSampleObject = null; - this.serial = null; - this.sync = { - curSyncObj: null, - eventEmitter: null, - objArray: [], - sntpActive: false, - timeOffsetMaster: 0, - timeOffsetAvg: 0, - timeOffsetArray: [] - }; - this.writer = null; - // Numbers - this.badPackets = 0; - this.curParsingMode = k.OBCIParsingReset; - this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); - this.previousSampleNumber = -1; - this.sampleCount = 0; - this.timeOfPacketArrival = 0; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - // Strings - - // NTP - if (this.options.sntpTimeSync) { - // establishing ntp connection - this.sntpStart() - .catch(ignored => { - // try again once after a delay - return new Promise((resolve, reject) => { - setTimeout(resolve, 500); - }).then(() => this.sntpStart()); - }) - .then(() => { - if (this.options.verbose) console.log('SNTP: connected'); - }) - .catch(err => { - if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); - this.emit('error', err); - }); - } -} - -/** -* @description The essential precursor method to be called initially to establish a -* serial connection to the OpenBCI board. -* @param portName - a string that contains the port name of the Cyton. -* @returns {Promise} if the board was able to connect. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.connect = function (portName) { - return new Promise((resolve, reject) => { - if (this.isConnected()) return reject('already connected!'); - - /* istanbul ignore else */ - if (this.options.simulate || portName === k.OBCISimulatorPortName) { - this.options.simulate = true; - // If we are simulating, set portName to fake name - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('using faux board ' + portName); - this.serial = new openBCISimulator.OpenBCISimulator(this.portName, { - accel: this.options.simulatorHasAccelerometer, - alpha: this.options.simulatorInjectAlpha, - boardFailure: this.options.simulatorBoardFailure, - daisy: this.options.simulatorDaisyModuleAttached, - drift: this.options.simulatorInternalClockDrift, - firmwareVersion: this.options.simulatorFirmwareVersion, - fragmentation: this.options.simulatorFragmentation, - latencyTime: this.options.simulatorLatencyTime, - bufferSize: this.options.simulatorBufferSize, - lineNoise: this.options.simulatorInjectLineNoise, - sampleRate: this.options.simulatorSampleRate, - serialPortFailure: this.options.simulatorSerialPortFailure, - verbose: this.options.verbose - }); - } else { - this.portName = portName; - if (this.options.verbose) console.log('using real board ' + portName); - this.serial = new SerialPort(portName, { - baudRate: this.options.baudRate - }, (err) => { - if (err) reject(err); - }); - } - - if (this.options.verbose) console.log('Serial port connected'); - - this.serial.on('data', data => { - this._processBytes(data); - }); - this.serial.once('open', () => { - if (this.options.verbose) console.log('Serial port open'); - new Promise(resolve => { - // TODO: document why this 300 ms delay is needed - setTimeout(resolve, this.options.simulate ? 50 : 300); - }).then(() => { - if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); - return this.write(k.OBCIStreamStop); - }).then(() => { - return new Promise(resolve => this.serial.flush(resolve)); - }).then(() => { - // TODO: document why this 250 ms delay is needed - return new Promise(resolve => setTimeout(resolve, 250)); - }).then(() => { - if (this.options.verbose) console.log('Sending soft reset'); - // TODO: this promise chain resolves early because - // A. some legacy code (in tests) sets the ready handler after this resolves - // and - // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented - // which is C. not implemented yet except in a manner such that replies occur in the write handler, - // resulting in the EOT arriving before this resolves - // Fix one or more of the above 3 situations, then move resolve() to the next block. - resolve(); - return this.softReset(); - }).then(() => { - if (this.options.verbose) console.log("Waiting for '$$$'"); - }); - }); - this.serial.once('close', () => { - if (this.options.verbose) console.log('Serial Port Closed'); - // 'close' is emitted in _disconnected() - this._disconnected('port closed'); - }); - this.serial.once('error', (err) => { - if (this.options.verbose) console.log('Serial Port Error'); - this.emit('error', err); - this._disconnected(err); - }); - }); -}; - -/** -* @description Called once when for any reason the serial port is no longer open. -* @private -*/ -Cyton.prototype._disconnected = function (err) { - this._streaming = false; - - clearTimeout(this.writer); - this.writer = null; - - this.serial.removeAllListeners(); - this.serial = null; - - this.emit('close'); - - while (this.writeOutArray.length > 0) { - var command = this.writeOutArray.pop(); - if (command.reject) command.reject(err); - } -}; - -/** -* @description Closes the serial port. Waits for stop streaming command to -* be sent if currently streaming. -* @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.disconnect = function () { - return Promise.resolve() - .then(() => { - if (this.isStreaming()) { - if (this.options.verbose) console.log('stop streaming'); - return this.streamStop(); - } - }) - .then(() => { - if (!this.isConnected()) { - return Promise.reject('no board connected'); - } else { - return new Promise((resolve, reject) => { - // serial emitting 'close' will call _disconnected - this.serial.close(() => { - resolve(); - }); - }); - } - }); -}; - -/** -* @description Checks if the driver is connected to a board. -* @returns {boolean} - True if connected. -*/ -Cyton.prototype.isConnected = function () { - if (!this.serial) return false; - return this.serial.isOpen(); -}; - -/** -* @description Checks if the board is currently sending samples. -* @returns {boolean} - True if streaming. -*/ -Cyton.prototype.isStreaming = function () { - return this._streaming; -}; - -/** -* @description Sends a start streaming command to the board. -* @returns {Promise} indicating if the signal was able to be sent. -* Note: You must have successfully connected to an OpenBCI board using the connect -* method. Just because the signal was able to be sent to the board, does not -* mean the board will start streaming. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.streamStart = function () { - return new Promise((resolve, reject) => { - if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); - this._streaming = true; - this._reset_ABANDONED(); // framework is incomplete but looks useful - this.write(k.OBCIStreamStart).then(resolve, reject); - }); -}; - -/** -* @description Sends a stop streaming command to the board. -* @returns {Promise} indicating if the signal was able to be sent. -* Note: You must have successfully connected to an OpenBCI board using the connect -* method. Just because the signal was able to be sent to the board, does not -* mean the board stopped streaming. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.streamStop = function () { - return new Promise((resolve, reject) => { - if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); - this._streaming = false; - this.write(k.OBCIStreamStop).then(resolve, reject); - }); -}; - -/** -* @description To start simulating an open bci board -* Note: Must be called after the constructor -* @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.simulatorEnable = function () { - return new Promise((resolve, reject) => { - if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? - if (this.isConnected()) { - this.disconnect() // disconnect first - .then(() => { - this.options.simulate = true; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = true; - resolve(); - } - }); -}; - -/** -* @description To stop simulating an open bci board -* Note: Must be called after the constructor -* @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.simulatorDisable = function () { - return new Promise((resolve, reject) => { - if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? - if (this.isConnected()) { - this.disconnect() - .then(() => { - this.options.simulate = false; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = false; - resolve(); - } - }); -}; - -/** -* @description To be able to easily write to the board but ensure that we never send commands -* with less than a 10ms spacing between sends in early version boards. This uses -* an array and shifts off the entries until there are none left. -* @param dataToWrite - Either a single character or an Array of characters -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.write = function (dataToWrite) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) { - reject('not connected'); - } else { - if (Array.isArray(dataToWrite)) { // Got an input array - var len = dataToWrite.length; - for (var i = 0; i < len; i++) { - this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); - } - this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; - } else { - this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); - } - - if (!this.writer) { // there is no writer started - var writerFunction = () => { - if (this.writeOutArray.length === 0) { - this.writer = null; - return; - } - - var command = this.writeOutArray.shift(); - var promise = this._writeAndDrain(command.cmd); - - promise.then(() => { - this.writer = setTimeout(writerFunction, this.writeOutDelay); - }, () => { - // write failed but more commands may be pending that need a result - writerFunction(); - }); - - if (command.reject) { - promise.catch(err => { - if (this.options.verbose) console.log('write failure: ' + err); - command.reject(err); - }); - } - if (command.resolve) promise.then(command.resolve); - }; - this.writer = setTimeout(writerFunction, this.writeOutDelay); - } - } - }); -}; - -/** -* @description Should be used to send data to the board -* @param data {Buffer} - The data to write out -* @returns {Promise} if signal was able to be sent -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._writeAndDrain = function (data) { - if (this.options.debug) openBCIUtils.debugBytes('>>>', data); - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Serial port not open'); - this.serial.write(data, (error) => { - if (error) { - console.log('Error [writeAndDrain]: ' + error); - reject(error); - } else { - this.serial.drain(function () { - resolve(); - }); - } - }); - }); -}; - -/** -* @description Automatically find an OpenBCI board. -* Note: This method is used for convenience and should be used when trying to -* connect to a board. If you find a case (i.e. a platform (linux, -* windows...) that this does not work, please open an issue and -* we will add support! -* @returns {Promise} - Fulfilled with portName, rejected when can't find the board. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.autoFindOpenBCIBoard = function () { - var serialPatterns = [ - { // mac - comName: /usbserial-D/ - }, - { // linux - comName: /^\/dev\/ttyUSB/, - manufacturer: /^FTDI$/, - serialNumber: /^FTDI_FT231X_USB_UART/, - vendorId: /^0x0403$/, - productId: /^0x6015$/ - } - ]; - return new Promise((resolve, reject) => { - /* istanbul ignore else */ - if (this.options.simulate) { - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('auto found sim board'); - resolve(k.OBCISimulatorPortName); - } else { - SerialPort.list((err, ports) => { - if (err) { - if (this.options.verbose) console.log('serial port err'); - reject(err); - } - // This is one big if statement - if (ports.some(port => { - return serialPatterns.some(patterns => { - for (var attribute in patterns) { - if (!String(port[attribute]).match(patterns[attribute])) { - return false; - } - } - this.portName = port.comName; - return true; - }); - })) { - if (this.options.verbose) console.log('auto found board'); - resolve(this.portName); - } else { - if (this.options.verbose) console.log('could not find board'); - reject('Could not auto find board'); - } - }); - } - }); -}; - -/** -* @description Convenience method to determine if you can use firmware v2.x.x -* capabilities. -* @returns {boolean} - True if using firmware version 2 or greater. Should -* be called after a `.softReset()` because we can parse the output of that -* to determine if we are using firmware version 2. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.usingVersionTwoFirmware = function () { - if (this.options.simulate) { - return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; - } else { - return this.info.firmware === k.OBCIFirmwareV2; - } -}; - -/** -* @description Used to set the system radio channel number. The function will reject if not -* connected to the serial port of the dongle. Further the function should reject if currently streaming. -* Lastly and more important, if the board is not running the new firmware then this functionality does not -* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. -* @since 1.0.0 -* @returns {Promise} - Resolves with the new channel number, rejects with err. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioChannelSet = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); - }); -}; - -/** -* @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if -* your dongle and board are not on the right channel and bring down your radio system if you take your -* dongle and board are not on the same channel. Use with caution! The function will reject if not -* connected to the serial port of the dongle. Further the function should reject if currently streaming. -* Lastly and more important, if the board is not running the new firmware then this functionality does not -* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @param `channelNumber` {Number} - The channel number you want to set to, 1-25. -* @since 1.0.0 -* @returns {Promise} - Resolves with the new channel number, rejects with err. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioChannelSetHostOverride = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(`${data.toString()}`); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); - }); -}; - -/** -* @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not -* connected to the serial port of the dongle. Further the function should reject if currently streaming. -* Lastly and more important, if the board is not running the new firmware then this functionality does not -* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve -* an Object. See `returns` below. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.0.0 -* @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in -* the condition that there system is experiencing board communications failure. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioChannelGet = function () { - // The function to run on timeout - var badCommsTimeout; - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 500); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - if (openBCISample.isSuccessInBuffer(data)) { - resolve({ - channelNumber: data[data.length - 4], - data: data - }); - } else { - reject(`Error [radioChannelGet]: ${data.toString()}`); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); - }); -}; - -/** -* @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not -* connected to the serial port of the dongle. Further the function should reject if currently streaming. -* Lastly and more important, if the board is not running the new firmware then this functionality does not -* exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve -* the poll time when fulfilled. It's important to note that if the board is not on, this function will always -* be rejected with a failure message. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.0.0 -* @returns {Promise} - Resolves with the poll time, rejects with an error message. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioPollTimeGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - var pollTime = data[data.length - 4]; - resolve(pollTime); - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); - }); -}; - -/** -* @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the -* Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this -* sets the interval at which the Device polls the Host for new information. Further the function should reject -* if currently streaming. Lastly and more important, if the board is not running the new firmware then this -* functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this -* function should resolve. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 -* @since 1.0.0 -* @returns {Promise} - Resolves with new poll time, rejects with error message. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioPollTimeSet = function (pollTime) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); - if (!k.isNumber(pollTime)) return reject('Must input type Number'); - if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); - if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); // Ditch the eot $$$ - } else { - reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); - }); -}; - -/** -* @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the -* Host and the Board is the Device. Only the Device can initiate a communication between the two entities. -* There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then -* all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial -* data is sent from the Host to the Serial driver. The rate can either be set to default or fast. -* Further the function should reject if currently streaming. Lastly and more important, if the board is not -* running the new firmware then this functionality does not exist and thus this method will reject. -* If the board is using firmware 2+ then this function should resolve the new baud rate after closing the -* current serial port and reopening one. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.0.0 -* @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) -* @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioBaudRateSet = function (speed) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (!k.isString(speed)) return reject('Must input type String'); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - var eotBuf = new Buffer('$$$'); - var newBaudRateBuf; - for (var i = data.length; i > 3; i--) { - if (bufferEqual(data.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = data.slice(i - 9, i - 3); - break; - } - } - var newBaudRateNum = Number(newBaudRateBuf.toString()); - if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { - return reject('Error parse mismatch, restart your system!'); - } - if (!this.isConnected()) { - reject('Lost connection to device during baud set'); - } else if (openBCISample.isSuccessInBuffer(data)) { - // Change the sample rate here - if (this.options.simulate === false) { - this.serial.update({baudRate: newBaudRateNum}, err => { - if (err) return reject(err); - else resolve(newBaudRateNum); - }); - } else { - resolve(newBaudRateNum); - } - } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - if (speed === k.OBCIRadioBaudRateFastStr) { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); - } else { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); - } - }); -}; - -/** -* @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are -* in fact ready to start trying to connect and such. The function will reject if not connected to the serial -* port of the dongle. Further the function should reject if currently streaming. -* Lastly and more important, if the board is not running the new firmware then this functionality does not -* exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the -* same channel and powered, then this will resolve true. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.0.0 -* @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.radioSystemStatusGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (this.options.verbose) console.log(data.toString()); - - if (openBCISample.isSuccessInBuffer(data)) { - resolve(true); - } else { - resolve(false); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); - }); -}; - -/** -* @description List available ports so the user can choose a device when not -* automatically found. -* Note: This method is used for convenience essentially just wrapping up -* serial port. -* @author Andy Heusser (@andyh616) -* @returns {Promise} - On fulfill will contain an array of Serial ports to use. -*/ -Cyton.prototype.listPorts = function () { - return new Promise((resolve, reject) => { - SerialPort.list((err, ports) => { - if (err) reject(err); - else { - ports.push({ - comName: k.OBCISimulatorPortName, - manufacturer: '', - serialNumber: '', - pnpId: '', - locationId: '', - vendorId: '', - productId: '' - }); - resolve(ports); - } - }); - }); -}; - -/** -* @description Sends a soft reset command to the board -* @returns {Promise} -* Note: The softReset command MUST be sent to the board before you can start -* streaming. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.softReset = function () { - this.curParsingMode = k.OBCIParsingReset; - return this.write(k.OBCIMiscSoftReset); -}; - -// /** -// * @description To get the specified channelSettings register data from printRegisterSettings call -// * @param channelNumber - a number -// * @returns {Promise.|*} -// * @author AJ Keller (@pushtheworldllc) -// */ -// // TODO: REDO THIS FUNCTION -// Cyton.prototype.getSettingsForChannel = function (channelNumber) { -// return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { -// // this.searchingBuf = newSearchingBuffer -// return this.printRegisterSettings(); -// }); -// }; - -/** -* @description To print out the register settings to the console -* @returns {Promise.|*} -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.printRegisterSettings = function () { - return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { - this.curParsingMode = k.OBCIParsingEOT; - }); -}; - -/** -* @description Send a command to the board to turn a specified channel off -* @param channelNumber -* @returns {Promise.} -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.channelOff = function (channelNumber) { - return k.commandChannelOff(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); -}; - -/** -* @description Send a command to the board to turn a specified channel on -* @param channelNumber -* @returns {Promise.|*} -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.channelOn = function (channelNumber) { - return k.commandChannelOn(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); -}; - -/** -* @description To send a channel setting command to the board -* @param channelNumber - Number (1-16) -* @param powerDown - Bool (true -> OFF, false -> ON (default)) -* turns the channel on or off -* @param gain - Number (1,2,4,6,8,12,24(default)) -* sets the gain for the channel -* @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) -* selects the ADC channel input source -* @param bias - Bool (true -> Include in bias (default), false -> remove from bias) -* selects to include the channel input in bias generation -* @param srb2 - Bool (true -> Connect this input to SRB2 (default), -* false -> Disconnect this input from SRB2) -* Select to connect (true) this channel's P input to the SRB2 pin. This closes -* a switch between P input and SRB2 for the given channel, and allows the -* P input to also remain connected to the ADC. -* @param srb1 - Bool (true -> connect all N inputs to SRB1, -* false -> Disconnect all N inputs from SRB1 (default)) -* Select to connect (true) all channels' N inputs to SRB1. This effects all pins, -* and disconnects all N inputs from the ADC. -* @returns {Promise} resolves if sent, rejects on bad input or no board -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { - var arrayOfCommands = []; - return new Promise((resolve, reject) => { - k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) - .then((val) => { - arrayOfCommands = val.commandArray; - this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; - return this.write(arrayOfCommands); - }).then(resolve, reject); - }); -}; - -/** -* @description Apply the internal test signal to all channels -* @param signal - A string indicating which test signal to apply -* - `dc` -* - Connect to DC signal -* - `ground` -* - Connect to internal GND (VDD - VSS) -* - `pulse1xFast` -* - Connect to test signal 1x Amplitude, fast pulse -* - `pulse1xSlow` -* - Connect to test signal 1x Amplitude, slow pulse -* - `pulse2xFast` -* - Connect to test signal 2x Amplitude, fast pulse -* - `pulse2xSlow` -* - Connect to test signal 2x Amplitude, slow pulse -* - `none` -* - Reset to default -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.testSignal = function (signal) { - return new Promise((resolve, reject) => { - k.getTestSignalCommand(signal) - .then(command => { - return this.write(command); - }) - .then(() => resolve()) - .catch(err => reject(err)); - }); -}; - -/** -* @description - Sends command to turn on impedances for all channels and continuously calculate their impedances -* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestContinuousStart = function () { - return new Promise((resolve, reject) => { - if (this.impedanceTest.active) return reject('Error: test already active'); - if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); - - this.impedanceTest.active = true; - this.impedanceTest.continuousMode = true; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, true)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); -}; - -/** -* @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances -* @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestContinuousStop = function () { - return new Promise((resolve, reject) => { - if (!this.impedanceTest.active) return reject('Error: no test active'); - if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); - - this.impedanceTest.active = false; - this.impedanceTest.continuousMode = false; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, false)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); -}; - -/** -* @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a -* little while to actually run (<8 seconds)! -* @returns {Promise} - Resovles when complete testing all the channels. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestAllChannels = function () { - var upperLimit = k.OBCINumberOfChannelsDefault; - - /* istanbul ignore if */ - if (this.options.daisy) { - upperLimit = k.OBCINumberOfChannelsDaisy; - } - - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > upperLimit) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - this.impedanceTestChannel(channelNumber) - .then(() => { - resolve(completeChannelImpedanceTest(channelNumber + 1)); - /* istanbul ignore next */ - }).catch(err => reject(err)); - } - }); - }; - - return completeChannelImpedanceTest(1); -}; - -/** -* @description To test specific input configurations of channels! -* @param arrayOfChannels - The array of configurations where: -* 'p' or 'P' is only test P input -* 'n' or 'N' is only test N input -* 'b' or 'B' is test both inputs (takes 66% longer to run) -* '-' to ignore channel -* EXAMPLE: -* For 8 channel board: ['-','N','n','p','P','-','b','b'] -* (Note: it doesn't matter if capitalized or not) -* @returns {Promise} - Fulfilled with a loaded impedance object. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestChannels = function (arrayOfChannels) { - if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); - // Check proper length of array - if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > arrayOfChannels.length) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - - var testCommand = arrayOfChannels[channelNumber - 1]; - - if (testCommand === 'p' || testCommand === 'P') { - this.impedanceTestChannelInputP(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'n' || testCommand === 'N') { - this.impedanceTestChannelInputN(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'b' || testCommand === 'B') { - this.impedanceTestChannel(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else { // skip ('-') condition - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - } - } - }); - }; - return completeChannelImpedanceTest(1); -}; - -/** -* @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. -* @param channelNumber - A Number, specifies which channel you want to test. -* @returns {Promise} - Fulfilled with a single channel impedance object. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestChannel = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. - }) - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/** -* @description Run impedance test on a single channel, applying the test signal only to P input. -* @param channelNumber - A Number, specifies which channel you want to test. -* @returns {Promise} - Fulfilled with a single channel impedance object. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestChannelInputP = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/** -* @description Run impedance test on a single channel, applying the test signal to N input. -* @param channelNumber - A Number, specifies which channel you want to test. -* @returns {Promise} - Fulfilled with a single channel impedance object. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.impedanceTestChannelInputN = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = openBCISample.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/* istanbul ignore next */ -/** -* @description To apply the impedance test signal to an input for any given channel -* @param channelNumber - Number - The channel you want to test. -* @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. -* @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. -* @returns {Promise} - With Number value of channel number -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected'); - - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tSending command to apply test signal to P input.'); - } else if (!pInput && nInput) { - console.log('\tSending command to apply test signal to N input.'); - } else if (pInput && nInput) { - console.log('\tSending command to apply test signal to P and N inputs.'); - } else { - console.log('\tSending command to stop applying test signal to both P and N inputs.'); - } - } - - if (!pInput && !nInput) { - this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort - } else { - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong - } - if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); - // Get impedance settings to send the board - k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { - return this.write(commandsArray); - }).then(() => { - /** - * If either pInput or nInput are true then we should start calculating impedance. Setting - * this.impedanceTest.active to true here allows us to route every sample for an impedance - * calculation instead of the normal sample output. - */ - if (pInput || nInput) this.impedanceTest.active = true; - resolve(channelNumber); - }, (err) => { - reject(err); - }); - }); -}; - -/** -* @description Calculates the impedance for a specified channel for a set time -* @param channelNumber - A Number, the channel number you want to test. -* @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. -* @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. -* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tCalculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tCalculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tCalculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - this.impedanceTest.onChannel = channelNumber; - this.impedanceTest.sampleNumber = 0; // Reset the sample number - this.impedanceTest.isTestingPInput = pInput; - this.impedanceTest.isTestingNInput = nInput; - // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) - // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) - setTimeout(() => { // Calculate for 250ms - this.impedanceTest.onChannel = 0; - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tDone calculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tDone calculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tDone calculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; - if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; - resolve(channelNumber); - }, 400); - }); -}; - -/** -* @description Calculates average and gets textual value of impedance for a specified channel -* @param channelNumber - A Number, the channel number you want to finalize. -* @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. -* @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. -* @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tFinalizing impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tFinalizing impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tFinalizing impedance for P and N input.'); - } else { - console.log('\tNot Finalizing impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); - - if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); - if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); - - setTimeout(() => { - resolve(channelNumber); - }, 50); // Introduce a delay to allow for extra time in case of back to back tests - }); -}; - -/** -* @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request -* response from the board. -* @param recordingDuration {String} - The duration you want to log SD information for. Limited to: -* '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' -* @returns {Promise} - Resolves when the command has been written. -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.sdStart = function (recordingDuration) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - k.sdSettingForString(recordingDuration) - .then(command => { - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(command).then(resolve, reject); - }) - .catch(err => reject(err)); - }); -}; - -/** -* @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted -* with request response from the board. -* @returns {Promise} - Resolves when written -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.sdStop = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(k.OBCISDLogStop).then(resolve, reject); - }); -}; - -/** -* @description Get the the current sample rate is. -* @returns {Number} The sample rate -* Note: This is dependent on if you configured the board correctly on setup options -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.sampleRate = function () { - if (this.options.simulate) { - return this.options.simulatorSampleRate; - } else { - if (this.info) { - return this.info.sampleRate; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCISampleRate125; - case k.OBCIBoardDefault: - default: - return k.OBCISampleRate250; - } - } - } -}; - -/** -* @description This function is used as a convenience method to determine how many -* channels the current board is using. -* @returns {Number} A number -* Note: This is dependent on if you configured the board correctly on setup options -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.numberOfChannels = function () { - if (this.info) { - return this.info.numberOfChannels; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCINumberOfChannelsDaisy; - case k.OBCIBoardDefault: - default: - return k.OBCINumberOfChannelsDefault; - } - } -}; - -/** -* @description Send the command to tell the board to start the syncing protocol. Must be connected, -* streaming and using at least version 2.0.0 firmware. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.0.0 -* @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.syncClocks = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); - }); -}; - -/** -* @description Send the command to tell the board to start the syncing protocol. Must be connected, -* streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs -* don't overlap. -* **Note**: This functionality requires OpenBCI Firmware Version 2.0 -* @since 1.1.0 -* @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.syncClocksFull = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); - var timeout = setTimeout(() => { - return reject('syncClocksFull timeout after 500ms with no sync'); - }, 500); // Should not take more than 1s to sync up - this.sync.eventEmitter = syncObj => { - clearTimeout(timeout); - return resolve(syncObj); - }; - this.once('synced', this.sync.eventEmitter); - this.sync.curSyncObj = openBCISample.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet) - .catch(err => { - clearTimeout(timeout); - return reject(err); - }); - }); -}; - -/** -* @description Consider the '_processBytes' method to be the work horse of this -* entire framework. This method gets called any time there is new -* data coming in on the serial port. If you are familiar with the -* 'serialport' package, then every time data is emitted, this function -* gets sent the input data. The data comes in very fragmented, sometimes -* we get half of a packet, and sometimes we get 3 and 3/4 packets, so -* we will need to store what we don't read for next time. -* @param data - a buffer of unknown size -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processBytes = function (data) { - if (this.options.debug) openBCIUtils.debugBytes(this.curParsingMode + '<<', data); - - // Concat old buffer - var oldDataBuffer = null; - if (this.buffer) { - oldDataBuffer = this.buffer; - data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); - } - - switch (this.curParsingMode) { - case k.OBCIParsingEOT: - if (openBCISample.doesBufferHaveEOT(data)) { - this.curParsingMode = k.OBCIParsingNormal; - this.emit('eot', data); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingReset: - // Does the buffer have an EOT in it? - if (openBCISample.doesBufferHaveEOT(data)) { - this._processParseBufferForReset(data); - this.curParsingMode = k.OBCIParsingNormal; - this.emit('ready'); - this.buffer = openBCISample.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingTimeSyncSent: - // If there is only one match - if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) { - if (this.options.verbose) console.log(`Found Time Sync Sent`); - this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); - this.curParsingMode = k.OBCIParsingNormal; - } - this.buffer = this._processDataBuffer(data); - break; - case k.OBCIParsingNormal: - default: - this.buffer = this._processDataBuffer(data); - break; - } - - if (this.buffer && oldDataBuffer) { - if (bufferEqual(this.buffer, oldDataBuffer)) { - this.buffer = null; - } - } -}; - -/** -* @description Used to extract samples out of a buffer of unknown length -* @param dataBuffer {Buffer} - A buffer to parse for samples -* @returns {Buffer} - Any data that was not pulled out of the buffer -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processDataBuffer = function (dataBuffer) { - if (!dataBuffer) return null; - var bytesToParse = dataBuffer.length; - // Exit if we have a buffer with less data than a packet - if (bytesToParse < k.OBCIPacketSize) return dataBuffer; - - var parsePosition = 0; - // Begin parseing - while (parsePosition <= bytesToParse - k.OBCIPacketSize) { - // Is the current byte a head byte that looks like 0xA0 - if (dataBuffer[parsePosition] === k.OBCIByteStart) { - // Now that we know the first is a head byte, let's see if the last one is a - // tail byte 0xCx where x is the set of numbers from 0-F (hex) - if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) { - /** We just qualified a raw packet */ - // This could be a time set packet! - this.timeOfPacketArrival = this.time(); - // Grab the raw packet, make a copy of it. - var rawPacket; - if (k.getVersionNumber(process.version) >= 6) { - // From introduced in node version 6.x.x - rawPacket = Buffer.from(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } else { - rawPacket = new Buffer(dataBuffer.slice(parsePosition, parsePosition + k.OBCIPacketSize)); - } - - // Emit that buffer - this.emit('rawDataPacket', rawPacket); - // Submit the packet for processing - this._processQualifiedPacket(rawPacket); - // Overwrite the dataBuffer with a new buffer - var tempBuf; - if (parsePosition > 0) { - tempBuf = Buffer.concat([dataBuffer.slice(0, parsePosition), dataBuffer.slice(parsePosition + k.OBCIPacketSize)], dataBuffer.byteLength - k.OBCIPacketSize); - } else { - tempBuf = dataBuffer.slice(k.OBCIPacketSize); - } - if (tempBuf.length === 0) { - dataBuffer = null; - } else { - if (k.getVersionNumber(process.version) >= 6) { - dataBuffer = Buffer.from(tempBuf); - } else { - dataBuffer = new Buffer(tempBuf); - } - } - // Move the parse position up one packet - parsePosition = -1; - bytesToParse -= k.OBCIPacketSize; - } - } - parsePosition++; - } - - return dataBuffer; -}; - -/** - * @description Alters the global info object by parseing an incoming soft reset key - * @param dataBuffer {Buffer} - The soft reset data buffer - * @returns {Buffer} - If there is data left in the buffer, just it will be returned. - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processParseBufferForReset = function (dataBuffer) { - if (openBCISample.countADSPresent(dataBuffer) === 2) { - this.info.boardType = k.OBCIBoardDaisy; - this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; - this.info.sampleRate = k.OBCISampleRate125; - } else { - this.info.boardType = k.OBCIBoardDefault; - this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; - this.info.sampleRate = k.OBCISampleRate250; - } - - if (openBCISample.findV2Firmware(dataBuffer)) { - this.info.firmware = k.OBCIFirmwareV2; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - } else { - this.info.firmware = k.OBCIFirmwareV1; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - } -}; - -/** -* @description Used to route qualified packets to their proper parsers -* @param rawDataPacketBuffer -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { - if (!rawDataPacketBuffer) return; - if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; - var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); - if (missedPacketArray) { - this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); - } - this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; - var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); - switch (packetType) { - case k.OBCIStreamPacketStandardAccel: - this._processPacketStandardAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketStandardRawAux: - this._processPacketStandardRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketUserDefinedType: - // Do nothing for User Defined Packets - break; - case k.OBCIStreamPacketAccelTimeSyncSet: - // Don't waste any time! - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketAccelTimeSynced: - this._processPacketTimeSyncedAccel(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSyncSet: - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - case k.OBCIStreamPacketRawAuxTimeSynced: - this._processPacketTimeSyncedRawAux(rawDataPacketBuffer); - break; - default: - // Don't do anything if the packet is not defined - break; - } -}; - -/** -* @description A method used to compute impedances. -* @param sampleObject - A sample object that follows the normal standards. -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processImpedanceTest = function (sampleObject) { - var impedanceArray; - if (this.impedanceTest.continuousMode) { - // console.log('running in continuous mode...') - // openBCIUtilities.debugPrettyPrint(sampleObject) - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.emit('impedanceArray', impedanceArray); - } - } else if (this.impedanceTest.onChannel !== 0) { - // Only calculate impedance for one channel - impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject); - if (impedanceArray) { - this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; - } - } -}; - -/** -* @description A method to parse a stream packet that has channel data and data in the aux channels that contains accel data. -* @param rawPacket - A 33byte data buffer from _processQualifiedPacket -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processPacketStandardAccel = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, true); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - -/** -* @description A method to parse a stream packet that has channel data and data in the aux channels that should not be scaled. -* @param rawPacket - A 33byte data buffer from _processQualifiedPacket -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processPacketStandardRawAux = function (rawPacket) { - try { - let sample = openBCISample.parseRawPacketStandard(rawPacket, this.channelSettingsArray, false); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - -/** -* @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp -* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket -* @param timeOfPacketArrival {Number} - The time the packet arrived. -* @private -* @returns {Object} - A time sync object. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { - /** - * Helper function for creating a bad sync object - * @param err {object} - Can be any object - * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} - */ - var getBadObject = (err) => { - var badObject = openBCISample.newSyncObject(); - badObject.timeOffsetMaster = this.sync.timeOffsetMaster; - // Create an error - badObject.error = err; - return badObject; - }; - - var syncObj = {}; - - if (this.sync.curSyncObj === null) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); - // Missed comma - } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); - } else { - try { - this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; - if (this.options.verbose) console.log('Got time set packet from the board'); - let boardTime = openBCISample.getFromTimePacketTime(rawPacket); - this.sync.curSyncObj.boardTime = boardTime; - // if (this.options.verbose) { - // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) - // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) - // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) - // } - - // Calculate the time between sending the `<` to getting the set packet, call this the round trip length - this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; - if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); - - // If the sync sent conf and set packet arrive in different serial flushes - // ------------------------------------------ - // | | timeTransmission | < GOOD :) - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - // - // Assume it's good... - this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); - - // If the conf and the set packet arrive in the same serial flush we have big problem! - // ------------------------------------------ - // | | | < BAD :( - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { - // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point - this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); - if (this.options.verbose) console.log(`Had to correct transmission time`); - this.sync.curSyncObj.correctedTransmissionTime = true; - } - - // Calculate the offset #finally - this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; - if (this.options.verbose) { - console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); - console.log(`Board time: ${boardTime}`); - } - - // Add to array - if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { - // Shift the oldest one out of the array - this.sync.timeOffsetArray.shift(); - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } else { - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } - - // Calculate the master time offset that we use averaging to compute - if (this.sync.timeOffsetArray.length > 1) { - var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); - this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); - } else { - this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; - } - - this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; - - if (this.options.verbose) { - console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); - } - - // Set the valid object to true - this.sync.curSyncObj.valid = true; - - // Make a byte by byte copy of event - syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); - - // Save obj to the global array - this.sync.objArray.push(syncObj); - } catch (err) { - // Log if verbose. - if (this.options.verbose) console.log(err.message); - syncObj = getBadObject(err); - } - } - // Fix the curParsingMode back to normal - this.curParsingMode = k.OBCIParsingNormal; - // Emit synced object - this.emit(k.OBCIEmitterSynced, syncObj); - // Set global to null - this.sync.curSyncObj = null; - // Return the object - return syncObj; -}; - -/** -* @description A method to parse a stream packet that contains channel data, a time stamp and event couple packets -* an accelerometer value. -* @param rawPacket - A 33byte data buffer from _processQualifiedPacket -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processPacketTimeSyncedAccel = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - -/** -* @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that -* shall be emitted as a raw buffer and not scaled. -* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._processPacketTimeSyncedRawAux = function (rawPacket) { - // if (this.sync.active === false) console.log('Need to sync with board...') - try { - let sample = openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster); - sample.rawPacket = rawPacket; - this._finalizeNewSample(sample); - } catch (err) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - if (this.options.verbose) console.log(err); - } -}; - -/** -* @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are -* being tested. -* @param sampleObject {Object} - A sample object that follows the normal standards. -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._finalizeNewSample = function (sampleObject) { - sampleObject._count = this.sampleCount++; - if (this.impedanceTest.active) { - this._processImpedanceTest(sampleObject); - } else { - // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper - // channels (9-16) come in packets with even sample numbers - if (this.info.boardType === k.OBCIBoardDaisy) { - // Send the sample for downstream sample compaction - this._finalizeNewSampleForDaisy(sampleObject); - } else { - this.emit(k.OBCIEmitterSample, sampleObject); - } - } -}; - -/** -* @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber -* sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a -* sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be -* emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the -* missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. -* @param sampleObject {Object} - The sample object to finalize -* @private -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { - if (openBCISample.isOdd(sampleObject.sampleNumber)) { - // Check for the skipped packet condition - if (this._lowerChannelsSampleObject) { - // The last packet was odd... missed the even packet - this.info.missedPackets++; - } - this._lowerChannelsSampleObject = sampleObject; - } else { - // Make sure there is an odd packet waiting to get merged with this packet - if (this._lowerChannelsSampleObject) { - // Merge these two samples - var mergedSample = openBCISample.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); - // Set the _lowerChannelsSampleObject object to null - this._lowerChannelsSampleObject = null; - // Emite the new merged sample - this.emit('sample', mergedSample); - } else { - // Missed the odd packet, i.e. two evens in a row - this.info.missedPackets++; - } - } -}; - -/** -* @description Reset the master buffer and reset the number of bad packets. -* @author AJ Keller (@pushtheworldllc) -*/ -// TODO: nothing is using these constructs, but they look like good constructs. See contents of masterBufferMaker() -Cyton.prototype._reset_ABANDONED = function () { - this.masterBuffer = masterBufferMaker(); - this.badPackets = 0; -}; - -/** -* @description Stateful method for querying the current offset only when the last -* one is too old. (defaults to daily) -* @returns {Promise} A promise with the time offset -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.sntpGetOffset = function () { - this.options.sntpTimeSync = true; - - if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); - - return new Promise((resolve, reject) => { - Sntp.offset({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, function (err, offset) { - if (err) reject(err); - else resolve(offset); - }); - }); -}; - -/** -* @description Allows users to utilize all features of sntp if they want to... -*/ -Cyton.prototype.sntp = Sntp; - -/** -* @description This gets the time plus offset -* @private -*/ -Cyton.prototype._sntpNow = Sntp.now; - -/** -* @description This starts the SNTP server and gets it to remain in sync with the SNTP server -* @returns {Promise} - A promise if the module was able to sync with ntp server. -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.sntpStart = function (options) { - this.options.sntpTimeSync = true; - - // Sntp.start doesn't actually report errors (2016-10-29) - // so functionality is first detected via sntpGetOffset - return this.sntpGetOffset().then(() => { - return new Promise((resolve, reject) => { - Sntp.start({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, () => { - this.sync.sntpActive = true; - this.emit('sntpTimeLock'); - resolve(); - }); - }); - }); -}; - -/** -* @description Stops the sntp from updating. -*/ -Cyton.prototype.sntpStop = function () { - Sntp.stop(); - this.options.sntpTimeSync = false; - this.sync.sntpActive = false; -}; - -/** -* @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time -* @returns {Number} - The time -* @author AJ Keller (@pushtheworldllc) -*/ -Cyton.prototype.time = function () { - if (this.options.sntpTimeSync) { - return this._sntpNow(); - } else { - return Date.now(); - } -}; - -/** -* @description This prints the total number of packets that were not able to be read -* @author AJ Keller (@pushtheworldllc) -*/ -/* istanbul ignore next */ -Cyton.prototype.printPacketsBad = function () { - if (this.badPackets > 1) { - console.log('Dropped a total of ' + this.badPackets + ' packets.'); - } else if (this.badPackets === 1) { - console.log('Dropped a total of 1 packet.'); - } else { - console.log('No packets dropped.'); - } -}; - -/** -* @description This prints the total bytes in -* @author AJ Keller (@pushtheworldllc) -*/ -/* istanbul ignore next */ -Cyton.prototype.printBytesIn = function () { - if (this.bytesIn > 1) { - console.log('Read in ' + this.bytesIn + ' bytes.'); - } else if (this.bytesIn === 1) { - console.log('Read one 1 packet in.'); - } else { - console.log('Read no packets.'); - } -}; - -/** -* @description This prints the total number of packets that have been read -* @author AJ Keller (@pushtheworldllc) -*/ -/* istanbul ignore next */ -Cyton.prototype.printPacketsRead = function () { - if (this.masterBuffer.packetsRead > 1) { - console.log('Read ' + this.masterBuffer.packetsRead + ' packets.'); - } else if (this.masterBuffer.packetsIn === 1) { - console.log('Read 1 packet.'); - } else { - console.log('No packets read.'); - } -}; - -/** -* @description Nice convenience method to print some session details -* @author AJ Keller (@pushtheworldllc) -*/ -/* istanbul ignore next */ -Cyton.prototype.debugSession = function () { - this.printBytesIn(); - this.printPacketsRead(); - this.printPacketsBad(); -}; - -/** -* @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) -* @param channelSettingsObj -*/ -/* istanbul ignore next */ -Cyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { - console.log('-- Channel Settings Object --'); - var powerState = 'OFF'; - if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { - powerState = 'ON'; - } - console.log('---- POWER STATE: ' + powerState); - console.log('-- END --'); -}; - -/** -* @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. -* @param channelSettingsObject -* @returns {boolean} -*/ -Cyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { - return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; -}; - -util.inherits(Cyton, EventEmitter); - -module.exports = Cyton; - -/** -* @description To parse a given channel given output from a print registers query -* @param rawChannelBuffer -* @example would be 'CH1SET 0x05, 0xFF, 1, 0, 0, 0, 0, 1, 0 -* @returns {Promise} -* @author AJ Keller (@pushtheworldllc) -*/ -// function getChannelSettingsObj (rawChannelBuffer) { -// return new Promise(function (resolve, reject) { -// if (rawChannelBuffer === undefined || rawChannelBuffer === null) { -// reject('Undefined or null channel buffer'); -// } -// -// var channelSettingsObject = { -// CHANNEL: '0', -// POWER_DOWN: '0', -// GAIN_SET: '0', -// INPUT_TYPE_SET: '0', -// BIAS_SET: '0', -// SRB2_SET: '0', -// SRB1_SET: '0' -// }; -// -// var bitsToSkip = 20; // CH1SET, 0x05, 0xE0 --> 20 bits -// var sizeOfData = rawChannelBuffer.byteLength; -// -// var objIndex = 0; -// for (var j = bitsToSkip; j < sizeOfData - 1; j += 3) { // every three bytes there is data -// switch (objIndex) { -// case 0: -// channelSettingsObject.POWER_DOWN = rawChannelBuffer.slice(j, j + 1).toString(); -// break; -// default: -// break; -// } -// -// objIndex++; -// } -// resolve(channelSettingsObject); -// }); -// } - -function masterBufferMaker () { - var masterBuf = new Buffer(k.OBCIMasterBufferSize); - masterBuf.fill(0); - return { // Buffer used to store bytes in and read packets from - buffer: masterBuf, - positionRead: 0, - positionWrite: 0, - packetsIn: 0, - packetsRead: 0, - looseBytes: 0 - }; -} From e76a5cd00fa1e4d634a8ce637664ba9da68e0584 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 13:26:03 -0400 Subject: [PATCH 21/42] Saving --- changelog.md | 1 + openBCICyton.js | 173 ++++++++++++++-------------- test/openBCICyton-Impedance-test.js | 1 + test/openBCICyton-radio-test.js | 13 ++- test/openBCICyton-test.js | 1 + 5 files changed, 98 insertions(+), 91 deletions(-) diff --git a/changelog.md b/changelog.md index 0c9a8b5..67d2e5d 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ ``` * Major change to how board is initialized with removal of `factory` paradigm. * New dependency called `openbci-utilities`. +* Rejections are errors with messages, so check err.message for info on message, don't expect string. ### Bug Fixes * Documentation error with `testSignal` function. diff --git a/openBCICyton.js b/openBCICyton.js index 1757a29..93b7c49 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -12,6 +12,7 @@ const Sntp = require('sntp'); const bufferEqual = require('buffer-equal'); const math = require('mathjs'); const _ = require('lodash'); +const Buffer = require('safe-buffer').Buffer; /** * @typedef {Object} InitializationObject Board optional configurations. @@ -137,7 +138,7 @@ function OpenBCICyton (options) { if (!(this instanceof OpenBCICyton)) { return new OpenBCICyton(options); } - options = (typeof options !== 'function') && options || {}; + options = options || {}; var opts = {}; stream.Stream.call(this); @@ -261,7 +262,7 @@ util.inherits(OpenBCICyton, stream.Stream); */ OpenBCICyton.prototype.connect = function (portName) { return new Promise((resolve, reject) => { - if (this.isConnected()) return reject('already connected!'); + if (this.isConnected()) return reject(Error('already connected!')); /* istanbul ignore else */ if (this.options.simulate || portName === k.OBCISimulatorPortName) { @@ -380,7 +381,7 @@ OpenBCICyton.prototype.disconnect = function () { }) .then(() => { if (!this.isConnected()) { - return Promise.reject('no board connected'); + return Promise.reject(Error('no board connected')); } else { return new Promise((resolve, reject) => { // serial emitting 'close' will call _disconnected @@ -427,7 +428,7 @@ OpenBCICyton.prototype.isStreaming = function () { */ OpenBCICyton.prototype.streamStart = function () { return new Promise((resolve, reject) => { - if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming'); + if (this.isStreaming()) return reject(Error('Error [.streamStart()]: Already streaming')); this._streaming = true; this.write(k.OBCIStreamStart).then(resolve, reject); }); @@ -443,7 +444,7 @@ OpenBCICyton.prototype.streamStart = function () { */ OpenBCICyton.prototype.streamStop = function () { return new Promise((resolve, reject) => { - if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop'); + if (!this.isStreaming()) return reject(Error('Error [.streamStop()]: No stream to stop')); this._streaming = false; this.write(k.OBCIStreamStop).then(resolve, reject); }); @@ -457,7 +458,7 @@ OpenBCICyton.prototype.streamStop = function () { */ OpenBCICyton.prototype.simulatorEnable = function () { return new Promise((resolve, reject) => { - if (this.options.simulate) return reject('Already simulating'); // Are we already in simulate mode? + if (this.options.simulate) return reject(Error('Already simulating')); // Are we already in simulate mode? if (this.isConnected()) { this.disconnect() // disconnect first .then(() => { @@ -480,7 +481,7 @@ OpenBCICyton.prototype.simulatorEnable = function () { */ OpenBCICyton.prototype.simulatorDisable = function () { return new Promise((resolve, reject) => { - if (!this.options.simulate) return reject('Not simulating'); // Are we already not in simulate mode? + if (!this.options.simulate) return reject(Error('Not simulating')); // Are we already not in simulate mode? if (this.isConnected()) { this.disconnect() .then(() => { @@ -506,7 +507,7 @@ OpenBCICyton.prototype.simulatorDisable = function () { OpenBCICyton.prototype.write = function (dataToWrite) { return new Promise((resolve, reject) => { if (!this.isConnected()) { - reject('not connected'); + reject(Error('not connected')); } else { if (Array.isArray(dataToWrite)) { // Got an input array var len = dataToWrite.length; @@ -559,7 +560,7 @@ OpenBCICyton.prototype._writeAndDrain = function (data) { obciDebug.debugBytes('>>>', data); return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Serial port not open'); + if (!this.isConnected()) return reject(Error('Serial port not open')); this.serial.write(data, (error) => { if (error) { console.log('Error [writeAndDrain]: ' + error); @@ -623,7 +624,7 @@ OpenBCICyton.prototype.autoFindOpenBCIBoard = function () { resolve(this.portName); } else { if (this.options.verbose) console.log('could not find board'); - reject('Could not auto find board'); + reject(Error('Could not auto find board')); } }); } @@ -660,18 +661,18 @@ OpenBCICyton.prototype.usingVersionTwoFirmware = function () { OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error('Don\'t query for the radio while streaming')); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware version 2')); + if (channelNumber === undefined || channelNumber === null) return reject(Error('Must input a new channel number to switch too!')); + if (!k.isNumber(channelNumber)) return reject(Error('Must input type Number')); + if (channelNumber > k.OBCIRadioChannelMax) return reject(Error(`New channel number must be less than ${k.OBCIRadioChannelMax}`)); + if (channelNumber < k.OBCIRadioChannelMin) return reject(Error(`New channel number must be greater than ${k.OBCIRadioChannelMin}`)); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); + reject(Error('Please make sure your dongle is using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -684,7 +685,7 @@ OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + reject(Error(`Error [radioChannelSet]: ${data}`)); // The channel number is in the first byte } }); @@ -711,17 +712,17 @@ OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { OpenBCICyton.prototype.radioChannelSetHostOverride = function (channelNumber) { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (channelNumber === undefined || channelNumber === null) return reject('Must input a new channel number to switch too!'); - if (!k.isNumber(channelNumber)) return reject('Must input type Number'); - if (channelNumber > k.OBCIRadioChannelMax) return reject(`New channel number must be less than ${k.OBCIRadioChannelMax}`); - if (channelNumber < k.OBCIRadioChannelMin) return reject(`New channel number must be greater than ${k.OBCIRadioChannelMin}`); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't query for the radio while streaming")); + if (channelNumber === undefined || channelNumber === null) return reject(Error('Must input a new channel number to switch too!')); + if (!k.isNumber(channelNumber)) return reject(Error('Must input type Number')); + if (channelNumber > k.OBCIRadioChannelMax) return reject(Error(`New channel number must be less than ${k.OBCIRadioChannelMax}`)); + if (channelNumber < k.OBCIRadioChannelMin) return reject(Error(`New channel number must be greater than ${k.OBCIRadioChannelMin}`)); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is using firmware v2'); + reject(Error('Please make sure your dongle is using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -734,7 +735,7 @@ OpenBCICyton.prototype.radioChannelSetHostOverride = function (channelNumber) { if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); } else { - reject(`Error [radioChannelSet]: ${data}`); // The channel number is in the first byte + reject(Error(`Error [radioChannelSet]: ${data}`)); // The channel number is in the first byte } }); @@ -762,14 +763,14 @@ OpenBCICyton.prototype.radioChannelGet = function () { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the radio while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't query for the radio while streaming")); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); + reject(Error('Please make sure your dongle is plugged in and using firmware v2')); }, 500); // Subscribe to the EOT event @@ -784,7 +785,7 @@ OpenBCICyton.prototype.radioChannelGet = function () { data: data }); } else { - reject(`Error [radioChannelGet]: ${data.toString()}`); + reject(Error(`Error [radioChannelGet]: ${data.toString()}`)); } }); @@ -810,13 +811,13 @@ OpenBCICyton.prototype.radioChannelGet = function () { OpenBCICyton.prototype.radioPollTimeGet = function () { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't query for the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't query for the poll time while streaming")); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); + reject(Error('Please make sure your dongle is plugged in and using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -830,7 +831,7 @@ OpenBCICyton.prototype.radioPollTimeGet = function () { var pollTime = data[data.length - 4]; resolve(pollTime); } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + reject(Error(`Error [radioPollTimeGet]: ${data}`)); // The channel number is in the first byte } }); @@ -857,18 +858,18 @@ OpenBCICyton.prototype.radioPollTimeGet = function () { OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (pollTime === undefined || pollTime === null) return reject('Must input a new poll time to switch too!'); - if (!k.isNumber(pollTime)) return reject('Must input type Number'); - if (pollTime > k.OBCIRadioPollTimeMax) return reject(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`); - if (pollTime < k.OBCIRadioPollTimeMin) return reject(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't change the poll time while streaming")); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (pollTime === undefined || pollTime === null) return reject(Error('Must input a new poll time to switch too!')); + if (!k.isNumber(pollTime)) return reject(Error('Must input type Number')); + if (pollTime > k.OBCIRadioPollTimeMax) return reject(Error(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`)); + if (pollTime < k.OBCIRadioPollTimeMin) return reject(Error(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`)); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); + reject(Error('Please make sure your dongle is plugged in and using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -881,7 +882,7 @@ OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { if (obciUtils.isSuccessInBuffer(data)) { resolve(data[data.length - 4]); // Ditch the eot $$$ } else { - reject(`Error [radioPollTimeSet]: ${data}`); // The channel number is in the first byte + reject(Error(`Error [radioPollTimeSet]: ${data}`)); // The channel number is in the first byte } }); @@ -911,14 +912,14 @@ OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { OpenBCICyton.prototype.radioBaudRateSet = function (speed) { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the baud rate while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware v2'); - if (!k.isString(speed)) return reject('Must input type String'); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't change the baud rate while streaming")); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (!k.isString(speed)) return reject(Error('Must input type String')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); + reject(Error('Please make sure your dongle is plugged in and using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -937,10 +938,10 @@ OpenBCICyton.prototype.radioBaudRateSet = function (speed) { } var newBaudRateNum = Number(newBaudRateBuf.toString()); if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { - return reject('Error parse mismatch, restart your system!'); + return reject(Error('Error parse mismatch, restart your system!')); } if (!this.isConnected()) { - reject('Lost connection to device during baud set'); + reject(Error('Lost connection to device during baud set')); } else if (obciUtils.isSuccessInBuffer(data)) { // Change the sample rate here if (this.options.simulate === false) { @@ -952,7 +953,7 @@ OpenBCICyton.prototype.radioBaudRateSet = function (speed) { resolve(newBaudRateNum); } } else { - reject(`Error [radioPollTimeGet]: ${data}`); // The channel number is in the first byte + reject(Error(`Error [radioPollTimeGet]: ${data}`)); // The channel number is in the first byte } }); @@ -982,14 +983,14 @@ OpenBCICyton.prototype.radioBaudRateSet = function (speed) { OpenBCICyton.prototype.radioSystemStatusGet = function () { var badCommsTimeout; return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to Dongle. Pro tip: Call .connect()'); - if (this.isStreaming()) return reject("Don't change the poll time while streaming"); - if (!this.usingVersionTwoFirmware()) return reject('Must be using firmware version 2'); + if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); + if (this.isStreaming()) return reject(Error("Don't check the radio status while streaming")); + if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware version 2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { - reject('Please make sure your dongle is plugged in and using firmware v2'); + reject(Error('Please make sure your dongle is plugged in and using firmware v2')); }, 1000); // Subscribe to the EOT event @@ -1090,7 +1091,7 @@ OpenBCICyton.prototype.overrideInfoForBoardType = function (boardType) { * @return {Promise} */ OpenBCICyton.prototype.hardSetBoardType = function (boardType) { - if (this.isStreaming()) return Promise.reject('Must not be streaming!'); + if (this.isStreaming()) return Promise.reject(Error('Must not be streaming!')); return new Promise((resolve, reject) => { const eotFunc = (data) => { switch (data.slice(0, data.length - k.OBCIParseEOT.length).toString()) { @@ -1108,7 +1109,7 @@ OpenBCICyton.prototype.hardSetBoardType = function (boardType) { break; case k.OBCIChannelMaxNumber16NoDaisyAttached: this.overrideInfoForBoardType(k.OBCIBoardDefault); - reject('unable to attach daisy'); + reject(Error('unable to attach daisy')); break; case k.OBCIChannelMaxNumber8NoDaisyToRemove: default: @@ -1134,7 +1135,7 @@ OpenBCICyton.prototype.hardSetBoardType = function (boardType) { reject(err); }); } else { - reject('invalid board type'); + reject(Error('invalid board type')); } }); }; @@ -1275,8 +1276,8 @@ OpenBCICyton.prototype.testSignal = function (signal) { */ OpenBCICyton.prototype.impedanceTestContinuousStart = function () { return new Promise((resolve, reject) => { - if (this.impedanceTest.active) return reject('Error: test already active'); - if (this.impedanceTest.continuousMode) return reject('Error: Already in continuous impedance test mode!'); + if (this.impedanceTest.active) return reject(Error('Error: test already active')); + if (this.impedanceTest.continuousMode) return reject(Error('Error: Already in continuous impedance test mode!')); this.impedanceTest.active = true; this.impedanceTest.continuousMode = true; @@ -1298,8 +1299,8 @@ OpenBCICyton.prototype.impedanceTestContinuousStart = function () { */ OpenBCICyton.prototype.impedanceTestContinuousStop = function () { return new Promise((resolve, reject) => { - if (!this.impedanceTest.active) return reject('Error: no test active'); - if (!this.impedanceTest.continuousMode) return reject('Error: Not in continuous impedance test mode!'); + if (!this.impedanceTest.active) return reject(Error('Error: no test active')); + if (!this.impedanceTest.continuousMode) return reject(Error('Error: Not in continuous impedance test mode!')); this.impedanceTest.active = false; this.impedanceTest.continuousMode = false; @@ -1328,7 +1329,7 @@ OpenBCICyton.prototype.impedanceTestAllChannels = function () { upperLimit = k.OBCINumberOfChannelsDaisy; } - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + if (!this.isStreaming()) return Promise.reject(Error('Must be streaming!')); // Recursive function call var completeChannelImpedanceTest = (channelNumber) => { @@ -1365,10 +1366,10 @@ OpenBCICyton.prototype.impedanceTestAllChannels = function () { * @author AJ Keller (@pushtheworldllc) */ OpenBCICyton.prototype.impedanceTestChannels = function (arrayOfChannels) { - if (!Array.isArray(arrayOfChannels)) return Promise.reject('Input must be array of channels... See Docs!'); - if (!this.isStreaming()) return Promise.reject('Must be streaming!'); + if (!Array.isArray(arrayOfChannels)) return Promise.reject(Error('Input must be array of channels... See Docs!')); + if (!this.isStreaming()) return Promise.reject(Error('Must be streaming!')); // Check proper length of array - if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length); + if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject(Error('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length)); // Recursive function call var completeChannelImpedanceTest = (channelNumber) => { @@ -1493,7 +1494,7 @@ OpenBCICyton.prototype.impedanceTestChannelInputN = function (channelNumber) { */ OpenBCICyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected'); + if (!this.isConnected()) return reject(Error('Must be connected')); /* istanbul ignore if */ if (this.options.verbose) { @@ -1555,9 +1556,9 @@ OpenBCICyton.prototype._impedanceTestCalculateChannel = function (channelNumber, } } return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject(Error('Invalid channel number')); + if (typeof pInput !== 'boolean') return reject(Error("Invalid Input: 'pInput' must be of type Bool")); + if (typeof nInput !== 'boolean') return reject(Error("Invalid Input: 'nInput' must be of type Bool")); this.impedanceTest.onChannel = channelNumber; this.impedanceTest.sampleNumber = 0; // Reset the sample number this.impedanceTest.isTestingPInput = pInput; @@ -1608,9 +1609,9 @@ OpenBCICyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, } } return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject('Invalid channel number'); - if (typeof pInput !== 'boolean') return reject("Invalid Input: 'pInput' must be of type Bool"); - if (typeof nInput !== 'boolean') return reject("Invalid Input: 'nInput' must be of type Bool"); + if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject(Error('Invalid channel number')); + if (typeof pInput !== 'boolean') return reject(Error("Invalid Input: 'pInput' must be of type Bool")); + if (typeof nInput !== 'boolean') return reject(Error("Invalid Input: 'nInput' must be of type Bool")); if (pInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); if (nInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); @@ -1632,7 +1633,7 @@ OpenBCICyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, */ OpenBCICyton.prototype.sdStart = function (recordingDuration) { return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isConnected()) return reject(Error('Must be connected to the device')); k.sdSettingForString(recordingDuration) .then(command => { // If we are not streaming, then expect a confirmation message back from the board @@ -1654,7 +1655,7 @@ OpenBCICyton.prototype.sdStart = function (recordingDuration) { */ OpenBCICyton.prototype.sdStop = function () { return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); + if (!this.isConnected()) return reject(Error('Must be connected to the device')); // If we are not streaming, then expect a confirmation message back from the board if (!this.isStreaming()) { this.curParsingMode = k.OBCIParsingEOT; @@ -1719,9 +1720,9 @@ OpenBCICyton.prototype.numberOfChannels = function () { */ OpenBCICyton.prototype.syncClocks = function () { return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + if (!this.isConnected()) return reject(Error('Must be connected to the device')); + if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); + if (!this.usingVersionTwoFirmware()) return reject(Error('Time sync not implemented on v1 firmware, please update to v2')); this.sync.curSyncObj = obciUtils.newSyncObject(); this.sync.curSyncObj.timeSyncSent = this.time(); this.curParsingMode = k.OBCIParsingTimeSyncSent; @@ -1740,11 +1741,11 @@ OpenBCICyton.prototype.syncClocks = function () { */ OpenBCICyton.prototype.syncClocksFull = function () { return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject('Must be connected to the device'); - if (!this.isStreaming()) return reject('Must be streaming to sync clocks'); - if (!this.usingVersionTwoFirmware()) return reject('Time sync not implemented on v1 firmware, please update to v2'); + if (!this.isConnected()) return reject(Error('Must be connected to the device')); + if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); + if (!this.usingVersionTwoFirmware()) return reject(Error('Time sync not implemented on v1 firmware, please update to v2')); var timeout = setTimeout(() => { - return reject('syncClocksFull timeout after 500ms with no sync'); + return reject(Error('syncClocksFull timeout after 500ms with no sync')); }, 500); // Should not take more than 1s to sync up this.sync.eventEmitter = syncObj => { clearTimeout(timeout); @@ -2151,7 +2152,7 @@ OpenBCICyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { OpenBCICyton.prototype.sntpGetOffset = function () { this.options.sntpTimeSync = true; - if (!this.options.sntpTimeSync) return Promise.reject('sntp not enabled'); + if (!this.options.sntpTimeSync) return Promise.reject(Error('sntp not enabled')); return new Promise((resolve, reject) => { Sntp.offset({ diff --git a/test/openBCICyton-Impedance-test.js b/test/openBCICyton-Impedance-test.js index 2166ea1..b565b7a 100644 --- a/test/openBCICyton-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -5,6 +5,7 @@ const should = chai.should(); // eslint-disable-line no-unused-vars const Cyton = require('../openBCICyton'); const openBCIUtilities = require('openbci-utilities').Utilities; const k = require('openbci-utilities').Constants; +const Buffer = require('safe-buffer').Buffer; const chaiAsPromised = require('chai-as-promised'); const sinonChai = require('sinon-chai'); diff --git a/test/openBCICyton-radio-test.js b/test/openBCICyton-radio-test.js index 136fea3..32e0a18 100644 --- a/test/openBCICyton-radio-test.js +++ b/test/openBCICyton-radio-test.js @@ -8,9 +8,12 @@ var OpenBCICyton = require('../openBCICyton'); var k = require('openbci-utilities').Constants; var chaiAsPromised = require('chai-as-promised'); var sinonChai = require('sinon-chai'); +const Buffer = require('safe-buffer').Buffer; +const dirtyChai = require('dirty-chai'); chai.use(chaiAsPromised); chai.use(sinonChai); +chai.use(dirtyChai); describe('openbci-radios', function () { this.timeout(2000); @@ -768,7 +771,7 @@ describe('openbci-radios', function () { .then(() => { ourBoard.once('ready', () => { ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true; + expect(isUp).to.be.true(); done(); }).catch(err => done(err)); }); @@ -785,7 +788,7 @@ describe('openbci-radios', function () { .then(() => { ourBoard.once('ready', () => { ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.false; + expect(isUp).to.be.false(); done(); }).catch(err => done(err)); }); @@ -897,7 +900,7 @@ describe('openbci-radios', function () { }) .then(isUp => { // console.log(`isUp test`, Date.now(), `${Date.now() - timey}ms`) - expect(isUp).to.be.false; + expect(isUp).to.be.false(); return ourBoard.radioChannelSetHostOverride(systemChanNumber); // Set back to good }) .then(newChanNumActual => { @@ -906,7 +909,7 @@ describe('openbci-radios', function () { return ourBoard.radioSystemStatusGet(); }) .then(isUp => { - expect(isUp).to.be.true; + expect(isUp).to.be.true(); done(); }) .catch(err => done(err)); @@ -940,7 +943,7 @@ describe('openbci-radios', function () { // Don't test if not using v2 if (!ourBoard.usingVersionTwoFirmware()) return done(); ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true; + expect(isUp).to.be.true(); done(); }).catch(err => done(err)); }); diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index f463e96..3d4108a 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -14,6 +14,7 @@ const bufferEqual = require('buffer-equal'); const fs = require('fs'); const math = require('mathjs'); const dirtyChai = require('dirty-chai'); +const Buffer = require('safe-buffer').Buffer; chai.use(chaiAsPromised); chai.use(sinonChai); From ffb84a006ab871c20cdb17adeab4ac489fca0da3 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 16:46:55 -0400 Subject: [PATCH 22/42] Fix issues with node 4 compatability --- .travis.yml | 2 +- openBCICyton.js | 152 +++++++++++++--------------- package.json | 5 +- test/openBCICyton-Impedance-test.js | 7 +- test/openBCICyton-radio-test.js | 19 ++-- test/openBCICyton-test.js | 20 ++-- 6 files changed, 99 insertions(+), 106 deletions(-) diff --git a/.travis.yml b/.travis.yml index caec7f4..0a2cdea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,5 @@ node_js: install: - npm install --all script: - - npm run lint + - npm run test-lint - npm run test-cov diff --git a/openBCICyton.js b/openBCICyton.js index 93b7c49..c8bcaf2 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1,7 +1,6 @@ 'use strict'; const EventEmitter = require('events').EventEmitter; const util = require('util'); -const stream = require('stream'); const SerialPort = require('serialport'); const OpenBCIUtilities = require('openbci-utilities'); const obciUtils = OpenBCIUtilities.Utilities; @@ -134,15 +133,13 @@ var _options = { * @constructor * @author AJ Keller (@pushtheworldllc) */ -function OpenBCICyton (options) { - if (!(this instanceof OpenBCICyton)) { - return new OpenBCICyton(options); +function Cyton (options) { + if (!(this instanceof Cyton)) { + return new Cyton(options); } options = options || {}; var opts = {}; - stream.Stream.call(this); - /** Configuring Options */ var o; for (o in _options) { @@ -250,8 +247,7 @@ function OpenBCICyton (options) { } } -// This allows us to use the emitter class freely outside of the module -util.inherits(OpenBCICyton, stream.Stream); +util.inherits(Cyton, EventEmitter); /** * @description The essential precursor method to be called initially to establish a @@ -260,7 +256,7 @@ util.inherits(OpenBCICyton, stream.Stream); * @returns {Promise} if the board was able to connect. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.connect = function (portName) { +Cyton.prototype.connect = function (portName) { return new Promise((resolve, reject) => { if (this.isConnected()) return reject(Error('already connected!')); @@ -346,7 +342,7 @@ OpenBCICyton.prototype.connect = function (portName) { * @description Called once when for any reason the serial port is no longer open. * @private */ -OpenBCICyton.prototype._disconnected = function (err) { +Cyton.prototype._disconnected = function (err) { this._streaming = false; clearTimeout(this.writer); @@ -371,7 +367,7 @@ OpenBCICyton.prototype._disconnected = function (err) { * @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.disconnect = function () { +Cyton.prototype.disconnect = function () { return Promise.resolve() .then(() => { if (this.isStreaming()) { @@ -397,7 +393,7 @@ OpenBCICyton.prototype.disconnect = function () { * @description Checks if the driver is connected to a board. * @returns {boolean} - True if connected. */ -OpenBCICyton.prototype.isConnected = function () { +Cyton.prototype.isConnected = function () { if (!this.serial) return false; return this.serial.isOpen(); }; @@ -406,7 +402,7 @@ OpenBCICyton.prototype.isConnected = function () { * @description Checks if the board is currently sending samples. * @returns {boolean} - True if streaming. */ -OpenBCICyton.prototype.isSimulating = function () { +Cyton.prototype.isSimulating = function () { return this.options.simulate; }; @@ -414,7 +410,7 @@ OpenBCICyton.prototype.isSimulating = function () { * @description Checks if the board is currently sending samples. * @returns {boolean} - True if streaming. */ -OpenBCICyton.prototype.isStreaming = function () { +Cyton.prototype.isStreaming = function () { return this._streaming; }; @@ -426,7 +422,7 @@ OpenBCICyton.prototype.isStreaming = function () { * mean the board will start streaming. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.streamStart = function () { +Cyton.prototype.streamStart = function () { return new Promise((resolve, reject) => { if (this.isStreaming()) return reject(Error('Error [.streamStart()]: Already streaming')); this._streaming = true; @@ -442,7 +438,7 @@ OpenBCICyton.prototype.streamStart = function () { * mean the board stopped streaming. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.streamStop = function () { +Cyton.prototype.streamStop = function () { return new Promise((resolve, reject) => { if (!this.isStreaming()) return reject(Error('Error [.streamStop()]: No stream to stop')); this._streaming = false; @@ -456,7 +452,7 @@ OpenBCICyton.prototype.streamStop = function () { * @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.simulatorEnable = function () { +Cyton.prototype.simulatorEnable = function () { return new Promise((resolve, reject) => { if (this.options.simulate) return reject(Error('Already simulating')); // Are we already in simulate mode? if (this.isConnected()) { @@ -479,7 +475,7 @@ OpenBCICyton.prototype.simulatorEnable = function () { * @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.simulatorDisable = function () { +Cyton.prototype.simulatorDisable = function () { return new Promise((resolve, reject) => { if (!this.options.simulate) return reject(Error('Not simulating')); // Are we already not in simulate mode? if (this.isConnected()) { @@ -504,7 +500,7 @@ OpenBCICyton.prototype.simulatorDisable = function () { * @returns {Promise} * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.write = function (dataToWrite) { +Cyton.prototype.write = function (dataToWrite) { return new Promise((resolve, reject) => { if (!this.isConnected()) { reject(Error('not connected')); @@ -556,7 +552,7 @@ OpenBCICyton.prototype.write = function (dataToWrite) { * @returns {Promise} if signal was able to be sent * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._writeAndDrain = function (data) { +Cyton.prototype._writeAndDrain = function (data) { obciDebug.debugBytes('>>>', data); return new Promise((resolve, reject) => { @@ -583,7 +579,7 @@ OpenBCICyton.prototype._writeAndDrain = function (data) { * @returns {Promise} - Fulfilled with portName, rejected when can't find the board. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.autoFindOpenBCIBoard = function () { +Cyton.prototype.autoFindOpenBCIBoard = function () { const serialPatterns = [ { // mac comName: /usbserial-D/ @@ -639,7 +635,7 @@ OpenBCICyton.prototype.autoFindOpenBCIBoard = function () { * to determine if we are using firmware version 2. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.usingVersionTwoFirmware = function () { +Cyton.prototype.usingVersionTwoFirmware = function () { if (this.options.simulate) { return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; } else { @@ -658,7 +654,7 @@ OpenBCICyton.prototype.usingVersionTwoFirmware = function () { * @returns {Promise} - Resolves with the new channel number, rejects with err. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { +Cyton.prototype.radioChannelSet = function (channelNumber) { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -709,7 +705,7 @@ OpenBCICyton.prototype.radioChannelSet = function (channelNumber) { * @returns {Promise} - Resolves with the new channel number, rejects with err. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioChannelSetHostOverride = function (channelNumber) { +Cyton.prototype.radioChannelSetHostOverride = function (channelNumber) { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -758,7 +754,7 @@ OpenBCICyton.prototype.radioChannelSetHostOverride = function (channelNumber) { * the condition that there system is experiencing board communications failure. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioChannelGet = function () { +Cyton.prototype.radioChannelGet = function () { // The function to run on timeout var badCommsTimeout; @@ -808,7 +804,7 @@ OpenBCICyton.prototype.radioChannelGet = function () { * @returns {Promise} - Resolves with the poll time, rejects with an error message. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioPollTimeGet = function () { +Cyton.prototype.radioPollTimeGet = function () { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -855,7 +851,7 @@ OpenBCICyton.prototype.radioPollTimeGet = function () { * @returns {Promise} - Resolves with new poll time, rejects with error message. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { +Cyton.prototype.radioPollTimeSet = function (pollTime) { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -909,7 +905,7 @@ OpenBCICyton.prototype.radioPollTimeSet = function (pollTime) { * @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioBaudRateSet = function (speed) { +Cyton.prototype.radioBaudRateSet = function (speed) { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -980,7 +976,7 @@ OpenBCICyton.prototype.radioBaudRateSet = function (speed) { * @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.radioSystemStatusGet = function () { +Cyton.prototype.radioSystemStatusGet = function () { var badCommsTimeout; return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); @@ -1023,7 +1019,7 @@ OpenBCICyton.prototype.radioSystemStatusGet = function () { * @author Andy Heusser (@andyh616) * @returns {Promise} - On fulfill will contain an array of Serial ports to use. */ -OpenBCICyton.prototype.listPorts = function () { +Cyton.prototype.listPorts = function () { return new Promise((resolve, reject) => { SerialPort.list((err, ports) => { if (err) reject(err); @@ -1047,7 +1043,7 @@ OpenBCICyton.prototype.listPorts = function () { * Get the board type. * @return boardType: string */ -OpenBCICyton.prototype.getBoardType = function () { +Cyton.prototype.getBoardType = function () { return this.info.boardType; }; @@ -1055,7 +1051,7 @@ OpenBCICyton.prototype.getBoardType = function () { * Get the core info object. * @return {{boardType: string, sampleRate: number, firmware: string, numberOfChannels: number, missedPackets: number}} */ -OpenBCICyton.prototype.getInfo = function () { +Cyton.prototype.getInfo = function () { return this.info; }; @@ -1064,7 +1060,7 @@ OpenBCICyton.prototype.getInfo = function () { * @param boardType {String} * `default` or `daisy`. Defaults to `default`. */ -OpenBCICyton.prototype.overrideInfoForBoardType = function (boardType) { +Cyton.prototype.overrideInfoForBoardType = function (boardType) { switch (boardType) { case k.OBCIBoardDaisy: this.info.boardType = k.OBCIBoardDaisy; @@ -1090,7 +1086,7 @@ OpenBCICyton.prototype.overrideInfoForBoardType = function (boardType) { * Either `default` or `daisy` * @return {Promise} */ -OpenBCICyton.prototype.hardSetBoardType = function (boardType) { +Cyton.prototype.hardSetBoardType = function (boardType) { if (this.isStreaming()) return Promise.reject(Error('Must not be streaming!')); return new Promise((resolve, reject) => { const eotFunc = (data) => { @@ -1147,7 +1143,7 @@ OpenBCICyton.prototype.hardSetBoardType = function (boardType) { * streaming. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.softReset = function () { +Cyton.prototype.softReset = function () { this.curParsingMode = k.OBCIParsingReset; return this.write(k.OBCIMiscSoftReset); }; @@ -1159,7 +1155,7 @@ OpenBCICyton.prototype.softReset = function () { * @author AJ Keller (@pushtheworldllc) */ // TODO: REDO THIS FUNCTION -OpenBCICyton.prototype.getSettingsForChannel = function (channelNumber) { +Cyton.prototype.getSettingsForChannel = function (channelNumber) { return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { // this.searchingBuf = newSearchingBuffer return this.printRegisterSettings(); @@ -1171,7 +1167,7 @@ OpenBCICyton.prototype.getSettingsForChannel = function (channelNumber) { * @returns {Promise.|*} * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.printRegisterSettings = function () { +Cyton.prototype.printRegisterSettings = function () { return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { this.curParsingMode = k.OBCIParsingChannelSettings; }); @@ -1183,7 +1179,7 @@ OpenBCICyton.prototype.printRegisterSettings = function () { * @returns {Promise.} * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.channelOff = function (channelNumber) { +Cyton.prototype.channelOff = function (channelNumber) { return k.commandChannelOff(channelNumber).then((charCommand) => { // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) return this.write(charCommand); @@ -1196,7 +1192,7 @@ OpenBCICyton.prototype.channelOff = function (channelNumber) { * @returns {Promise.|*} * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.channelOn = function (channelNumber) { +Cyton.prototype.channelOn = function (channelNumber) { return k.commandChannelOn(channelNumber).then((charCommand) => { // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) return this.write(charCommand); @@ -1226,7 +1222,7 @@ OpenBCICyton.prototype.channelOn = function (channelNumber) { * @returns {Promise} resolves if sent, rejects on bad input or no board * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { +Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { var arrayOfCommands = []; return new Promise((resolve, reject) => { k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) @@ -1258,7 +1254,7 @@ OpenBCICyton.prototype.channelSet = function (channelNumber, powerDown, gain, in * @returns {Promise} * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.testSignal = function (signal) { +Cyton.prototype.testSignal = function (signal) { return new Promise((resolve, reject) => { k.getTestSignalCommand(signal) .then(command => { @@ -1274,7 +1270,7 @@ OpenBCICyton.prototype.testSignal = function (signal) { * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestContinuousStart = function () { +Cyton.prototype.impedanceTestContinuousStart = function () { return new Promise((resolve, reject) => { if (this.impedanceTest.active) return reject(Error('Error: test already active')); if (this.impedanceTest.continuousMode) return reject(Error('Error: Already in continuous impedance test mode!')); @@ -1297,7 +1293,7 @@ OpenBCICyton.prototype.impedanceTestContinuousStart = function () { * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestContinuousStop = function () { +Cyton.prototype.impedanceTestContinuousStop = function () { return new Promise((resolve, reject) => { if (!this.impedanceTest.active) return reject(Error('Error: no test active')); if (!this.impedanceTest.continuousMode) return reject(Error('Error: Not in continuous impedance test mode!')); @@ -1321,7 +1317,7 @@ OpenBCICyton.prototype.impedanceTestContinuousStop = function () { * @returns {Promise} - Resovles when complete testing all the channels. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestAllChannels = function () { +Cyton.prototype.impedanceTestAllChannels = function () { var upperLimit = k.OBCINumberOfChannelsDefault; /* istanbul ignore if */ @@ -1365,7 +1361,7 @@ OpenBCICyton.prototype.impedanceTestAllChannels = function () { * @returns {Promise} - Fulfilled with a loaded impedance object. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestChannels = function (arrayOfChannels) { +Cyton.prototype.impedanceTestChannels = function (arrayOfChannels) { if (!Array.isArray(arrayOfChannels)) return Promise.reject(Error('Input must be array of channels... See Docs!')); if (!this.isStreaming()) return Promise.reject(Error('Must be streaming!')); // Check proper length of array @@ -1410,7 +1406,7 @@ OpenBCICyton.prototype.impedanceTestChannels = function (arrayOfChannels) { * @returns {Promise} - Fulfilled with a single channel impedance object. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestChannel = function (channelNumber) { +Cyton.prototype.impedanceTestChannel = function (channelNumber) { this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. @@ -1440,7 +1436,7 @@ OpenBCICyton.prototype.impedanceTestChannel = function (channelNumber) { * @returns {Promise} - Fulfilled with a single channel impedance object. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestChannelInputP = function (channelNumber) { +Cyton.prototype.impedanceTestChannelInputP = function (channelNumber) { this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. @@ -1464,7 +1460,7 @@ OpenBCICyton.prototype.impedanceTestChannelInputP = function (channelNumber) { * @returns {Promise} - Fulfilled with a single channel impedance object. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.impedanceTestChannelInputN = function (channelNumber) { +Cyton.prototype.impedanceTestChannelInputN = function (channelNumber) { this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); return new Promise((resolve, reject) => { this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. @@ -1492,7 +1488,7 @@ OpenBCICyton.prototype.impedanceTestChannelInputN = function (channelNumber) { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { +Cyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected')); @@ -1542,7 +1538,7 @@ OpenBCICyton.prototype._impedanceTestSetChannel = function (channelNumber, pInpu * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { +Cyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { /* istanbul ignore if */ if (this.options.verbose) { if (pInput && !nInput) { @@ -1595,7 +1591,7 @@ OpenBCICyton.prototype._impedanceTestCalculateChannel = function (channelNumber, * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { +Cyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { /* istanbul ignore if */ if (this.options.verbose) { if (pInput && !nInput) { @@ -1631,7 +1627,7 @@ OpenBCICyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.sdStart = function (recordingDuration) { +Cyton.prototype.sdStart = function (recordingDuration) { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); k.sdSettingForString(recordingDuration) @@ -1653,7 +1649,7 @@ OpenBCICyton.prototype.sdStart = function (recordingDuration) { * @returns {Promise} - Resolves when written * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.sdStop = function () { +Cyton.prototype.sdStop = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); // If we are not streaming, then expect a confirmation message back from the board @@ -1671,7 +1667,7 @@ OpenBCICyton.prototype.sdStop = function () { * Note: This is dependent on if you configured the board correctly on setup options * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.sampleRate = function () { +Cyton.prototype.sampleRate = function () { if (this.options.simulate) { return this.options.simulatorSampleRate; } else { @@ -1696,7 +1692,7 @@ OpenBCICyton.prototype.sampleRate = function () { * Note: This is dependent on if you configured the board correctly on setup options * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.numberOfChannels = function () { +Cyton.prototype.numberOfChannels = function () { if (this.info) { return this.info.numberOfChannels; } else { @@ -1718,7 +1714,7 @@ OpenBCICyton.prototype.numberOfChannels = function () { * @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.syncClocks = function () { +Cyton.prototype.syncClocks = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); @@ -1739,7 +1735,7 @@ OpenBCICyton.prototype.syncClocks = function () { * @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.syncClocksFull = function () { +Cyton.prototype.syncClocksFull = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); @@ -1775,7 +1771,7 @@ OpenBCICyton.prototype.syncClocksFull = function () { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processBytes = function (data) { +Cyton.prototype._processBytes = function (data) { obciDebug.debugBytes(this.curParsingMode + '<<', data); // Concat old buffer @@ -1855,7 +1851,7 @@ OpenBCICyton.prototype._processBytes = function (data) { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { +Cyton.prototype._processDataBuffer = function (dataBuffer) { if (_.isNull(dataBuffer) || _.isUndefined(dataBuffer)) return null; const output = obciUtils.extractRawDataPackets(dataBuffer); @@ -1883,7 +1879,7 @@ OpenBCICyton.prototype._processDataBuffer = function (dataBuffer) { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processParseBufferForReset = function (dataBuffer) { +Cyton.prototype._processParseBufferForReset = function (dataBuffer) { if (obciUtils.countADSPresent(dataBuffer) === 2) { this.overrideInfoForBoardType(k.OBCIBoardDaisy); } else { @@ -1905,7 +1901,7 @@ OpenBCICyton.prototype._processParseBufferForReset = function (dataBuffer) { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { +Cyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { if (!rawDataPacketBuffer) return; if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; var missedPacketArray = obciUtils.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); @@ -1931,7 +1927,7 @@ OpenBCICyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { +Cyton.prototype._processImpedanceTest = function (sampleObject) { var impedanceArray; if (this.impedanceTest.continuousMode) { // console.log('running in continuous mode...') @@ -1957,7 +1953,7 @@ OpenBCICyton.prototype._processImpedanceTest = function (sampleObject) { * @returns {Object} - A time sync object. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { +Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { /** * Helper function for creating a bad sync object * @param err {object} - Can be any object @@ -2090,7 +2086,7 @@ OpenBCICyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPa * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._finalizeNewSample = function (sampleObject) { +Cyton.prototype._finalizeNewSample = function (sampleObject) { sampleObject._count = this.sampleCount++; if (!sampleObject.valid) { this.badPackets++; @@ -2119,7 +2115,7 @@ OpenBCICyton.prototype._finalizeNewSample = function (sampleObject) { * @private * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { +Cyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { if (obciUtils.isOdd(sampleObject.sampleNumber)) { // Check for the skipped packet condition if (this._lowerChannelsSampleObject) { @@ -2149,7 +2145,7 @@ OpenBCICyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { * @returns {Promise} A promise with the time offset * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.sntpGetOffset = function () { +Cyton.prototype.sntpGetOffset = function () { this.options.sntpTimeSync = true; if (!this.options.sntpTimeSync) return Promise.reject(Error('sntp not enabled')); @@ -2170,20 +2166,20 @@ OpenBCICyton.prototype.sntpGetOffset = function () { /** * @description Allows users to utilize all features of sntp if they want to... */ -OpenBCICyton.prototype.sntp = Sntp; +Cyton.prototype.sntp = Sntp; /** * @description This gets the time plus offset * @private */ -OpenBCICyton.prototype._sntpNow = Sntp.now; +Cyton.prototype._sntpNow = Sntp.now; /** * @description This starts the SNTP server and gets it to remain in sync with the SNTP server * @returns {Promise} - A promise if the module was able to sync with ntp server. * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.sntpStart = function (options) { +Cyton.prototype.sntpStart = function (options) { this.options.sntpTimeSync = true; // Sntp.start doesn't actually report errors (2016-10-29) @@ -2207,7 +2203,7 @@ OpenBCICyton.prototype.sntpStart = function (options) { /** * @description Stops the sntp from updating. */ -OpenBCICyton.prototype.sntpStop = function () { +Cyton.prototype.sntpStop = function () { Sntp.stop(); this.options.sntpTimeSync = false; this.sync.sntpActive = false; @@ -2218,7 +2214,7 @@ OpenBCICyton.prototype.sntpStop = function () { * @returns {Number} - The time * @author AJ Keller (@pushtheworldllc) */ -OpenBCICyton.prototype.time = function () { +Cyton.prototype.time = function () { if (this.options.sntpTimeSync) { return this._sntpNow(); } else { @@ -2231,7 +2227,7 @@ OpenBCICyton.prototype.time = function () { * @author AJ Keller (@pushtheworldllc) */ /* istanbul ignore next */ -OpenBCICyton.prototype.printPacketsBad = function () { +Cyton.prototype.printPacketsBad = function () { if (this.badPackets > 1) { console.log('Dropped a total of ' + this.badPackets + ' packets.'); } else if (this.badPackets === 1) { @@ -2246,7 +2242,7 @@ OpenBCICyton.prototype.printPacketsBad = function () { * @author AJ Keller (@pushtheworldllc) */ /* istanbul ignore next */ -OpenBCICyton.prototype.printBytesIn = function () { +Cyton.prototype.printBytesIn = function () { if (this.bytesIn > 1) { console.log('Read in ' + this.bytesIn + ' bytes.'); } else if (this.bytesIn === 1) { @@ -2261,7 +2257,7 @@ OpenBCICyton.prototype.printBytesIn = function () { * @author AJ Keller (@pushtheworldllc) */ /* istanbul ignore next */ -OpenBCICyton.prototype.debugSession = function () { +Cyton.prototype.debugSession = function () { this.printBytesIn(); this.printPacketsBad(); }; @@ -2271,7 +2267,7 @@ OpenBCICyton.prototype.debugSession = function () { * @param channelSettingsObj */ /* istanbul ignore next */ -OpenBCICyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { +Cyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { console.log('-- Channel Settings Object --'); var powerState = 'OFF'; if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { @@ -2286,10 +2282,8 @@ OpenBCICyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) * @param channelSettingsObject * @returns {boolean} */ -OpenBCICyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { +Cyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; }; -util.inherits(OpenBCICyton, EventEmitter); - -module.exports = OpenBCICyton; +module.exports = Cyton; diff --git a/package.json b/package.json index 1cda7b7..2b0244a 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { - "lint": "semistandard | snazzy", "start": "node index", "test": "semistandard | snazzy && mocha test", - "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov" + "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov", + "test-lint": "semistandard | snazzy" }, "keywords": [ "openbci", @@ -23,6 +23,7 @@ "mathjs": "^3.14.2", "openbci-utilities": "0.0.9", "performance-now": "^2.1.0", + "safe-buffer": "^5.1.1", "serialport": "4.0.7", "sntp": "^2.0.2", "streamsearch": "^0.1.2" diff --git a/test/openBCICyton-Impedance-test.js b/test/openBCICyton-Impedance-test.js index b565b7a..ee685ed 100644 --- a/test/openBCICyton-Impedance-test.js +++ b/test/openBCICyton-Impedance-test.js @@ -2,10 +2,9 @@ const bluebirdChecks = require('./bluebirdChecks'); const chai = require('chai'); const should = chai.should(); // eslint-disable-line no-unused-vars -const Cyton = require('../openBCICyton'); +const OpenBCICyton = require('../openBCICyton'); const openBCIUtilities = require('openbci-utilities').Utilities; const k = require('openbci-utilities').Constants; -const Buffer = require('safe-buffer').Buffer; const chaiAsPromised = require('chai-as-promised'); const sinonChai = require('sinon-chai'); @@ -13,11 +12,11 @@ chai.use(chaiAsPromised); chai.use(sinonChai); describe('#impedanceTesting', function () { - var ourBoard; + let ourBoard; this.timeout(20000); before(function (done) { - ourBoard = new Cyton({ + ourBoard = new OpenBCICyton({ verbose: true, simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); diff --git a/test/openBCICyton-radio-test.js b/test/openBCICyton-radio-test.js index 32e0a18..6696790 100644 --- a/test/openBCICyton-radio-test.js +++ b/test/openBCICyton-radio-test.js @@ -1,14 +1,13 @@ 'use strict'; -var bluebirdChecks = require('./bluebirdChecks'); -var sinon = require('sinon'); // eslint-disable-line no-unused-vars -var chai = require('chai'); -var expect = chai.expect; -var should = chai.should(); // eslint-disable-line no-unused-vars -var OpenBCICyton = require('../openBCICyton'); -var k = require('openbci-utilities').Constants; -var chaiAsPromised = require('chai-as-promised'); -var sinonChai = require('sinon-chai'); -const Buffer = require('safe-buffer').Buffer; +const bluebirdChecks = require('./bluebirdChecks'); +const sinon = require('sinon'); // eslint-disable-line no-unused-vars +const chai = require('chai'); +const expect = chai.expect; +const should = chai.should(); // eslint-disable-line no-unused-vars +const OpenBCICyton = require('../openBCICyton'); +const k = require('openbci-utilities').Constants; +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); const dirtyChai = require('dirty-chai'); chai.use(chaiAsPromised); diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 3d4108a..6bc9760 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -570,7 +570,7 @@ describe('openbci-sdk', function () { done('should have failed to connect'); }) .catch((err) => { - if (err === 'already connected!') { + if (err.message === 'already connected!') { return ourBoard.disconnect(); } else { return Promise.reject(err); @@ -976,7 +976,7 @@ describe('openbci-sdk', function () { var writeSpy2 = sinon.spy(ourBoard.serial, 'write'); ourBoard.once('ready', () => { writeSpy2.should.equal(ourBoard.serial.write); - writeSpy1.should.have.not.been.called; + writeSpy1.should.have.not.been.called(); writeSpy2.should.have.not.been.calledWith(byteToWrite); writeSpy1.restore(); writeSpy2.restore(); @@ -1196,7 +1196,7 @@ describe('openbci-sdk', function () { ourBoard.hardSetBoardType('daisy') .then(done) .catch((err) => { - expect(err).to.equal('unable to attach daisy'); + expect(err.message).to.equal('unable to attach daisy'); expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); done(); }); @@ -1496,7 +1496,7 @@ describe('openbci-sdk', function () { // Call the function under test buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledThrice; + _processQualifiedPacketSpy.should.have.been.calledThrice(); // The buffer should not have anything in it any more expect(buffer).to.be.null(); }); @@ -1517,7 +1517,7 @@ describe('openbci-sdk', function () { // Call the function under test buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice; + _processQualifiedPacketSpy.should.have.been.calledTwice(); // The buffer should not have anything in it any more buffer.length.should.equal(extraBuffer.length); }); @@ -1539,9 +1539,9 @@ describe('openbci-sdk', function () { // Call the function under test buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice; + _processQualifiedPacketSpy.should.have.been.calledTwice(); // The buffer should not have anything in it any more - bufferEqual(extraBuffer, buffer).should.be.true; + bufferEqual(extraBuffer, buffer).should.be.true(); buffer.length.should.equal(extraBuffer.length); }); @@ -1562,9 +1562,9 @@ describe('openbci-sdk', function () { // Call the function under test buffer = ourBoard._processDataBuffer(buffer); // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice; + _processQualifiedPacketSpy.should.have.been.calledTwice(); // The buffer should not have anything in it any more - bufferEqual(Buffer.concat([extraBuffer, extraBuffer], 2), buffer).should.be.true; + bufferEqual(Buffer.concat([extraBuffer, extraBuffer], 2), buffer).should.be.true(); buffer.length.should.equal(extraBuffer.length * 2); }); }); @@ -2333,7 +2333,7 @@ $$$`); var buf2 = new Buffer('amazing af$$$'); var eotEvent = data => { - bufferEqual(data, Buffer.concat([buf1, buf2], buf1.length + buf2.length)).should.be.true; + bufferEqual(data, Buffer.concat([buf1, buf2], buf1.length + buf2.length)).should.be.true(); ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); done(); }; From f053d7b045efb6dc498f2a6c53a8efa24c9034fb Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 16:53:17 -0400 Subject: [PATCH 23/42] Fix issues with node 8 compatability --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a2cdea..39322e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,16 @@ node_js: - "6.11.1" - "7.10.1" - "8.1.4" +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 install: - npm install --all script: - npm run test-lint - - npm run test-cov + - npm run test-cov \ No newline at end of file From 86307aa8abbd33bd5be8f13d74fb775f1637d8a8 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 17 Jul 2017 21:02:56 -0400 Subject: [PATCH 24/42] Fix for printing debug output --- README.md | 4 ++-- changelog.md | 6 ++++++ openBCICyton.js | 4 ++-- package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ce8ab28..d2867a0 100644 --- a/README.md +++ b/README.md @@ -1139,7 +1139,7 @@ ourBoard.write('o'); Emitted when the serial connection to the board is closed. -#### .on('droppedPacket', callback) +#### .on('droppedPacket', callback) Emitted when a packet (or packets) are dropped. Returns an array. @@ -1171,7 +1171,7 @@ Emitted when the board is in a ready to start streaming state. Emitted when there is a new sample available. -#### .on('synced', callback) +#### .on('synced', callback) Emitted when there is a new sample available. diff --git a/changelog.md b/changelog.md index 67d2e5d..0b16ca0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +# 2.0.1 + +### Bug Fixes + +* Debug bytes was set to always print in processBytes + # 2.0.0 ### New Features diff --git a/openBCICyton.js b/openBCICyton.js index c8bcaf2..356f2cd 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -553,7 +553,7 @@ Cyton.prototype.write = function (dataToWrite) { * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype._writeAndDrain = function (data) { - obciDebug.debugBytes('>>>', data); + if (this.options.debug) obciDebug.debugBytes('>>>', data); return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Serial port not open')); @@ -1772,7 +1772,7 @@ Cyton.prototype.syncClocksFull = function () { * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype._processBytes = function (data) { - obciDebug.debugBytes(this.curParsingMode + '<<', data); + if (this.options.debug) obciDebug.debugBytes(this.curParsingMode + '<<', data); // Concat old buffer var oldDataBuffer = null; diff --git a/package.json b/package.json index 2b0244a..5887bea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.0.0", + "version": "2.0.1", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { From fe4ec3f9c1c675eca85e28e1c2f092b90739ff0d Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 18 Jul 2017 12:48:59 -0400 Subject: [PATCH 25/42] Working on stabalizing daisy --- README.md | 4 ++ changelog.md | 10 +++ openBCICyton.js | 75 +++++++-------------- package.json | 4 +- test/openBCICyton-test.js | 138 ++++++++++++++++++++++++-------------- 5 files changed, 130 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index d2867a0..df8c600 100644 --- a/README.md +++ b/README.md @@ -1143,6 +1143,10 @@ Emitted when the serial connection to the board is closed. Emitted when a packet (or packets) are dropped. Returns an array. +#### .on('eot', callback) + +Emitted when there is an EOT a.k.a. '$$$' with a buffer filled with the data. + #### .on('error', callback) Emitted when there is an on the serial port. diff --git a/changelog.md b/changelog.md index 0b16ca0..8c4258f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +# 2.1.0 + +### Breaking changes + +* Significantly reduce the properties in `this.info` object to only have firmware version and number of missed packets. Code dependent on this.info should switch to using `numberOfChannels()`, and `getBoardType()` and `sampleRate()` for accurate board info! + +### Enhancements + +* Fixes for daisy with new board, specifically hardSet. + # 2.0.1 ### Bug Fixes diff --git a/openBCICyton.js b/openBCICyton.js index 356f2cd..542ef44 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -102,7 +102,7 @@ const Buffer = require('safe-buffer').Buffer; * @private */ var _options = { - boardType: [k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], + boardType: [k.OBCIBoardCyton, k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], baudRate: 115200, hardSet: false, sendCounts: false, @@ -179,7 +179,7 @@ function Cyton (options) { * @type {RawDataToSample} * @private */ - this._rawDataPacketToSample = k.rawDataToSampleObjectDefault(8); + this._rawDataPacketToSample = k.rawDataToSampleObjectDefault(k.numberOfChannelsForBoardType(this.options.boardType)); this._rawDataPacketToSample.scale = !this.options.sendCounts; this._rawDataPacketToSample.protocol = k.OBCIProtocolSerial; this._rawDataPacketToSample.verbose = this.options.verbose; @@ -194,15 +194,9 @@ function Cyton (options) { // Objects this.impedanceTest = obciUtils.impedanceTestObjDefault(); this.info = { - boardType: this.options.boardType, - sampleRate: k.OBCISampleRate250, firmware: k.OBCIFirmwareV1, - numberOfChannels: k.OBCINumberOfChannelsCyton, missedPackets: 0 }; - if (this.options.boardType === k.OBCIBoardDefault) { - this.info.sampleRate = k.OBCISampleRate250; - } this._lowerChannelsSampleObject = null; this.serial = null; @@ -259,7 +253,7 @@ util.inherits(Cyton, EventEmitter); Cyton.prototype.connect = function (portName) { return new Promise((resolve, reject) => { if (this.isConnected()) return reject(Error('already connected!')); - + this.overrideInfoForBoardType(this.options.boardType); /* istanbul ignore else */ if (this.options.simulate || portName === k.OBCISimulatorPortName) { this.options.simulate = true; @@ -1044,12 +1038,12 @@ Cyton.prototype.listPorts = function () { * @return boardType: string */ Cyton.prototype.getBoardType = function () { - return this.info.boardType; + return k.boardTypeForNumberOfChannels(this._rawDataPacketToSample.channelSettings.length); }; /** * Get the core info object. - * @return {{boardType: string, sampleRate: number, firmware: string, numberOfChannels: number, missedPackets: number}} + * @return {{firmware: string, missedPackets: number}} */ Cyton.prototype.getInfo = function () { return this.info; @@ -1063,19 +1057,14 @@ Cyton.prototype.getInfo = function () { Cyton.prototype.overrideInfoForBoardType = function (boardType) { switch (boardType) { case k.OBCIBoardDaisy: - this.info.boardType = k.OBCIBoardDaisy; - this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy; - this.info.sampleRate = k.OBCISampleRate125; - this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); + this._rawDataPacketToSample.channelSettings = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsDaisy); break; + case k.OBCIBoardCyton: case k.OBCIBoardDefault: default: - this.info.boardType = k.OBCIBoardDefault; - this.info.numberOfChannels = k.OBCINumberOfChannelsDefault; - this.info.sampleRate = k.OBCISampleRate250; - this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault); - this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsDefault); + this._rawDataPacketToSample.channelSettings = k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton); + this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsCyton); break; } }; @@ -1092,7 +1081,7 @@ Cyton.prototype.hardSetBoardType = function (boardType) { const eotFunc = (data) => { switch (data.slice(0, data.length - k.OBCIParseEOT.length).toString()) { case k.OBCIChannelMaxNumber8SuccessDaisyRemoved: - this.overrideInfoForBoardType(k.OBCIBoardDefault); + this.overrideInfoForBoardType(k.OBCIBoardCyton); resolve('daisy removed'); break; case k.OBCIChannelMaxNumber16DaisyAlreadyAttached: @@ -1104,18 +1093,19 @@ Cyton.prototype.hardSetBoardType = function (boardType) { resolve('daisy attached'); break; case k.OBCIChannelMaxNumber16NoDaisyAttached: - this.overrideInfoForBoardType(k.OBCIBoardDefault); + this.overrideInfoForBoardType(k.OBCIBoardCyton); reject(Error('unable to attach daisy')); break; case k.OBCIChannelMaxNumber8NoDaisyToRemove: default: - this.overrideInfoForBoardType(k.OBCIBoardDefault); + this.overrideInfoForBoardType(k.OBCIBoardCyton); resolve('no daisy to remove'); break; } }; - if (boardType === k.OBCIBoardDefault) { + if (boardType === k.OBCIBoardCyton || boardType === k.OBCIBoardDefault) { this.curParsingMode = k.OBCIParsingEOT; + if (this.options.verbose) console.log('Attempting to hardset board type'); this.once(k.OBCIEmitterEot, eotFunc); this.write(k.OBCIChannelMaxNumber8) .catch((err) => { @@ -1228,7 +1218,7 @@ Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) .then((val) => { arrayOfCommands = val.commandArray; - this.channelSettingsArray[channelNumber - 1] = val.newChannelSettingsObject; + this._rawDataPacketToSample.channelSettings[channelNumber - 1] = val.newChannelSettingsObject; return this.write(arrayOfCommands); }).then(resolve, reject); }); @@ -1318,7 +1308,7 @@ Cyton.prototype.impedanceTestContinuousStop = function () { * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype.impedanceTestAllChannels = function () { - var upperLimit = k.OBCINumberOfChannelsDefault; + var upperLimit = k.OBCINumberOfChannelsCyton; /* istanbul ignore if */ if (this.options.daisy) { @@ -1671,16 +1661,13 @@ Cyton.prototype.sampleRate = function () { if (this.options.simulate) { return this.options.simulatorSampleRate; } else { - if (this.info) { - return this.info.sampleRate; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCISampleRate125; - case k.OBCIBoardDefault: - default: - return k.OBCISampleRate250; - } + switch (this.getBoardType()) { + case k.OBCIBoardDaisy: + return k.OBCISampleRate125; + case k.OBCIBoardCyton: + case k.OBCIBoardDefault: + default: + return k.OBCISampleRate250; } } }; @@ -1693,17 +1680,7 @@ Cyton.prototype.sampleRate = function () { * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype.numberOfChannels = function () { - if (this.info) { - return this.info.numberOfChannels; - } else { - switch (this.boardType) { - case k.OBCIBoardDaisy: - return k.OBCINumberOfChannelsDaisy; - case k.OBCIBoardDefault: - default: - return k.OBCINumberOfChannelsDefault; - } - } + return this._rawDataPacketToSample.channelSettings.length; }; /** @@ -1883,7 +1860,7 @@ Cyton.prototype._processParseBufferForReset = function (dataBuffer) { if (obciUtils.countADSPresent(dataBuffer) === 2) { this.overrideInfoForBoardType(k.OBCIBoardDaisy); } else { - this.overrideInfoForBoardType(k.OBCIBoardDefault); + this.overrideInfoForBoardType(k.OBCIBoardCyton); } if (obciUtils.findV2Firmware(dataBuffer)) { @@ -2096,7 +2073,7 @@ Cyton.prototype._finalizeNewSample = function (sampleObject) { } else { // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper // channels (9-16) come in packets with even sample numbers - if (this.info.boardType === k.OBCIBoardDaisy) { + if (this.getBoardType === k.OBCIBoardDaisy) { // Send the sample for downstream sample compaction this._finalizeNewSampleForDaisy(sampleObject); } else { diff --git a/package.json b/package.json index 5887bea..3a227b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.0.1", + "version": "2.1.0", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "0.0.9", + "openbci-utilities": "0.0.10", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 6bc9760..a8d3852 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -71,7 +71,7 @@ describe('openbci-sdk', function () { }); it('constructs with the correct default options', () => { var board = new Cyton(); - expect(board.options.boardType).to.equal(k.OBCIBoardDefault); + expect(board.options.boardType).to.equal(k.OBCIBoardCyton); expect(board.options.baudRate).to.equal(115200); expect(board.options.hardSet).to.be.false(); expect(board.options.sendCounts).to.be.false(); @@ -467,32 +467,70 @@ describe('openbci-sdk', function () { }); }).catch(err => done(err)); }); - it('should be able to set info for default board', function () { - ourBoard.info.boardType = 'burrito'; - ourBoard.info.sampleRate = 60; - ourBoard.info.numberOfChannels = 200; - ourBoard.overrideInfoForBoardType('default'); - expect(ourBoard.getInfo().boardType).to.be.equal(k.OBCIBoardDefault); - expect(ourBoard.getInfo().numberOfChannels).to.be.equal(k.OBCINumberOfChannelsDefault); - expect(ourBoard.getInfo().sampleRate).to.be.equal(k.OBCISampleRate250); - }); - it('should be able to set info for daisy board', function () { - ourBoard.info.boardType = 'burrito'; - ourBoard.info.sampleRate = 60; - ourBoard.info.numberOfChannels = 200; - ourBoard.overrideInfoForBoardType('daisy'); - expect(ourBoard.getInfo().boardType).to.be.equal(k.OBCIBoardDaisy); - expect(ourBoard.getInfo().numberOfChannels).to.be.equal(k.OBCINumberOfChannelsDaisy); - expect(ourBoard.getInfo().sampleRate).to.be.equal(k.OBCISampleRate125); - }); - it('should set info to default on bad input string', function () { - ourBoard.info.boardType = 'burrito'; - ourBoard.info.sampleRate = 60; - ourBoard.info.numberOfChannels = 200; - ourBoard.overrideInfoForBoardType('taco'); - expect(ourBoard.getInfo().boardType).to.be.equal(k.OBCIBoardDefault); - expect(ourBoard.getInfo().numberOfChannels).to.be.equal(k.OBCINumberOfChannelsDefault); - expect(ourBoard.getInfo().sampleRate).to.be.equal(k.OBCISampleRate250); + it('should be able to set info for cyton board', function (done) { + ourBoard.simulatorDisable() + .then(() => { + ourBoard.overrideInfoForBoardType('cyton'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); + done(); + }) + .catch((err) => { + if (err.message === 'Not simulating') { + ourBoard.overrideInfoForBoardType('cyton'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); + done(); + } else { + done(err); + } + }); + + }); + it('should be able to set info for daisy board', function (done) { + ourBoard.simulatorDisable() + .then(() => { + ourBoard.overrideInfoForBoardType('daisy'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardDaisy); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsDaisy); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate125); + done(); + }) + .catch((err) => { + if (err.message === 'Not simulating') { + ourBoard.overrideInfoForBoardType('daisy'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardDaisy); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsDaisy); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate125); + done(); + } else { + done(err); + } + }); + }); + it('should set info to default on bad input string', function (done) { + ourBoard.simulatorDisable() + .then(() => { + ourBoard.overrideInfoForBoardType('taco'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); + done(); + }) + .catch((err) => { + if (err.message === 'Not simulating') { + ourBoard.overrideInfoForBoardType('cyton'); + expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); + expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); + expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); + done(); + } else { + done(err); + } + }); + }); }); describe('#debug', function () { @@ -639,7 +677,7 @@ describe('openbci-sdk', function () { }; const hardSetFuncOnTime = () => { // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); // Remove the premature ready function because it won't fire ourBoard.removeListener('ready', readyFuncPreMature); // If the board was able to attach the daisy @@ -695,7 +733,7 @@ describe('openbci-sdk', function () { }; const hardSetFuncOnTime = () => { // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); // Remove the premature ready function because it won't fire ourBoard.removeListener('ready', readyFuncPreMature); // If the board was able to attach the daisy @@ -705,7 +743,7 @@ describe('openbci-sdk', function () { }; const errorFuncTestSuccess = () => { // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); ourBoard.options.hardSet = false; ourBoard.disconnect().then(() => { // call disconnect ourBoard.removeListener('ready', readyFuncTestFailure); @@ -735,7 +773,7 @@ describe('openbci-sdk', function () { // Turn hardSet on ourBoard.options.hardSet = true; // Set the options to daisy boardType - ourBoard.options.boardType = k.OBCIBoardDefault; + ourBoard.options.boardType = k.OBCIBoardCyton; // The simulator has a daisy attached ourBoard.options.simulatorDaisyModuleAttached = true; @@ -767,7 +805,7 @@ describe('openbci-sdk', function () { }; const readyFuncSuccess = () => { // Verify the module switched to default type - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); // Remove because it won't fire ourBoard.removeListener('error', errorFuncTestFailure); ourBoard.options.hardSet = false; @@ -839,7 +877,7 @@ describe('openbci-sdk', function () { // Turn hardSet on ourBoard.options.hardSet = true; // Set the options to default boardType - ourBoard.options.boardType = k.OBCIBoardDefault; + ourBoard.options.boardType = k.OBCIBoardCyton; // The simulator does not have a daisy ourBoard.options.simulatorDaisyModuleAttached = false; // The simulator is able to attach daisy @@ -866,7 +904,7 @@ describe('openbci-sdk', function () { ourBoard.removeListener('error', errorFuncTestFailure); ourBoard.removeListener('hardSet', hardSetFuncFailure); // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); ourBoard.options.hardSet = false; ourBoard.disconnect().then(() => { // call disconnect done(); @@ -1142,7 +1180,7 @@ describe('openbci-sdk', function () { ourBoard.hardSetBoardType('default') .then((res) => { expect(res).to.equal('no daisy to remove'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); done(); }).catch(done); } else { @@ -1155,7 +1193,7 @@ describe('openbci-sdk', function () { ourBoard.hardSetBoardType('default') .then((res) => { expect(res).to.equal('daisy removed'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); done(); }).catch(done); } else { @@ -1197,7 +1235,7 @@ describe('openbci-sdk', function () { .then(done) .catch((err) => { expect(err.message).to.equal('unable to attach daisy'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); done(); }); } else { @@ -1980,9 +2018,9 @@ LIS3DH Device ID: 0x38422$$$`); ourBoard._processParseBufferForReset(buf); ourBoard.info.firmware.should.equal(k.OBCIFirmwareV1); - ourBoard.info.boardType.should.equal(k.OBCIBoardDefault); - ourBoard.info.sampleRate.should.equal(k.OBCISampleRate250); - ourBoard.info.numberOfChannels.should.equal(k.OBCINumberOfChannelsDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); it('should recognize firmware version 1 with daisy', () => { var buf = new Buffer(`OpenBCI V3 Simulator @@ -1994,9 +2032,9 @@ $$$`); ourBoard._processParseBufferForReset(buf); ourBoard.info.firmware.should.equal(k.OBCIFirmwareV1); - ourBoard.info.boardType.should.equal(k.OBCIBoardDaisy); - ourBoard.info.sampleRate.should.equal(k.OBCISampleRate125); - ourBoard.info.numberOfChannels.should.equal(k.OBCINumberOfChannelsDaisy); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); }); it('should recognize firmware version 2 with no daisy', () => { var buf = new Buffer(`OpenBCI V3 Simulator @@ -2008,9 +2046,9 @@ $$$`); ourBoard._processParseBufferForReset(buf); ourBoard.info.firmware.should.equal(k.OBCIFirmwareV2); - ourBoard.info.boardType.should.equal(k.OBCIBoardDefault); - ourBoard.info.sampleRate.should.equal(k.OBCISampleRate250); - ourBoard.info.numberOfChannels.should.equal(k.OBCINumberOfChannelsDefault); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); it('should recognize firmware version 2 with daisy', () => { var buf = new Buffer(`OpenBCI V3 Simulator @@ -2023,9 +2061,9 @@ $$$`); ourBoard._processParseBufferForReset(buf); ourBoard.info.firmware.should.equal(k.OBCIFirmwareV2); - ourBoard.info.boardType.should.equal(k.OBCIBoardDaisy); - ourBoard.info.sampleRate.should.equal(k.OBCISampleRate125); - ourBoard.info.numberOfChannels.should.equal(k.OBCINumberOfChannelsDaisy); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); }); }); @@ -2352,7 +2390,7 @@ $$$`); ourBoard = new Cyton({ verbose: true }); - randomSampleGenerator = openBCIUtilities.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, false, 'none'); + randomSampleGenerator = openBCIUtilities.randomSample(k.OBCINumberOfChannelsCyton, k.OBCISampleRate250, false, 'none'); }); beforeEach(() => { // Clear the global var From 8971878e89ef0f5664998e5bfb366eb3fed00040 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 18 Jul 2017 13:04:44 -0400 Subject: [PATCH 26/42] Fix linting... as usual --- test/openBCICyton-test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index a8d3852..a4f454d 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -487,7 +487,6 @@ describe('openbci-sdk', function () { done(err); } }); - }); it('should be able to set info for daisy board', function (done) { ourBoard.simulatorDisable() @@ -530,7 +529,6 @@ describe('openbci-sdk', function () { done(err); } }); - }); }); describe('#debug', function () { From 014741368f3f764f0f992b35c68682a98da6f9cc Mon Sep 17 00:00:00 2001 From: nateGeorge Date: Wed, 19 Jul 2017 14:00:31 -0600 Subject: [PATCH 27/42] BUG: forgot parenthesis on getBoardType() func --- openBCICyton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openBCICyton.js b/openBCICyton.js index 542ef44..92e2c6d 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -2073,7 +2073,7 @@ Cyton.prototype._finalizeNewSample = function (sampleObject) { } else { // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper // channels (9-16) come in packets with even sample numbers - if (this.getBoardType === k.OBCIBoardDaisy) { + if (this.getBoardType() === k.OBCIBoardDaisy) { // Send the sample for downstream sample compaction this._finalizeNewSampleForDaisy(sampleObject); } else { From b12c710e313dad6ad6a0c3d4e23da80df3de090a Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 19 Jul 2017 16:30:26 -0400 Subject: [PATCH 28/42] ADD: Firmware v3 support --- examples/getStreaming/getStreaming.js | 2 +- examples/getStreamingDaisy/getStreamingDaisy.js | 2 +- openBCICyton.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/getStreaming/getStreaming.js b/examples/getStreaming/getStreaming.js index 2bc7584..6b1fbb6 100644 --- a/examples/getStreaming/getStreaming.js +++ b/examples/getStreaming/getStreaming.js @@ -12,7 +12,7 @@ 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('../../index').Cyton; let ourBoard = new Cyton({ debug: debug, verbose: verbose diff --git a/examples/getStreamingDaisy/getStreamingDaisy.js b/examples/getStreamingDaisy/getStreamingDaisy.js index 973be10..ab3b446 100644 --- a/examples/getStreamingDaisy/getStreamingDaisy.js +++ b/examples/getStreamingDaisy/getStreamingDaisy.js @@ -12,7 +12,7 @@ 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('../../index').Cyton; let ourBoard = new Cyton({ boardType: 'daisy', debug: debug, diff --git a/openBCICyton.js b/openBCICyton.js index 542ef44..667f0d6 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -2060,7 +2060,7 @@ Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArr * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are * being tested. * @param sampleObject {Object} - A sample object that follows the normal standards. - * @private + * @privatec * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype._finalizeNewSample = function (sampleObject) { From f09d26e4ae30169e12161d56847746f11b576dc0 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 19 Jul 2017 21:20:59 -0400 Subject: [PATCH 29/42] ADD: Cyton Firmware V3.0.0 support for detection with time syncing and such --- changelog.md | 10 ++++++ openBCICyton.js | 64 +++++++++++++++++++++++++++--------- package.json | 4 +-- test/openBCICyton-test.js | 69 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 19 deletions(-) diff --git a/changelog.md b/changelog.md index 8c4258f..7adf1a6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +# 2.1.1 + +### Bug Fixes + +* Closes forgot parentheses in `getBoardType()` #152 (thanks @nateGeorge) + +### Enhancements + +* Add support for v3 cyton firmware. + # 2.1.0 ### Breaking changes diff --git a/openBCICyton.js b/openBCICyton.js index 9827154..564f5d9 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -110,7 +110,7 @@ var _options = { simulatorBoardFailure: false, simulatorDaisyModuleAttached: false, simulatorDaisyModuleCanBeAttached: true, - simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2], + simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2, k.OBCIFirmwareV3], simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], simulatorLatencyTime: 16, simulatorBufferSize: 4096, @@ -254,6 +254,7 @@ Cyton.prototype.connect = function (portName) { return new Promise((resolve, reject) => { if (this.isConnected()) return reject(Error('already connected!')); this.overrideInfoForBoardType(this.options.boardType); + this.buffer = null; /* istanbul ignore else */ if (this.options.simulate || portName === k.OBCISimulatorPortName) { this.options.simulate = true; @@ -637,6 +638,34 @@ Cyton.prototype.usingVersionTwoFirmware = function () { } }; +/** + * @description Convenience method to determine if you can use firmware v2.x.x + * or greater capabilities. + * @returns {boolean} - True if using firmware version 2 or greater. Should + * be called after a `.softReset()` because we can parse the output of that + * to determine if we are using firmware version 2. + * @author AJ Keller (@pushtheworldllc) + */ +Cyton.prototype.usingAtLeastVersionTwoFirmware = function () { + return this.usingVersionTwoFirmware() || this.usingVersionThreeFirmware(); +}; + +/** + * @description Convenience method to determine if you can use firmware v2.x.x + * capabilities. + * @returns {boolean} - True if using firmware version 2 or greater. Should + * be called after a `.softReset()` because we can parse the output of that + * to determine if we are using firmware version 2. + * @author AJ Keller (@pushtheworldllc) + */ +Cyton.prototype.usingVersionThreeFirmware = function () { + if (this.options.simulate) { + return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV3; + } else { + return this.info.firmware === k.OBCIFirmwareV3; + } +}; + /** * @description Used to set the system radio channel number. The function will reject if not * connected to the serial port of the dongle. Further the function should reject if currently streaming. @@ -653,7 +682,7 @@ Cyton.prototype.radioChannelSet = function (channelNumber) { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error('Don\'t query for the radio while streaming')); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware version 2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); if (channelNumber === undefined || channelNumber === null) return reject(Error('Must input a new channel number to switch too!')); if (!k.isNumber(channelNumber)) return reject(Error('Must input type Number')); if (channelNumber > k.OBCIRadioChannelMax) return reject(Error(`New channel number must be less than ${k.OBCIRadioChannelMax}`)); @@ -755,7 +784,7 @@ Cyton.prototype.radioChannelGet = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error("Don't query for the radio while streaming")); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware @@ -803,7 +832,7 @@ Cyton.prototype.radioPollTimeGet = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error("Don't query for the poll time while streaming")); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware badCommsTimeout = setTimeout(() => { @@ -850,7 +879,7 @@ Cyton.prototype.radioPollTimeSet = function (pollTime) { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error("Don't change the poll time while streaming")); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); if (pollTime === undefined || pollTime === null) return reject(Error('Must input a new poll time to switch too!')); if (!k.isNumber(pollTime)) return reject(Error('Must input type Number')); if (pollTime > k.OBCIRadioPollTimeMax) return reject(Error(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`)); @@ -904,7 +933,7 @@ Cyton.prototype.radioBaudRateSet = function (speed) { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error("Don't change the baud rate while streaming")); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); if (!k.isString(speed)) return reject(Error('Must input type String')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware @@ -975,7 +1004,7 @@ Cyton.prototype.radioSystemStatusGet = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); if (this.isStreaming()) return reject(Error("Don't check the radio status while streaming")); - if (!this.usingVersionTwoFirmware()) return reject(Error('Must be using firmware version 2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is // important if the module was connected, not streaming and using the old firmware @@ -1695,7 +1724,7 @@ Cyton.prototype.syncClocks = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); - if (!this.usingVersionTwoFirmware()) return reject(Error('Time sync not implemented on v1 firmware, please update to v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); this.sync.curSyncObj = obciUtils.newSyncObject(); this.sync.curSyncObj.timeSyncSent = this.time(); this.curParsingMode = k.OBCIParsingTimeSyncSent; @@ -1716,7 +1745,7 @@ Cyton.prototype.syncClocksFull = function () { return new Promise((resolve, reject) => { if (!this.isConnected()) return reject(Error('Must be connected to the device')); if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); - if (!this.usingVersionTwoFirmware()) return reject(Error('Time sync not implemented on v1 firmware, please update to v2')); + if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); var timeout = setTimeout(() => { return reject(Error('syncClocksFull timeout after 500ms with no sync')); }, 500); // Should not take more than 1s to sync up @@ -1773,7 +1802,7 @@ Cyton.prototype._processBytes = function (data) { if (obciUtils.doesBufferHaveEOT(data)) { this._processParseBufferForReset(data); if (this.options.hardSet) { - if (this.getBoardType() !== this.options.boardType) { + if (!_.eq(this.getBoardType(), this.options.boardType)) { this.emit(k.OBCIEmitterHardSet); this.hardSetBoardType(this.options.boardType) .then(() => { @@ -1788,7 +1817,7 @@ Cyton.prototype._processBytes = function (data) { this.buffer = obciUtils.stripToEOTBuffer(data); } } else { - if (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; @@ -1863,8 +1892,13 @@ Cyton.prototype._processParseBufferForReset = function (dataBuffer) { this.overrideInfoForBoardType(k.OBCIBoardCyton); } - if (obciUtils.findV2Firmware(dataBuffer)) { - this.info.firmware = k.OBCIFirmwareV2; + const firmware = obciUtils.getFirmware(dataBuffer); + if (firmware) { + if (firmware.major === 2) { + this.info.firmware = k.OBCIFirmwareV2; + } else { + this.info.firmware = k.OBCIFirmwareV3; + } this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; } else { this.info.firmware = k.OBCIFirmwareV1; @@ -2060,7 +2094,7 @@ Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArr * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are * being tested. * @param sampleObject {Object} - A sample object that follows the normal standards. - * @privatec + * @private * @author AJ Keller (@pushtheworldllc) */ Cyton.prototype._finalizeNewSample = function (sampleObject) { @@ -2073,7 +2107,7 @@ Cyton.prototype._finalizeNewSample = function (sampleObject) { } else { // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper // channels (9-16) come in packets with even sample numbers - if (this.getBoardType() === k.OBCIBoardDaisy) { + if (_.eq(this.getBoardType(), k.OBCIBoardDaisy)) { // Send the sample for downstream sample compaction this._finalizeNewSampleForDaisy(sampleObject); } else { diff --git a/package.json b/package.json index 3a227b3..ae48ffa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.1.0", + "version": "2.1.1", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "0.0.10", + "openbci-utilities": "0.1.0", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index a4f454d..f21b6ae 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -2038,7 +2038,7 @@ $$$`); var buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 LIS3DH Device ID: 0x38422 -Firmware: v2 +Firmware: v2.0.0 $$$`); ourBoard._processParseBufferForReset(buf); @@ -2048,12 +2048,26 @@ $$$`); expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); + it('should recognize firmware version 3 with no daisy', () => { + var buf = new Buffer(`OpenBCI V3 Simulator +On Board ADS1299 Device ID: 0x12345 +LIS3DH Device ID: 0x38422 +Firmware: v3.0.1 +$$$`); + + ourBoard._processParseBufferForReset(buf); + + ourBoard.info.firmware.should.equal(k.OBCIFirmwareV3); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); + }); it('should recognize firmware version 2 with daisy', () => { var buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 On Daisy ADS1299 Device ID: 0xFFFFF LIS3DH Device ID: 0x38422 -Firmware: v2 +Firmware: v2.0.0 $$$`); ourBoard._processParseBufferForReset(buf); @@ -2063,6 +2077,21 @@ $$$`); expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); }); + it('should recognize firmware version 3 with daisy', () => { + var buf = new Buffer(`OpenBCI V3 Simulator +On Board ADS1299 Device ID: 0x12345 +On Daisy ADS1299 Device ID: 0xFFFFF +LIS3DH Device ID: 0x38422 +Firmware: v3.1.69 +$$$`); + + ourBoard._processParseBufferForReset(buf); + + ourBoard.info.firmware.should.equal(k.OBCIFirmwareV3); + expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); + expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); + expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); + }); }); describe('#_processBytes', function () { @@ -2507,6 +2536,42 @@ $$$`); }); }); + describe('#usingVersionThreeFirmware', function () { + after(() => bluebirdChecks.noPendingPromises()); + it('should return true if firmware is version 3', () => { + ourBoard = new Cyton(); + ourBoard.info.firmware = 'v3'; + + expect(ourBoard.usingVersionThreeFirmware()).to.be.true(); + }); + it('should return false if not firmware version 3', () => { + ourBoard = new Cyton(); + + expect(ourBoard.usingVersionThreeFirmware()).to.be.false(); + }); + }); + + describe('#usingAtLeastVersionTwoFirmware', function () { + after(() => bluebirdChecks.noPendingPromises()); + it('should return true if firmware is version 3', () => { + ourBoard = new Cyton(); + ourBoard.info.firmware = 'v2'; + + expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.true(); + }); + it('should return true if firmware is version 3', () => { + ourBoard = new Cyton(); + ourBoard.info.firmware = 'v3'; + + expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.true(); + }); + it('should return false if not firmware version 3', () => { + ourBoard = new Cyton(); + + expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.false(); + }); + }); + xdescribe('#hardwareValidation', function () { this.timeout(20000); // long timeout for pleanty of stream time :) var runHardwareValidation = true; From 45bb260a84b02f47ef8fc86ffaed4d4fcd3ca7d8 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 19 Jul 2017 21:35:13 -0400 Subject: [PATCH 30/42] Chore: spin up travis From 1e0dba532ef06485ff1c8eb078eb0beeb73da9bc Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 19 Jul 2017 23:35:16 -0400 Subject: [PATCH 31/42] CHORE: Bump openbci-utilities to v0.1.1 --- changelog.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 7adf1a6..d8aafcd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +# 2.1.2 + +### Enhancements + +* Update openbci-utilities to v0.1.1 to patch for sendCounts. + # 2.1.1 ### Bug Fixes diff --git a/package.json b/package.json index ae48ffa..cd95f03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.1.1", + "version": "2.1.2", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "0.1.0", + "openbci-utilities": "0.1.1", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", From 0abebcdd9d730e6822ba64ed2e57d2d4ec26a8ee Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Thu, 20 Jul 2017 01:09:53 -0400 Subject: [PATCH 32/42] FIX: disconnect did not close serial port if device was not streaming --- changelog.md | 3 ++- openBCICyton.js | 4 +++- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index d8aafcd..98fea1e 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,8 @@ ### Enhancements -* Update openbci-utilities to v0.1.1 to patch for sendCounts. +* Update openbci-utilities to v0.1.2 to patch for sendCounts. +* Disconnect did not disconnect from serial port if the device was not streaming. # 2.1.1 diff --git a/openBCICyton.js b/openBCICyton.js index 564f5d9..9b35de2 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -368,13 +368,15 @@ Cyton.prototype.disconnect = function () { if (this.isStreaming()) { if (this.options.verbose) console.log('stop streaming'); return this.streamStop(); + } else { + return Promise.resolve(); } }) .then(() => { if (!this.isConnected()) { return Promise.reject(Error('no board connected')); } else { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { // serial emitting 'close' will call _disconnected this.serial.close(() => { resolve(); diff --git a/package.json b/package.json index cd95f03..0622d73 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "0.1.1", + "openbci-utilities": "0.1.2", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", From efadcf1efa7c4573b06b53df70c8ba3b88d0f7cf Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 15 Aug 2017 17:40:19 -0400 Subject: [PATCH 33/42] ADD: .syncRegisterSettings() functionality --- README.md | 6 + changelog.md | 7 + openBCICyton.js | 39 +++- package.json | 4 +- test/openBCICyton-test.js | 456 +++++++++++++++++++------------------- 5 files changed, 278 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index df8c600..ec11fac 100644 --- a/README.md +++ b/README.md @@ -1069,6 +1069,12 @@ ourBoard.connect(portName) ``` +#### .syncRegisterSettings() + +Syncs the internal channel settings object with a cyton, this will take about over a second because there are delays between the register reads in the firmware. + +**_Returns_** a promise, fulfilled once the channel settings have been synced and reject on error. + #### .testSignal(signal) Apply the internal test signal to all channels. diff --git a/changelog.md b/changelog.md index 98fea1e..8e1e3bb 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,13 @@ ### Enhancements +* Can now sync the channel settings from the actual registers on the ADS! Call `.syncRegisterSettings()` +* Bumped openbci-utilities to v0.1.4 + +# 2.1.2 + +### Enhancements + * Update openbci-utilities to v0.1.2 to patch for sendCounts. * Disconnect did not disconnect from serial port if the device was not streaming. diff --git a/openBCICyton.js b/openBCICyton.js index 9b35de2..9f290c4 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1184,14 +1184,43 @@ Cyton.prototype.getSettingsForChannel = function (channelNumber) { }; /** - * @description To print out the register settings to the console - * @returns {Promise.|*} + * @description Syncs the internal channel settings object with a cyton, this will take about + * over a second because there are delays between the register reads in the firmware. + * @returns {Promise.|*} Resolved once synced, rejects on error or 2 second timeout * @author AJ Keller (@pushtheworldllc) */ -Cyton.prototype.printRegisterSettings = function () { - return this.write(k.OBCIMiscQueryRegisterSettings).then(() => { - this.curParsingMode = k.OBCIParsingChannelSettings; +Cyton.prototype.syncRegisterSettings = function () { + // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is + // important if the module was connected, not streaming and using the old firmware + let badCommsTimeout; + return new Promise((resolve, reject) => { + badCommsTimeout = setTimeout(() => { + reject(Error('Please make sure your radio system is up')); + }, 2500); + // Subscribe to the EOT event + this.once(k.OBCIEmitterEot, data => { + if (this.options.verbose) console.log(data.toString()); + this._rawDataPacketToSample.data = data; + try { + obciUtils.syncChannelSettingsWithRawData(this._rawDataPacketToSample); + resolve(); + } catch (e) { + reject(e); + } + // Remove the timeout! + clearTimeout(badCommsTimeout); + badCommsTimeout = null; + + }); + this.curParsingMode = k.OBCIParsingEOT; + + this.write(k.OBCIMiscQueryRegisterSettings) + .catch((err) => { + clearTimeout(badCommsTimeout); + reject(err); + }) }); + }; /** diff --git a/package.json b/package.json index 0622d73..1710cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.1.2", + "version": "2.1.3", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "0.1.2", + "openbci-utilities": "^0.1.4", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index f21b6ae..7a48ac9 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -3,7 +3,7 @@ const bluebirdChecks = require('./bluebirdChecks'); const sinon = require('sinon'); const chai = require('chai'); const expect = chai.expect; -const should = chai.should(); // eslint-disable-line no-unused-vars +const should = chai.should(); // eslint-disable-line no-unused-lets const Cyton = require('../openBCICyton'); const OpenBCIUtilities = require('openbci-utilities'); const openBCIUtilities = OpenBCIUtilities.Utilities; @@ -22,7 +22,7 @@ chai.use(dirtyChai); describe('openbci-sdk', function () { this.timeout(2000); - var ourBoard, masterPortName, realBoard, spy; + let ourBoard, masterPortName, realBoard, spy; before(function (done) { ourBoard = new Cyton(); @@ -63,14 +63,14 @@ describe('openbci-sdk', function () { return bluebirdChecks.noPendingPromises(); }); it('constructs with require', function () { - var OpenBCICyton = require('../index').Cyton; + let OpenBCICyton = require('../index').Cyton; ourBoard = new OpenBCICyton({ verbose: true }); expect(ourBoard.numberOfChannels()).to.equal(8); }); it('constructs with the correct default options', () => { - var board = new Cyton(); + let board = new Cyton(); expect(board.options.boardType).to.equal(k.OBCIBoardCyton); expect(board.options.baudRate).to.equal(115200); expect(board.options.hardSet).to.be.false(); @@ -100,16 +100,16 @@ describe('openbci-sdk', function () { }); }); it('should be able to set ganglion mode', () => { - var board = new Cyton({ + let board = new Cyton({ boardType: 'ganglion' }); (board.options.boardType).should.equal('ganglion'); }); it('should be able to set set daisy mode', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ boardType: 'daisy' }); - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ boardtype: 'daisy' }); (ourBoard1.options.boardType).should.equal('daisy'); @@ -123,104 +123,104 @@ describe('openbci-sdk', function () { }); }); it('should be able to change baud rate', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ baudRate: 9600 }); (ourBoard1.options.baudRate).should.equal(9600); - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ baudrate: 9600 }); (ourBoard2.options.baudRate).should.equal(9600); }); it('should be able to enter simulate mode from the constructor', () => { - var board = new Cyton({ + let board = new Cyton({ simulate: true }); expect(board.options.simulate).to.be.true(); }); it('should be able to set the simulator to board failure mode', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorBoardFailure: true }); expect(ourBoard1.options.simulatorBoardFailure).to.be.true(); - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorboardfailure: true }); expect(ourBoard2.options.simulatorBoardFailure).to.be.true(); }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorDaisyModuleAttached: true }); expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true(); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatordaisymoduleattached: true }); expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true(); }); it('should be able to start the simulator with firmware version 2', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorFirmwareVersion: 'v2' }); (ourBoard1.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorfirmwareversion: 'v2' }); (ourBoard2.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); }); it('should be able to put the simulator in raw aux mode', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorHasAccelerometer: false }); expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false(); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorhasaccelerometer: false }); expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false(); }); it('should be able to make the internal clock of the simulator run slow', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorInternalClockDrift: -1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.lessThan(0); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorinternalclockdrift: -1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.lessThan(0); }); it('should be able to make the internal clock of the simulator run fast', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorInternalClockDrift: 1 }); expect(ourBoard1.options.simulatorInternalClockDrift).to.be.greaterThan(0); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorinternalclockdrift: 1 }); expect(ourBoard2.options.simulatorInternalClockDrift).to.be.greaterThan(0); }); it('should be able to not inject alpha waves into the simulator', function () { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorInjectAlpha: false }); expect(ourBoard1.options.simulatorInjectAlpha).to.be.false(); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorinjectalpha: false }); expect(ourBoard2.options.simulatorInjectAlpha).to.be.false(); }); it('can turn 50Hz line noise on', function () { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorInjectLineNoise: '50Hz' }); expect(ourBoard1.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorinjectlinenoise: '50Hz' }); expect(ourBoard2.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); @@ -247,18 +247,18 @@ describe('openbci-sdk', function () { (ourBoard.sampleRate()).should.equal(69); }); it('should be able to attach the daisy board in the simulator', () => { - var ourBoard1 = new Cyton({ + let ourBoard1 = new Cyton({ simulatorSerialPortFailure: true }); expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true(); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ simulatorserialportfailure: true }); expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true(); }); it('should be able to enter sync mode', function () { - var ourBoard = new Cyton({ + let ourBoard = new Cyton({ sntpTimeSync: true }); expect(ourBoard.options.sntpTimeSync).to.be.true(); @@ -274,31 +274,31 @@ describe('openbci-sdk', function () { }); }); it('should be able to change the ntp pool host', function () { - var expectedPoolName = 'time.apple.com'; - var ourBoard1 = new Cyton({ + let expectedPoolName = 'time.apple.com'; + let ourBoard1 = new Cyton({ sntpTimeSyncHost: expectedPoolName }); expect(ourBoard1.options.sntpTimeSyncHost).to.equal(expectedPoolName); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ sntptimesynchost: expectedPoolName }); expect(ourBoard2.options.sntpTimeSyncHost).to.equal(expectedPoolName); }); it('should be able to change the ntp pool port', function () { - var expectedPortNumber = 73; - var ourBoard1 = new Cyton({ + let expectedPortNumber = 73; + let ourBoard1 = new Cyton({ sntpTimeSyncPort: expectedPortNumber }); expect(ourBoard1.options.sntpTimeSyncPort).to.equal(expectedPortNumber); // Verify multi case support - var ourBoard2 = new Cyton({ + let ourBoard2 = new Cyton({ sntptimesyncport: expectedPortNumber }); expect(ourBoard2.options.sntpTimeSyncPort).to.equal(expectedPortNumber); }); it('should report when sntp fails', function (done) { - var ourBoard = new Cyton({ + let ourBoard = new Cyton({ sntpTimeSync: true, sntpTimeSyncHost: 'no\'where' }); @@ -321,7 +321,7 @@ describe('openbci-sdk', function () { ourBoard.curParsingMode.should.equal(k.OBCIParsingReset); }); - it('configures impedance testing variables correctly', function () { + it('configures impedance testing letiables correctly', function () { ourBoard = new Cyton(); (ourBoard.impedanceTest.active).should.equal(false); (ourBoard.impedanceTest.isTestingNInput).should.equal(false); @@ -376,8 +376,8 @@ describe('openbci-sdk', function () { ourBoard = new Cyton({ verbose: true }); - var disconnectStub = sinon.stub(ourBoard, 'disconnect').returns(Promise.resolve()); - var isConnectedStub = sinon.stub(ourBoard, 'isConnected').returns(true); + let disconnectStub = sinon.stub(ourBoard, 'disconnect').returns(Promise.resolve()); + let isConnectedStub = sinon.stub(ourBoard, 'isConnected').returns(true); ourBoard.options.simulate.should.equal(false); ourBoard.simulatorEnable().then(() => { disconnectStub.should.have.been.calledOnce(); @@ -415,7 +415,7 @@ describe('openbci-sdk', function () { ourBoard.connect(k.OBCISimulatorPortName).catch(done); ourBoard.on('ready', function () { expect(ourBoard.isSimulating()).to.equal(true); - var disconnectSpy = sinon.spy(ourBoard, 'disconnect'); + let disconnectSpy = sinon.spy(ourBoard, 'disconnect'); ourBoard.options.simulate.should.equal(true); ourBoard.simulatorDisable().then(() => { disconnectSpy.should.have.been.calledOnce(); @@ -447,7 +447,7 @@ describe('openbci-sdk', function () { ourBoard.connect(k.OBCISimulatorPortName) .then(() => { ourBoard.once('ready', () => { - var simOptions = ourBoard.serial.options; + let simOptions = ourBoard.serial.options; expect(simOptions).to.be.an('object'); expect(simOptions.accel).to.be.false(); expect(simOptions.alpha).to.be.false(); @@ -633,7 +633,7 @@ describe('openbci-sdk', function () { // console.log('Device is streaming: ' + ourBoard.isStreaming() ? 'true' : 'false') setTimeout(() => { spy.should.have.been.calledWithMatch(k.OBCIStreamStop); - var conditionalTimeout = realBoard ? 300 : 0; + let conditionalTimeout = realBoard ? 300 : 0; setTimeout(() => { done(); }, conditionalTimeout); @@ -839,17 +839,17 @@ describe('openbci-sdk', function () { done(err); }); }; - var hardSetFuncFailure = () => { + let hardSetFuncFailure = () => { ourBoard.removeListener('ready', readyFuncSuccess); ourBoard.removeListener('hardSet', hardSetFuncFailure); failTestWithErr('should not hardSet'); }; - var errorFuncTestFailure = () => { + let errorFuncTestFailure = () => { ourBoard.removeListener('ready', readyFuncSuccess); ourBoard.removeListener('hardSet', hardSetFuncFailure); failTestWithErr('should not error'); }; - var readyFuncSuccess = () => { + let readyFuncSuccess = () => { ourBoard.removeListener('error', errorFuncTestFailure); ourBoard.removeListener('hardSet', hardSetFuncFailure); // Verify the module is still default @@ -888,17 +888,17 @@ describe('openbci-sdk', function () { done(err); }); }; - var hardSetFuncFailure = () => { + let hardSetFuncFailure = () => { ourBoard.removeListener('ready', readyFuncSuccess); ourBoard.removeListener('hardSet', hardSetFuncFailure); failTestWithErr('should not hard set'); }; - var errorFuncTestFailure = () => { + let errorFuncTestFailure = () => { ourBoard.removeListener('ready', readyFuncSuccess); ourBoard.removeListener('hardSet', hardSetFuncFailure); failTestWithErr('should not emit error'); }; - var readyFuncSuccess = () => { + let readyFuncSuccess = () => { ourBoard.removeListener('error', errorFuncTestFailure); ourBoard.removeListener('hardSet', hardSetFuncFailure); // Verify the module is still default @@ -949,7 +949,7 @@ describe('openbci-sdk', function () { }); }); it('is no longer connected after a stream error', function () { - var errorDamper = () => true; + let errorDamper = () => true; ourBoard.on('error', errorDamper); ourBoard.serial.emit('error', new Error('test error')); expect(ourBoard.isConnected()).to.be.false(); @@ -986,16 +986,16 @@ describe('openbci-sdk', function () { }); }); it('rejects after a stream error', function (done) { - var errorDamper = () => true; + let errorDamper = () => true; ourBoard.on('error', errorDamper); ourBoard.serial.emit('error', new Error('test error')); ourBoard.write(k.OBCIMiscSoftReset).should.be.rejected.and.notify(done); ourBoard.removeListener('error', errorDamper); }); it('does not allow data to be sent after clean disconnection', function (done) { - var writeSpy1 = sinon.spy(ourBoard.serial, 'write'); - var byteToWrite = k.OBCISDLogStop; - var writeWhileConnected = function () { + let writeSpy1 = sinon.spy(ourBoard.serial, 'write'); + let byteToWrite = k.OBCISDLogStop; + let writeWhileConnected = function () { ourBoard.write(byteToWrite).then(() => { if (ourBoard.isConnected()) { writeSpy1.reset(); @@ -1009,7 +1009,7 @@ describe('openbci-sdk', function () { } else { process.nextTick(() => { ourBoard.connect(masterPortName).catch(done); - var writeSpy2 = sinon.spy(ourBoard.serial, 'write'); + let writeSpy2 = sinon.spy(ourBoard.serial, 'write'); ourBoard.once('ready', () => { writeSpy2.should.equal(ourBoard.serial.write); writeSpy1.should.have.not.been.called(); @@ -1026,7 +1026,7 @@ describe('openbci-sdk', function () { ourBoard.disconnect().catch(done); }); it('disconnects immediately, rejecting all buffered writes', function (done) { - var writeSpy = sinon.spy(ourBoard.serial, 'write'); + let writeSpy = sinon.spy(ourBoard.serial, 'write'); ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); @@ -1345,22 +1345,24 @@ describe('openbci-sdk', function () { ourBoard.channelSet(1, true, 24, 'normal', 'taco', true, true).should.be.rejected.and.notify(done); }); }); - describe('#printRegisterSettings', function () { + describe('#syncRegisterSettings', function () { + this.timeout(6000); before(function (done) { - ourBoard.printRegisterSettings() + if (!ourBoard.isConnected()) { + ourBoard.connect(masterPortName) + .then(done) + .catch(err => done(err)); + } else { + done(); + } + }); + it('should send the correct register setting', function (done) { + ourBoard.syncRegisterSettings() .then(() => { done(); }).catch(done); }); - after(function () { - ourBoard.curParsingMode = k.OBCIParsingNormal; - }); - it('should send the correct register setting', function () { - spy.should.have.been.calledWith(k.OBCIMiscQueryRegisterSettings); - }); - it('should have set the proper parseing mode', function () { - ourBoard.curParsingMode = k.OBCIParsingEOT; - }); + }); describe('#testSignal', function () { it('should call the write function with proper command for dc test signal', function (done) { @@ -1447,7 +1449,7 @@ describe('openbci-sdk', function () { * Test the function that parses an incoming data buffer for packets */ describe('#_processDataBuffer', function () { - var _processQualifiedPacketSpy; + let _processQualifiedPacketSpy; before(() => { ourBoard = new Cyton({ verbose: true @@ -1460,7 +1462,7 @@ describe('openbci-sdk', function () { }); it('should do nothing when empty buffer inserted', () => { - var buffer = null; + let buffer = null; // Test the function buffer = ourBoard._processDataBuffer(buffer); @@ -1468,8 +1470,8 @@ describe('openbci-sdk', function () { expect(buffer).to.be.null(); }); it('should return an unaltered buffer if there is less than a packets worth of data in it', () => { - var expectedString = 'AJ'; - var buffer = new Buffer(expectedString); + let expectedString = 'AJ'; + let buffer = new Buffer(expectedString); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1484,7 +1486,7 @@ describe('openbci-sdk', function () { _processQualifiedPacketSpy.should.not.have.been.called(); }); it('should identify a packet', () => { - var buffer = openBCIUtilities.samplePacketReal(0); + let buffer = openBCIUtilities.samplePacketReal(0); // Reset the spy if it exists if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); @@ -1499,10 +1501,10 @@ describe('openbci-sdk', function () { expect(buffer).to.be.null(); }); it('should extract a buffer and preserve the remaining data in the buffer', () => { - var expectedString = 'AJ'; - var extraBuffer = new Buffer(expectedString); + let expectedString = 'AJ'; + let extraBuffer = new Buffer(expectedString); // declare the big buffer - var buffer = new Buffer(k.OBCIPacketSize + extraBuffer.length); + let buffer = new Buffer(k.OBCIPacketSize + extraBuffer.length); // Fill that new big buffer with buffers openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); @@ -1520,9 +1522,9 @@ describe('openbci-sdk', function () { it('should be able to extract multiple packets from a single buffer', () => { // We are going to extract multiple buffers - var expectedNumberOfBuffers = 3; + let expectedNumberOfBuffers = 3; // declare the big buffer - var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers); + let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers); // Fill that new big buffer with buffers openBCIUtilities.samplePacketReal(0).copy(buffer, 0); openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); @@ -1538,12 +1540,12 @@ describe('openbci-sdk', function () { }); it('should be able to get multiple packets and keep extra data on the end', () => { - var expectedString = 'AJ'; - var extraBuffer = new Buffer(expectedString); + let expectedString = 'AJ'; + let extraBuffer = new Buffer(expectedString); // We are going to extract multiple buffers - var expectedNumberOfBuffers = 2; + let expectedNumberOfBuffers = 2; // declare the big buffer - var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); + let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); // Fill that new big buffer with buffers openBCIUtilities.samplePacketReal(0).copy(buffer, 0); openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); @@ -1559,12 +1561,12 @@ describe('openbci-sdk', function () { }); it('should be able to get multiple packets with junk in the middle', () => { - var expectedString = ','; - var extraBuffer = new Buffer(expectedString); + let expectedString = ','; + let extraBuffer = new Buffer(expectedString); // We are going to extract multiple buffers - var expectedNumberOfBuffers = 2; + let expectedNumberOfBuffers = 2; // declare the big buffer - var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); + let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); // Fill that new big buffer with buffers openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); @@ -1582,12 +1584,12 @@ describe('openbci-sdk', function () { }); it('should be able to get multiple packets with junk in the middle and end', () => { - var expectedString = ','; - var extraBuffer = new Buffer(expectedString); + let expectedString = ','; + let extraBuffer = new Buffer(expectedString); // We are going to extract multiple buffers - var expectedNumberOfBuffers = 2; + let expectedNumberOfBuffers = 2; // declare the big buffer - var buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length * 2); + let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length * 2); // Fill that new big buffer with buffers openBCIUtilities.samplePacketReal(0).copy(buffer, 0); extraBuffer.copy(buffer, k.OBCIPacketSize); @@ -1609,8 +1611,8 @@ describe('openbci-sdk', function () { * Test the function that routes raw packets for processing */ describe('#_processQualifiedPacket', function () { - var ourBoard; - var funcSpyTimeSyncSet; + let ourBoard; + let funcSpyTimeSyncSet; before(function () { ourBoard = new Cyton({ @@ -1630,7 +1632,7 @@ describe('openbci-sdk', function () { after(() => bluebirdChecks.noPendingPromises()); it('should process a time sync set packet with accel', function () { - var buffer = openBCIUtilities.samplePacketAccelTimeSyncSet(); + let buffer = openBCIUtilities.samplePacketAccelTimeSyncSet(); // Call the function under test ourBoard._processQualifiedPacket(buffer); @@ -1640,7 +1642,7 @@ describe('openbci-sdk', function () { funcSpyTimeSyncSet.should.have.been.calledWith(buffer); }); it('should process a time sync set packet with raw aux', function () { - var buffer = openBCIUtilities.samplePacketRawAuxTimeSyncSet(0); + let buffer = openBCIUtilities.samplePacketRawAuxTimeSyncSet(0); // Call the function under test ourBoard._processQualifiedPacket(buffer); @@ -1650,7 +1652,7 @@ describe('openbci-sdk', function () { funcSpyTimeSyncSet.should.have.been.calledWith(buffer); }); it('should not identify any packet', function () { - var buffer = openBCIUtilities.samplePacket(0); + let buffer = openBCIUtilities.samplePacket(0); // Set the stop byte to some number not yet defined buffer[k.OBCIPacketPositionStopByte] = 0xCF; @@ -1664,26 +1666,26 @@ describe('openbci-sdk', function () { it('should emit a dropped packet on dropped packet', function (done) { // Set to default state ourBoard.previousSampleNumber = -1; - var sampleNumber0 = openBCIUtilities.samplePacket(0); + let sampleNumber0 = openBCIUtilities.samplePacket(0); ourBoard.once('droppedPacket', () => { done(); }); - var sampleNumber2 = openBCIUtilities.samplePacket(2); + let sampleNumber2 = openBCIUtilities.samplePacket(2); // Call the function under test ourBoard._processDataBuffer(sampleNumber0); ourBoard._processDataBuffer(sampleNumber2); }); it('should emit a dropped packet on dropped packet with edge', function (done) { // Set to default state - var count = 0; + let count = 0; ourBoard.previousSampleNumber = 253; - var buf1 = openBCIUtilities.samplePacket(254); - var countFunc = arr => { + let buf1 = openBCIUtilities.samplePacket(254); + let countFunc = arr => { count++; }; ourBoard.on('droppedPacket', countFunc); - var buf2 = openBCIUtilities.samplePacket(0); - var buf3 = openBCIUtilities.samplePacket(1); + let buf2 = openBCIUtilities.samplePacket(0); + let buf3 = openBCIUtilities.samplePacket(1); // Call the function under test ourBoard._processDataBuffer(buf1); ourBoard._processDataBuffer(buf2); @@ -1697,8 +1699,8 @@ describe('openbci-sdk', function () { }); describe('#_processPacketTimeSyncSet', function () { - var timeSyncSetPacket; - var ourBoard; + let timeSyncSetPacket; + let ourBoard; before(() => { ourBoard = new Cyton({ verbose: false @@ -1713,8 +1715,8 @@ describe('openbci-sdk', function () { }); after(() => bluebirdChecks.noPendingPromises()); it('should emit and return bad object if no sync in progress', function () { - var timeSetPacketArrived = ourBoard.time(); - var expectedTimeSyncOffsetMaster = 72; + let timeSetPacketArrived = ourBoard.time(); + let expectedTimeSyncOffsetMaster = 72; ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; ourBoard.once('synced', (syncObj) => { @@ -1729,8 +1731,8 @@ describe('openbci-sdk', function () { expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); }); it('should emit and return bad synced object if no sent confirmation found', function () { - var timeSetPacketArrived = ourBoard.time(); - var expectedTimeSyncOffsetMaster = 72; + let timeSetPacketArrived = ourBoard.time(); + let expectedTimeSyncOffsetMaster = 72; ourBoard.once('synced', (syncObj) => { expect(syncObj).to.have.property('valid', false); expect(syncObj).to.have.property('error', k.OBCIErrorTimeSyncNoComma); @@ -1746,9 +1748,9 @@ describe('openbci-sdk', function () { expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); }); it('should emit and return bad synced object with invalid raw packet', function () { - var timeSetPacketArrived = ourBoard.time(); - var expectedTimeSyncOffsetMaster = 72; - var badPacket; + let timeSetPacketArrived = ourBoard.time(); + let expectedTimeSyncOffsetMaster = 72; + let badPacket; if (k.getVersionNumber(process.version) >= 6) { // from introduced in node version 6.x.x badPacket = Buffer.from(timeSyncSetPacket.slice(0, 30)); @@ -1769,8 +1771,8 @@ describe('openbci-sdk', function () { expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); }); it('should calculate round trip time as the difference between time sent and time set packet arrived', function (done) { - var timeSetPacketArrived = ourBoard.time(); - var expectedRoundTripTime = 20; // ms + let timeSetPacketArrived = ourBoard.time(); + let expectedRoundTripTime = 20; // ms ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); @@ -1784,9 +1786,9 @@ describe('openbci-sdk', function () { ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); }); it('should calculate transmission time as the difference between round trip time and (sentConf - sent) when set arrived - sent conf is larger than threshold', function (done) { - var timeSetPacketArrived = ourBoard.time(); - var expectedRoundTripTime = 20; // ms - var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 1; // 9 ms + let timeSetPacketArrived = ourBoard.time(); + let expectedRoundTripTime = 20; // ms + let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 1; // 9 ms // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! @@ -1802,9 +1804,9 @@ describe('openbci-sdk', function () { ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); }); it('should calculate transmission time as a percentage of round trip time when set arrived - sent conf is smaller than threshold', function (done) { - var timeSetPacketArrived = ourBoard.time(); - var expectedRoundTripTime = 20; // ms - var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS + 1; // 11 ms + let timeSetPacketArrived = ourBoard.time(); + let expectedRoundTripTime = 20; // ms + let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS + 1; // 11 ms // Setup ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found // Make a new object! @@ -1820,11 +1822,11 @@ describe('openbci-sdk', function () { ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); }); it('should calculate offset time as a time packet arrived - transmission time - board time', function (done) { - var timeSetPacketArrived = ourBoard.time(); - var expectedRoundTripTime = 20; // ms - var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms + let timeSetPacketArrived = ourBoard.time(); + let expectedRoundTripTime = 20; // ms + let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms // Set the board time - var boardTime = 5000; + let boardTime = 5000; timeSyncSetPacket.writeInt32BE(boardTime, k.OBCIPacketPositionTimeSyncTimeStart); // Setup @@ -1835,9 +1837,9 @@ describe('openbci-sdk', function () { ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - var expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); + let expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); - var expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; + let expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; ourBoard.once('synced', obj => { expect(obj.timeOffset, 'object timeOffset').to.equal(expectedTimeOffset); @@ -1847,11 +1849,11 @@ describe('openbci-sdk', function () { ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); }); it('should calculate offset time as an average of previous offset times', function (done) { - var timeSetPacketArrived = ourBoard.time(); - var expectedRoundTripTime = 20; // ms - var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms + let timeSetPacketArrived = ourBoard.time(); + let expectedRoundTripTime = 20; // ms + let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms // Set the board time - var boardTime = 5000; + let boardTime = 5000; timeSyncSetPacket.writeInt32BE(boardTime, k.OBCIPacketPositionTimeSyncTimeStart); // Setup @@ -1862,19 +1864,19 @@ describe('openbci-sdk', function () { ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - var expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); + let expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); - var expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; + let expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; - var dif1 = 3; + let dif1 = 3; ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif1); - var dif2 = 1; + let dif2 = 1; ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif2); ourBoard.once('synced', obj => { expect(obj.timeOffset, 'object timeOffset').to.equal(expectedTimeOffset); - var expectedMasterTimeoffset = math.floor((obj.timeOffset + (obj.timeOffset + dif1) + (obj.timeOffset + dif2)) / 3); + let expectedMasterTimeoffset = math.floor((obj.timeOffset + (obj.timeOffset + dif1) + (obj.timeOffset + dif2)) / 3); expect(ourBoard.sync.timeOffsetMaster, 'master time offset').to.equal(expectedMasterTimeoffset); done(); }); @@ -1885,12 +1887,12 @@ describe('openbci-sdk', function () { describe('#time', function () { after(() => bluebirdChecks.noPendingPromises()); it('should use sntp time when sntpTimeSync specified in options', function (done) { - var board = new Cyton({ + let board = new Cyton({ verbose: true, sntpTimeSync: true }); board.on('sntpTimeLock', function () { - var funcSpySntpNow = sinon.spy(board, '_sntpNow'); + let funcSpySntpNow = sinon.spy(board, '_sntpNow'); board.time(); funcSpySntpNow.should.have.been.calledOnce(); funcSpySntpNow.restore(); @@ -1899,10 +1901,10 @@ describe('openbci-sdk', function () { }); }); it('should use Date.now() for time when sntpTimeSync is not specified in options', function () { - var board = new Cyton({ + let board = new Cyton({ verbose: true }); - var funcSpySntpNow = sinon.spy(board, '_sntpNow'); + let funcSpySntpNow = sinon.spy(board, '_sntpNow'); board.time(); @@ -1913,7 +1915,7 @@ describe('openbci-sdk', function () { funcSpySntpNow = null; }); it('should emit sntpTimeLock event after sycned with ntp server', function (done) { - var board = new Cyton({ + let board = new Cyton({ verbose: true, sntpTimeSync: true }); @@ -1927,7 +1929,7 @@ describe('openbci-sdk', function () { describe('#sntpStart', function () { after(() => bluebirdChecks.noPendingPromises()); it('should be able to start ntp server', () => { - var board = new Cyton(); + let board = new Cyton(); expect(board.sntp.isLive()).to.be.false(); return Promise.all([ board.sntpStart() @@ -1944,7 +1946,7 @@ describe('openbci-sdk', function () { }); describe('#sntpStop', function () { this.timeout(5000); - var board; + let board; before(done => { board = new Cyton({ sntpTimeSync: true @@ -1975,7 +1977,7 @@ describe('openbci-sdk', function () { describe('#sntpGetOffset', function () { after(() => bluebirdChecks.noPendingPromises()); it('should get the sntp offset', function (done) { - var board = new Cyton({ + let board = new Cyton({ sntpTimeSync: true }); board.once('sntpTimeLock', () => { @@ -1987,7 +1989,7 @@ describe('openbci-sdk', function () { }); }); describe('#_processParseBufferForReset', function () { - var ourBoard; + let ourBoard; before(() => { ourBoard = new Cyton({ @@ -2009,7 +2011,7 @@ describe('openbci-sdk', function () { after(() => bluebirdChecks.noPendingPromises()); it('should recognize firmware version 1 with no daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 LIS3DH Device ID: 0x38422$$$`); @@ -2021,7 +2023,7 @@ LIS3DH Device ID: 0x38422$$$`); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); it('should recognize firmware version 1 with daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 On Daisy ADS1299 Device ID: 0xFFFFF LIS3DH Device ID: 0x38422 @@ -2035,7 +2037,7 @@ $$$`); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); }); it('should recognize firmware version 2 with no daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 LIS3DH Device ID: 0x38422 Firmware: v2.0.0 @@ -2049,7 +2051,7 @@ $$$`); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); it('should recognize firmware version 3 with no daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 LIS3DH Device ID: 0x38422 Firmware: v3.0.1 @@ -2063,7 +2065,7 @@ $$$`); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); }); it('should recognize firmware version 2 with daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 On Daisy ADS1299 Device ID: 0xFFFFF LIS3DH Device ID: 0x38422 @@ -2078,7 +2080,7 @@ $$$`); expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); }); it('should recognize firmware version 3 with daisy', () => { - var buf = new Buffer(`OpenBCI V3 Simulator + let buf = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 On Daisy ADS1299 Device ID: 0xFFFFF LIS3DH Device ID: 0x38422 @@ -2109,7 +2111,7 @@ $$$`); after(() => bluebirdChecks.noPendingPromises()); describe('#OBCIParsingReset', function () { - var _processParseBufferForResetSpy; + let _processParseBufferForResetSpy; before(() => { _processParseBufferForResetSpy = sinon.spy(ourBoard, '_processParseBufferForReset'); }); @@ -2117,11 +2119,11 @@ $$$`); _processParseBufferForResetSpy.reset(); }); it('should wait till EOT ($$$) before starting parse', function () { - var buf1 = new Buffer(`OpenBCI V3 Simulator + let buf1 = new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 `); - var buf2 = new Buffer(`LIS3DH Device ID: `); - var buf3 = new Buffer(`0x38422 + let buf2 = new Buffer(`LIS3DH Device ID: `); + let buf3 = new Buffer(`0x38422 $$$`); // Fake a soft reset send @@ -2149,7 +2151,7 @@ $$$`); }); describe('#OBCIParsingTimeSyncSent', function () { - // var spy + // let spy before(() => { // spy = sinon.spy(ourBoard.openBCIUtilities,"isTimeSyncSetConfirmationInBuffer") }); @@ -2160,7 +2162,7 @@ $$$`); }); it('should call to find the time sync set character in the buffer', function () { // Verify the log event is called - var buf = new Buffer(','); + let buf = new Buffer(','); // Call the processBytes function ourBoard._processBytes(buf); // Verify the function was called @@ -2172,7 +2174,7 @@ $$$`); emitted = true; }); // Make a new buffer - var buf1 = openBCIUtilities.samplePacketReal(1); + let buf1 = openBCIUtilities.samplePacketReal(1); // Send the buffer in ourBoard._processBytes(buf1); expect(ourBoard.buffer).to.be.null(); @@ -2181,7 +2183,7 @@ $$$`); it('should clear the buffer after a time sync set packet', function () { let emitted = false; // Verify the log event is called - var buf = new Buffer(','); + let buf = new Buffer(','); // Call the processBytes function ourBoard._processBytes(buf); // Verify the function was called @@ -2193,15 +2195,15 @@ $$$`); emitted = true; }); // Make a new buffer - var buf1 = openBCIUtilities.samplePacketAccelTimeSyncSet(1); + let buf1 = openBCIUtilities.samplePacketAccelTimeSyncSet(1); // Send the buffer in ourBoard._processBytes(buf1); expect(ourBoard.buffer).to.be.null(); expect(emitted).to.be.true(); }); it('should call to find the time sync set character in the buffer after packet', function () { - var buf1 = openBCIUtilities.samplePacket(); - var buf2 = new Buffer(','); + let buf1 = openBCIUtilities.samplePacket(); + let buf2 = new Buffer(','); // Call the processBytes function ourBoard._processBytes(Buffer.concat([buf1, buf2], buf1.length + 1)); @@ -2212,15 +2214,15 @@ $$$`); // ourBoard.buffer.length.should.equal(0) }); it('should find time sync and emit two samples', function (done) { - var buf1 = openBCIUtilities.samplePacket(250); - var buf2 = new Buffer([0x2C]); - var buf3 = openBCIUtilities.samplePacket(251); + let buf1 = openBCIUtilities.samplePacket(250); + let buf2 = new Buffer([0x2C]); + let buf3 = openBCIUtilities.samplePacket(251); - var inputBuf = Buffer.concat([buf1, buf2, buf3], buf1.byteLength + 1 + buf3.byteLength); + let inputBuf = Buffer.concat([buf1, buf2, buf3], buf1.byteLength + 1 + buf3.byteLength); - var sampleCounter = 0; + let sampleCounter = 0; - var newSample = sample => { + let newSample = sample => { // console.log(`sample ${JSON.stringify(sample)}`) if (sampleCounter === 0) { sample.sampleNumber.should.equal(250); @@ -2241,9 +2243,9 @@ $$$`); ourBoard._processBytes(inputBuf); }); it('should not find the packet if in packet', () => { - var buf1 = openBCIUtilities.samplePacket(250); + let buf1 = openBCIUtilities.samplePacket(250); buf1[4] = 0x2C; // Inject a false packet - var buf2 = openBCIUtilities.samplePacket(251); + let buf2 = openBCIUtilities.samplePacket(251); // Call the processBytes function ourBoard._processBytes(Buffer.concat([buf1, buf2], buf1.length + buf2.length)); @@ -2257,8 +2259,8 @@ $$$`); ourBoard.curParsingMode = k.OBCIParsingNormal; }); it('should emit a sample when inserted', function (done) { - var expectedSampleNumber = 0; - var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + let expectedSampleNumber = 0; + let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); // Declare the event emitter prior to calling function ourBoard.once('sample', sample => { @@ -2271,23 +2273,23 @@ $$$`); done(); }); it('should get three packets even if one was sent in the last data emit', function () { - var expectedSampleNumber = 0; - var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); + let expectedSampleNumber = 0; + let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // Pretend that half of buf1 got sent in the first serial flush // and that the last half of it will arrive a lil later - var splitPoint = 15; + let splitPoint = 15; if (k.getVersionNumber(process.version) >= 6) { // from introduced in node version 6.x.x ourBoard.buffer = Buffer.from(buf1.slice(0, splitPoint)); } else { ourBoard.buffer = new Buffer(buf1.slice(0, splitPoint)); } - var dataBuf = Buffer.concat([buf1.slice(splitPoint), buf2, buf3]); + let dataBuf = Buffer.concat([buf1.slice(splitPoint), buf2, buf3]); - var sampleCounter = 0; - var newSample = sample => { + let sampleCounter = 0; + let newSample = sample => { if (sampleCounter === expectedSampleNumber) { expect(sample.sampleNumber).to.equal(expectedSampleNumber); } else if (sampleCounter === expectedSampleNumber + 1) { @@ -2304,16 +2306,16 @@ $$$`); expect(ourBoard.buffer).to.be.null(); }); it('should keep extra data in the buffer', function () { - var expectedSampleNumber = 0; - var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); + let expectedSampleNumber = 0; + let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // Pretend that half of buf1 got sent in the first serial flush // and that the last half of it will arrive a lil later - var splitPoint = 15; + let splitPoint = 15; ourBoard['buffer'] = null; - var bufFirstHalf, bufLastHalf; + let bufFirstHalf, bufLastHalf; if (k.getVersionNumber(process.version) >= 6) { // from introduced in node version 6.x.x bufFirstHalf = Buffer.from(buf3.slice(0, splitPoint)); @@ -2323,8 +2325,8 @@ $$$`); bufLastHalf = new Buffer(buf3.slice(splitPoint)); } - var sampleCounter = 0; - var newSample = sample => { + let sampleCounter = 0; + let newSample = sample => { if (sampleCounter === expectedSampleNumber) { expect(sample.sampleNumber).to.equal(expectedSampleNumber); } else if (sampleCounter === expectedSampleNumber + 1) { @@ -2348,16 +2350,16 @@ $$$`); ourBoard.buffer = new Buffer([0xA0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xC0]); // New buffer incoming - var expectedSampleNumber = 1; - var buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - var buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - var buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); + let expectedSampleNumber = 1; + let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); + let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); + let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); // New data incoming! - var dataBuf = Buffer.concat([buf1, buf2, buf3]); + let dataBuf = Buffer.concat([buf1, buf2, buf3]); - var sampleCounter = expectedSampleNumber; - var newSample = sample => { + let sampleCounter = expectedSampleNumber; + let newSample = sample => { if (sampleCounter === expectedSampleNumber) { expect(sample.sampleNumber).to.equal(expectedSampleNumber); } else if (sampleCounter === expectedSampleNumber + 1) { @@ -2381,9 +2383,9 @@ $$$`); ourBoard.curParsingMode = k.OBCIParsingEOT; }); it("should emit the 'eot' event", function (done) { - var buf = new Buffer('Tacos are amazing af$$$'); + let buf = new Buffer('Tacos are amazing af$$$'); - var eotEvent = data => { + let eotEvent = data => { expect(bufferEqual(data, buf)).to.be.true(); ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); done(); @@ -2394,10 +2396,10 @@ $$$`); ourBoard._processBytes(buf); }); it("should emit the 'eot' event even if stuff comes in two serial flushes", function (done) { - var buf1 = new Buffer('Tacos are '); - var buf2 = new Buffer('amazing af$$$'); + let buf1 = new Buffer('Tacos are '); + let buf2 = new Buffer('amazing af$$$'); - var eotEvent = data => { + let eotEvent = data => { bufferEqual(data, Buffer.concat([buf1, buf2], buf1.length + buf2.length)).should.be.true(); ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); done(); @@ -2412,7 +2414,7 @@ $$$`); }); describe('#_finalizeNewSampleForDaisy', function () { - var ourBoard, randomSampleGenerator, sampleEvent, failTimeout; + let ourBoard, randomSampleGenerator, sampleEvent, failTimeout; before(() => { ourBoard = new Cyton({ verbose: true @@ -2420,7 +2422,7 @@ $$$`); randomSampleGenerator = openBCIUtilities.randomSample(k.OBCINumberOfChannelsCyton, k.OBCISampleRate250, false, 'none'); }); beforeEach(() => { - // Clear the global var + // Clear the global let ourBoard._lowerChannelsSampleObject = null; ourBoard.info.missedPackets = 0; }); @@ -2435,21 +2437,21 @@ $$$`); } }); after(() => bluebirdChecks.noPendingPromises()); - it('should store the sample to a global variable for next time', () => { - var oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) + it('should store the sample to a global letiable for next time', () => { + let oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) // Call the function under test ourBoard._finalizeNewSampleForDaisy(oddSample); - // Check to make sure the variable is stored + // Check to make sure the letiable is stored expect(ourBoard._lowerChannelsSampleObject).to.equal(oddSample); }); it('should emit a sample on even sample if odd was before', function (done) { - var oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) - var evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) + let oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) + let evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) // The function to be called when sample event is fired - var sampleEvent = (sample) => { + let sampleEvent = (sample) => { // test pass here done(); }; @@ -2469,7 +2471,7 @@ $$$`); }, 5); // 5ms should be plenty of time }); it('should not emit a sample if there is no lower sample object and this is an even sample number', function (done) { - var evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) + let evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) // The function to be called when sample event is fired sampleEvent = (sample) => { @@ -2494,8 +2496,8 @@ $$$`); }, 5); // 5ms should be plenty of time }); it('should not emit a sample if back to back odd samples', function (done) { - var oddSample1 = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) - var oddSample2 = randomSampleGenerator(2); // Previous was 0, so the next one will be 1 (odd) + let oddSample1 = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) + let oddSample2 = randomSampleGenerator(2); // Previous was 0, so the next one will be 1 (odd) // The function to be called when sample event is fired sampleEvent = (sample) => { @@ -2574,9 +2576,9 @@ $$$`); xdescribe('#hardwareValidation', function () { this.timeout(20000); // long timeout for pleanty of stream time :) - var runHardwareValidation = true; - var wstream; - var board; + let runHardwareValidation = true; + let wstream; + let board; before(function (done) { if (masterPortName === k.OBCISimulatorPortName) { runHardwareValidation = false; @@ -2662,7 +2664,7 @@ $$$`); }); describe('#daisy', function () { - var ourBoard; + let ourBoard; this.timeout(4000); before(function (done) { ourBoard = new Cyton({ @@ -2672,7 +2674,7 @@ describe('#daisy', function () { simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); - var useSim = () => { + let useSim = () => { ourBoard.simulatorEnable() .then(() => { console.log(`has daisy module: ${ourBoard.options.simulatorDaisyModuleAttached}`); @@ -2715,13 +2717,13 @@ describe('#daisy', function () { }); after(() => bluebirdChecks.noPendingPromises()); it('can get samples with channel array of length 16 if daisy', function (done) { - var numberOfSamples = 130; - var sampleCount = 0; + let numberOfSamples = 130; + let sampleCount = 0; if (ourBoard.info.boardType !== k.OBCIBoardDaisy) { return done(); } - var samp = sample => { + let samp = sample => { expect(sample.channelData.length).to.equal(k.OBCINumberOfChannelsDaisy); if (sampleCount <= numberOfSamples) { sampleCount++; @@ -2743,7 +2745,7 @@ describe('#daisy', function () { }); describe('#syncWhileStreaming', function () { - var ourBoard; + let ourBoard; this.timeout(4000); before(function (done) { ourBoard = new Cyton({ @@ -2751,7 +2753,7 @@ describe('#syncWhileStreaming', function () { simulatorFirmwareVersion: 'v2', simulatorFragmentation: k.OBCISimulatorFragmentationRandom }); - var useSim = () => { + let useSim = () => { ourBoard.simulatorEnable() .then(() => { console.log(`sim firmware version: ${ourBoard.options.simulatorFirmwareVersion}`); @@ -2805,13 +2807,13 @@ describe('#syncWhileStreaming', function () { describe('#syncClocks', function () { this.timeout(4000); it('can sync while streaming', done => { - var syncAfterSamples = 50; - var notSynced = true; - var syncFunc = obj => { + let syncAfterSamples = 50; + let notSynced = true; + let syncFunc = obj => { ourBoard.removeListener('sample', samp); done(); }; - var samp = sample => { + let samp = sample => { if (sample.sampleNumber >= syncAfterSamples && notSynced) { notSynced = false; // Call the first one @@ -2830,8 +2832,8 @@ describe('#syncWhileStreaming', function () { describe('#syncClocksFull', function () { this.timeout(4000); it('can run a full clock sync', done => { - var notSynced = true; - var sampleFun = sample => { + let notSynced = true; + let sampleFun = sample => { if (notSynced) { notSynced = false; // Call the first one @@ -2856,14 +2858,14 @@ describe('#syncWhileStreaming', function () { }); describe('#syncErrors', function () { - var ourBoard; + let ourBoard; this.timeout(4000); before(function (done) { ourBoard = new Cyton({ verbose: true, simulatorFirmwareVersion: 'v2' }); - var useSim = () => { + let useSim = () => { ourBoard.simulatorEnable() .then(() => { return ourBoard.connect(k.OBCISimulatorPortName); @@ -2909,8 +2911,8 @@ describe('#syncErrors', function () { }); describe('#syncClocksFull', function () { it('should reject syncClocksFull request because of timeout', done => { - var notSynced = true; - var sampleFun = sample => { + let notSynced = true; + let sampleFun = sample => { if (notSynced) { notSynced = false; // Call the first one From b10fa1b4ec6f19bc4e3206a3d906af5de0924290 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 15 Aug 2017 17:42:11 -0400 Subject: [PATCH 34/42] FIX: changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 8e1e3bb..7668bd5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# 2.1.2 +# 2.1.3 ### Enhancements From d256e87495bc45b2e971a9cf05a9f41db7d58254 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 15 Aug 2017 22:03:09 -0400 Subject: [PATCH 35/42] FIX: Forgot to make it snazzy --- openBCICyton.js | 4 +--- test/openBCICyton-test.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/openBCICyton.js b/openBCICyton.js index 9f290c4..f110278 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1210,7 +1210,6 @@ Cyton.prototype.syncRegisterSettings = function () { // Remove the timeout! clearTimeout(badCommsTimeout); badCommsTimeout = null; - }); this.curParsingMode = k.OBCIParsingEOT; @@ -1218,9 +1217,8 @@ Cyton.prototype.syncRegisterSettings = function () { .catch((err) => { clearTimeout(badCommsTimeout); reject(err); - }) + }); }); - }; /** diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 7a48ac9..73932c4 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -3,7 +3,7 @@ const bluebirdChecks = require('./bluebirdChecks'); const sinon = require('sinon'); const chai = require('chai'); const expect = chai.expect; -const should = chai.should(); // eslint-disable-line no-unused-lets +const should = chai.should(); // eslint-disable-line no-unused-vars const Cyton = require('../openBCICyton'); const OpenBCIUtilities = require('openbci-utilities'); const openBCIUtilities = OpenBCIUtilities.Utilities; @@ -1362,7 +1362,6 @@ describe('openbci-sdk', function () { done(); }).catch(done); }); - }); describe('#testSignal', function () { it('should call the write function with proper command for dc test signal', function (done) { From 74c4d9719071abe297cc29d8d75cd160592e61f9 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 16 Aug 2017 11:13:51 -0400 Subject: [PATCH 36/42] ADD: syncRegisterSettings now resolves channel settings object --- README.md | 2 +- changelog.md | 7 +++++++ openBCICyton.js | 2 +- package.json | 4 ++-- test/openBCICyton-test.js | 3 ++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ec11fac..8e3224d 100644 --- a/README.md +++ b/README.md @@ -1073,7 +1073,7 @@ ourBoard.connect(portName) Syncs the internal channel settings object with a cyton, this will take about over a second because there are delays between the register reads in the firmware. -**_Returns_** a promise, fulfilled once the channel settings have been synced and reject on error. +**_Returns_** a promise, fulfilled with channel settings array, where each element is a standard channel setting object, once the channel settings have been synced and reject on error. #### .testSignal(signal) diff --git a/changelog.md b/changelog.md index 7668bd5..9e9de13 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +# 2.1.4 + +### Enhancements + +* syncRegisterSettings now resolves channel setting object +* Bumped openbci-utilities to v0.1.5 + # 2.1.3 ### Enhancements diff --git a/openBCICyton.js b/openBCICyton.js index f110278..b2342fb 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -1203,7 +1203,7 @@ Cyton.prototype.syncRegisterSettings = function () { this._rawDataPacketToSample.data = data; try { obciUtils.syncChannelSettingsWithRawData(this._rawDataPacketToSample); - resolve(); + resolve(this._rawDataPacketToSample.channelSettings); } catch (e) { reject(e); } diff --git a/package.json b/package.json index 1710cfc..ea1a0e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.1.3", + "version": "2.1.4", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -21,7 +21,7 @@ "gaussian": "^1.0.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", - "openbci-utilities": "^0.1.4", + "openbci-utilities": "^0.1.5", "performance-now": "^2.1.0", "safe-buffer": "^5.1.1", "serialport": "4.0.7", diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index 73932c4..db51552 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -1358,7 +1358,8 @@ describe('openbci-sdk', function () { }); it('should send the correct register setting', function (done) { ourBoard.syncRegisterSettings() - .then(() => { + .then((channelSettings) => { + expect(channelSettings).to.not.equal(null); done(); }).catch(done); }); From a4e0edbb8683ce4c8aefe16975c0fd1a3c725025 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Mon, 21 Aug 2017 15:40:27 -0500 Subject: [PATCH 37/42] FIX: Sending channel setting commands might not work --- changelog.md | 8 ++++++ examples/getStreaming/getStreaming.js | 14 ++++++++- openBCICyton.js | 41 +++++++-------------------- package.json | 2 +- test/openBCICyton-test.js | 2 +- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/changelog.md b/changelog.md index 9e9de13..9dc5bd6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +# 2.2.0 + +### Bug Fixes + +* Calling sdStart and stop would change the writeOutDelay +* Timeout for v1 was set to 10 ms instead of 50 ms. +* Timeout for v2+ was set to 0 ms instead of 10 ms. + # 2.1.4 ### Enhancements diff --git a/examples/getStreaming/getStreaming.js b/examples/getStreaming/getStreaming.js index 6b1fbb6..424b012 100644 --- a/examples/getStreaming/getStreaming.js +++ b/examples/getStreaming/getStreaming.js @@ -28,7 +28,19 @@ ourBoard.autoFindOpenBCIBoard().then(portName => { ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` .then(() => { ourBoard.on('ready', () => { - ourBoard.streamStart(); + ourBoard.syncRegisterSettings() + .then((cs) => { + return ourBoard.streamStart(); + }) + .catch((err) => { + console.log('err', err); + return ourBoard.streamStart(); + }) + .catch((err) => { + console.log('fatal err', err); + process.exit(0); + }); + ourBoard.on('sample', (sample) => { /** Work with sample */ for (let i = 0; i < ourBoard.numberOfChannels(); i++) { diff --git a/openBCICyton.js b/openBCICyton.js index b2342fb..4293ff2 100644 --- a/openBCICyton.js +++ b/openBCICyton.js @@ -545,7 +545,7 @@ Cyton.prototype.write = function (dataToWrite) { /** * @description Should be used to send data to the board - * @param data {Buffer} - The data to write out + * @param data {Buffer | Buffer2} - The data to write out * @returns {Promise} if signal was able to be sent * @author AJ Keller (@pushtheworldllc) */ @@ -1277,7 +1277,12 @@ Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType .then((val) => { arrayOfCommands = val.commandArray; this._rawDataPacketToSample.channelSettings[channelNumber - 1] = val.newChannelSettingsObject; - return this.write(arrayOfCommands); + if (this.usingAtLeastVersionTwoFirmware()) { + const buf = Buffer.from(arrayOfCommands.join('')); + return this._writeAndDrain(buf); + } else { + return this.write(arrayOfCommands); + } }).then(resolve, reject); }); }; @@ -1684,7 +1689,7 @@ Cyton.prototype.sdStart = function (recordingDuration) { if (!this.isStreaming()) { this.curParsingMode = k.OBCIParsingEOT; } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; this.write(command).then(resolve, reject); }) .catch(err => reject(err)); @@ -1704,7 +1709,7 @@ Cyton.prototype.sdStop = function () { if (!this.isStreaming()) { this.curParsingMode = k.OBCIParsingEOT; } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + // this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; this.write(k.OBCISDLogStop).then(resolve, reject); }); }; @@ -1928,10 +1933,10 @@ Cyton.prototype._processParseBufferForReset = function (dataBuffer) { } else { this.info.firmware = k.OBCIFirmwareV3; } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; } else { this.info.firmware = k.OBCIFirmwareV1; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; + this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong; } }; @@ -2302,28 +2307,4 @@ Cyton.prototype.debugSession = function () { this.printPacketsBad(); }; -/** - * @description To pretty print the info recieved on a Misc Register Query (printRegisterSettings) - * @param channelSettingsObj - */ -/* istanbul ignore next */ -Cyton.prototype.debugPrintChannelSettings = function (channelSettingsObj) { - console.log('-- Channel Settings Object --'); - var powerState = 'OFF'; - if (channelSettingsObj.POWER_DOWN.toString().localeCompare('1')) { - powerState = 'ON'; - } - console.log('---- POWER STATE: ' + powerState); - console.log('-- END --'); -}; - -/** - * @description Quickly determine if a channel is on or off from a channelSettingObject. Most likely from a getChannelSettings call. - * @param channelSettingsObject - * @returns {boolean} - */ -Cyton.prototype.channelIsOnFromChannelSettingsObject = function (channelSettingsObject) { - return channelSettingsObject.POWER_DOWN.toString().localeCompare('1') === 1; -}; - module.exports = Cyton; diff --git a/package.json b/package.json index ea1a0e0..4485d1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.1.4", + "version": "2.2.0", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js index db51552..89fa6df 100644 --- a/test/openBCICyton-test.js +++ b/test/openBCICyton-test.js @@ -553,7 +553,7 @@ describe('openbci-sdk', function () { setTimeout(() => { console.log.should.have.been.calledWithMatch(k.OBCIStreamStop); done(); - }, 20); + }, k.OBCIWriteIntervalDelayMSLong * 2); }); it('outputs a packet when received', done => { console.log.reset(); From c50cd8483acb8220491ee35106ff327ddbc02294 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 4 Oct 2017 19:37:48 -0400 Subject: [PATCH 38/42] REFACTOR: For adding in ganglion and wifi support --- changelog.md | 12 + package.json | 31 +- test/bluebirdChecks.js | 142 -- test/openBCICyton-Impedance-test.js | 608 ------ test/openBCICyton-radio-test.js | 950 --------- test/openBCICyton-test.js | 2937 --------------------------- test/timingEventsAsPromises.js | 108 - 7 files changed, 16 insertions(+), 4772 deletions(-) delete mode 100644 test/bluebirdChecks.js delete mode 100644 test/openBCICyton-Impedance-test.js delete mode 100644 test/openBCICyton-radio-test.js delete mode 100644 test/openBCICyton-test.js delete mode 100644 test/timingEventsAsPromises.js diff --git a/changelog.md b/changelog.md index 9dc5bd6..ad88fb9 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +# 3.0.0 + +This adds ganglion, wifi, and cyton support to the `openbci` main library. + +If you only need cyton over serial and so not want the additional support, please see [`openbci-cyton`](https://www.npmjs.com/package/openbci-cyton). + +# 2.2.1 + +### Enhancements + +* Bumped openbci-utilities to v0.2.0 for new accel data support + # 2.2.0 ### Bug Fixes diff --git a/package.json b/package.json index 4485d1d..e255437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "2.2.0", + "version": "2.2.1", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "index.js", "scripts": { @@ -16,36 +16,13 @@ "author": "AJ Keller (www.openbci.com)", "license": "MIT", "dependencies": { - "buffer-equal": "^1.0.0", - "clone": "^2.0.0", - "gaussian": "^1.0.0", - "lodash": "^4.17.4", - "mathjs": "^3.14.2", - "openbci-utilities": "^0.1.5", - "performance-now": "^2.1.0", - "safe-buffer": "^5.1.1", - "serialport": "4.0.7", - "sntp": "^2.0.2", - "streamsearch": "^0.1.2" + "openbci-cyton": "^1.0.2", + "openbci-utilities": "^0.2.0", + "openbci-wifi": "^0.1.2" }, "directories": { "test": "test" }, - "devDependencies": { - "bluebird": "3.5.0", - "chai": "^4.1.0", - "chai-as-promised": "^7.1.1", - "codecov": "^2.1.0", - "dirty-chai": "^2.0.1", - "istanbul": "^0.4.4", - "mocha": "^3.4.2", - "sandboxed-module": "^2.0.3", - "semistandard": "^11.0.0", - "sinon": "^1.17.7", - "sinon-as-promised": "^4.0.3", - "sinon-chai": "^2.11.0", - "snazzy": "^7.0.0" - }, "repository": { "type": "git", "url": "git+https://github.com/openbci/openbci_nodejs.git" diff --git a/test/bluebirdChecks.js b/test/bluebirdChecks.js deleted file mode 100644 index 2b792ac..0000000 --- a/test/bluebirdChecks.js +++ /dev/null @@ -1,142 +0,0 @@ -var timingEventsAsPromises = require('./timingEventsAsPromises'); -exports.BluebirdPromise = require('bluebird'); -exports.PromiseIgnored = global.Promise; - -// Enable bluebird for all promise usage during tests only -// Fails tests for issues bluebird finds -// Exports a function to list all promises (getPendingPromises) -// Exports a function to verify no promises pending within a timeout (noPendingPromises) - -exports.BluebirdPromise.config({ - // TODO: wForgottenReturn is disabled because timingEventsAsPromises triggers it; find a workaround - warnings: { wForgottenReturn: false }, - longStackTraces: true, - monitoring: true, - cancellation: true -}); - -// nextTick conveniently not instrumented by timingEventsAsPromises -exports.BluebirdPromise.setScheduler(process.nextTick); - -// unhandled rejections become test failures -process.on('unhandledRejection', (reason, promise) => { - if (!(reason instanceof Error)) { - reason = new Error('unhandled promise rejection: ' + reason); - } else { - reason.message = 'unhandled promise rejection: ' + reason.message; - } - process.nextTick(() => { throw reason; }); -}); - -// // warnings become test failures -// process.on('warning', (warning) => { -// var error = new Error(warning); -// process.nextTick(() => { throw error; }); -// }); - -// provide access to all currently pending promises -var pendingPromises = {}; -var promiseId = 0; -var nested = 0; - -function promiseCreationHandler (promise) { - // promise created already resolved; ignore - if (!promise.isPending()) return; - - // need to create another promise to get access to the extended stack trace - // nested detects if we are inside our own dummy promise - ++nested; - if (nested === 1) { - // not the dummy promise - promise.___id = ++promiseId; - // store promise details - var error = new Error('Promise ' + promise.___id + ' is still pending'); - var entry = { - promise: promise, - id: promise.___id, - error: error - }; - pendingPromises[promise.___id] = entry; - // extract stack trace by rejecting an error; bluebird fills in expanded stack - exports.BluebirdPromise.reject(error).catch(error => { - entry.error = error; - entry.stack = error.stack; - }); - } else { - promise.___nested = nested; - } - --nested; -} -process.on('promiseCreated', promiseCreationHandler); - -function promiseDoneHandler (promise) { - if (promise.___nested) return; - delete pendingPromises[promise.___id]; -} -process.on('promiseFulfilled', promiseDoneHandler); -process.on('promiseRejected', promiseDoneHandler); -process.on('promiseResolved', promiseDoneHandler); -process.on('promiseCancelled', promiseDoneHandler); - -exports.getPendingPromises = function () { - var ret = []; - for (var promise in pendingPromises) { - ret.push(pendingPromises[promise]); - } - return ret; -}; - -exports.noPendingPromises = function (milliseconds) { - if (!milliseconds) milliseconds = 0; - - return new exports.PromiseIgnored((resolve, reject) => { - function waited100 () { - var promises = exports.getPendingPromises(); - - if (promises.length === 0) { - return resolve(); - } - - if (milliseconds > 0) { - milliseconds -= 100; - return timingEventsAsPromises.setTimeoutIgnored(waited100, 100); - } - - // timed out, but promises remaining: cancel all - - console.log(promises.length + ' promises still pending'); - - promises.forEach(promise => { - promise.promise.cancel(); - }); - - // report one - reject(promises[0].error); - } - - timingEventsAsPromises.setTimeoutIgnored(waited100, 0); - }); -}; - -// now instrument the Promise object itself to always use a simplified version of bluebird -// bluebird is composed inside a bare-bones Promise object providing only the official calls - -global.Promise = function (handler) { - this._promise = new exports.BluebirdPromise(handler); -}; - -// compose class methods -['all', 'race', 'reject', 'resolve'].forEach(classMethod => { - global.Promise[classMethod] = function () { - return exports.BluebirdPromise[classMethod].apply(exports.BluebirdPromise, [].slice.call(arguments)); - }; - Object.defineProperty(global.Promise[classMethod], 'name', { value: 'Promise.' + classMethod }); -}); - -// compose object methods -['then', 'catch'].forEach(objectMethod => { - global.Promise.prototype[objectMethod] = function () { - return this._promise[objectMethod].apply(this._promise, [].slice.call(arguments)); - }; - Object.defineProperty(global.Promise.prototype[objectMethod], 'name', { value: 'Promise.' + objectMethod }); -}); diff --git a/test/openBCICyton-Impedance-test.js b/test/openBCICyton-Impedance-test.js deleted file mode 100644 index ee685ed..0000000 --- a/test/openBCICyton-Impedance-test.js +++ /dev/null @@ -1,608 +0,0 @@ -'use strict'; -const bluebirdChecks = require('./bluebirdChecks'); -const chai = require('chai'); -const should = chai.should(); // eslint-disable-line no-unused-vars -const OpenBCICyton = require('../openBCICyton'); -const openBCIUtilities = require('openbci-utilities').Utilities; -const k = require('openbci-utilities').Constants; - -const chaiAsPromised = require('chai-as-promised'); -const sinonChai = require('sinon-chai'); -chai.use(chaiAsPromised); -chai.use(sinonChai); - -describe('#impedanceTesting', function () { - let ourBoard; - this.timeout(20000); - - before(function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - var useSim = () => { - return ourBoard.simulatorEnable().then(() => { - return ourBoard.connect(k.OBCISimulatorPortName); - }); - }; - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - return ourBoard.connect(portName); - }) - .catch(() => { - return useSim(); - }) - .then(() => { - console.log('connected'); - }) - .catch(err => { - console.log('Error: ' + err); - }); - - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - setTimeout(() => { - done(); - }, 100); // give some time for the stream command to be sent - }) - .catch(err => { - console.log(err); - done(err); - }); - }); - }); - after(done => { - if (ourBoard.isConnected()) { - ourBoard.disconnect() - .then(() => { - done(); - }) - .catch(err => { - done(err); - }); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - - describe('#impedanceTestAllChannels', function () { - var impedanceArray = []; - - before(function (done) { - ourBoard.once('impedanceArray', arr => { - impedanceArray = arr; - console.log(impedanceArray); - done(); - }); - ourBoard.impedanceTestAllChannels(); - }); - describe('#channel1', function () { - it('has valid channel number', function () { - impedanceArray[0].channel.should.be.equal(1); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[0].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[0].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[0].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[0].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel2', function () { - it('has valid channel number', function () { - impedanceArray[1].channel.should.be.equal(2); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[1].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[1].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[1].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[1].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel3', function () { - it('has valid channel number', function () { - impedanceArray[2].channel.should.be.equal(3); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[2].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[2].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[2].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[2].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel4', function () { - it('has valid channel number', function () { - impedanceArray[3].channel.should.be.equal(4); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[3].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[3].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[3].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[3].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel5', function () { - it('has valid channel number', function () { - impedanceArray[4].channel.should.be.equal(5); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[4].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[4].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[4].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[4].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel6', function () { - it('has valid channel number', function () { - impedanceArray[5].channel.should.be.equal(6); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[5].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[5].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[5].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[5].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel7', function () { - it('has valid channel number', function () { - impedanceArray[6].channel.should.be.equal(7); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[6].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[6].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[6].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[6].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel8', function () { - it('has valid channel number', function () { - impedanceArray[7].channel.should.be.equal(8); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[7].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[7].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[7].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[7].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - }); - describe('#impedanceTestChannelsRejects', function () { - it('rejects when it does not get an array', function (done) { - ourBoard.impedanceTestChannels('taco').should.be.rejected.and.notify(done); - }); - - it('rejects when it array length does not match number of channels', function (done) { - ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', 'p', 'b']).should.be.rejected.and.notify(done); - }); - }); - describe('#impedanceTestChannels', function () { - var impedanceArray = []; - - before(function (done) { - ourBoard.once('impedanceArray', arr => { - impedanceArray = arr; - done(); - }); - ourBoard.impedanceArray[0] = openBCIUtilities.impedanceObject(1); - ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', 'p', 'b', 'B']).catch(err => done(err)); - }); - describe('#channel1', function () { - it('has valid channel number', function () { - impedanceArray[0].channel.should.be.equal(1); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[0].P.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[0].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[0].N.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[0].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel2', function () { - it('has valid channel number', function () { - impedanceArray[1].channel.should.be.equal(2); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[1].P.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[1].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[1].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[1].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel3', function () { - it('has valid channel number', function () { - impedanceArray[2].channel.should.be.equal(3); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[2].P.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[2].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[2].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[2].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel4', function () { - it('has valid channel number', function () { - impedanceArray[3].channel.should.be.equal(4); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[3].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[3].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[3].N.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[3].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel5', function () { - it('has valid channel number', function () { - impedanceArray[4].channel.should.be.equal(5); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[4].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[4].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[4].N.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[4].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel6', function () { - it('has valid channel number', function () { - impedanceArray[5].channel.should.be.equal(6); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[5].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[5].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[5].N.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceArray[5].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel7', function () { - it('has valid channel number', function () { - impedanceArray[6].channel.should.be.equal(7); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[6].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[6].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[6].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[6].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - describe('#channel8', function () { - it('has valid channel number', function () { - impedanceArray[7].channel.should.be.equal(8); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceArray[7].P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[7].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceArray[7].N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceArray[7].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - }); - describe('#impedanceTestChannel', function () { - var impedanceObject = {}; - - before(function (done) { - ourBoard.impedanceTestChannel(1) - .then(impdObj => { - impedanceObject = impdObj; - done(); - }) - .catch(err => done(err)); - }); - describe('#channel1', function () { - it('has valid channel number', function () { - impedanceObject.channel.should.be.equal(1); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceObject.P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceObject.P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceObject.N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceObject.N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - }); - describe('#impedanceTestChannelInputP', function () { - var impedanceObject = {}; - - before(function (done) { - ourBoard.impedanceTestChannelInputP(1) - .then(impdObj => { - impedanceObject = impdObj; - done(); - }) - .catch(err => done(err)); - }); - describe('#channel1', function () { - it('has valid channel number', function () { - impedanceObject.channel.should.be.equal(1); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceObject.P.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceObject.P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceObject.N.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceObject.N.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - }); - }); - describe('#impedanceTestChannelInputN', function () { - var impedanceObject = {}; - // wstream = fs.createWriteStream('hardwareVoltageOutputAll.txt') - - before(function (done) { - console.log('7'); - - ourBoard.on('sample', sample => { - // console.log('8') - // OpenBCISample.debugPrettyPrint(sample) - // good to start impedance testing.. - }); - - ourBoard.impedanceTestChannelInputN(1) - .then(impdObj => { - impedanceObject = impdObj; - setTimeout(() => { - done(); - }, 1000); - }) - .catch(err => done(err)); - }); - describe('#channel1', function () { - it('has valid channel number', function () { - impedanceObject.channel.should.be.equal(1); - }); - describe('#inputP', function () { - it('got raw impedance value', function () { - impedanceObject.P.should.have.property('raw').equal(-1); - }); - it("text is not 'init'", function () { - impedanceObject.P.should.have.property('text').equal(k.OBCIImpedanceTextInit); - }); - }); - describe('#inputN', function () { - it('got raw impedance value', function () { - impedanceObject.N.should.have.property('raw').above(-1); - }); - it("text is not 'init'", function () { - impedanceObject.N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); - }); - }); - }); - }); - describe('#impedanceTestContinuousStXX', function () { - before(function (done) { - ourBoard.impedanceTestContinuousStart() - .then(done).catch(err => done(err)); - }); - - after(function (done) { - ourBoard.impedanceTestContinuousStop() - .then(done); - }); - - it('prints 10 impedance arrays', function (done) { - var count = 1; - - var listener = impedanceArray => { - // console.log('\nImpedance Array: ' + count) - // console.log(impedanceArray) - count++; - if (count > 10) { - ourBoard.removeListener('impedanceArray', listener); - done(); - } - }; - ourBoard.on('impedanceArray', listener); - }); - }); - describe('#_impedanceTestSetChannel', function () { - it('reject with invalid channel', function (done) { - ourBoard._impedanceTestSetChannel(0, false, false).should.be.rejected.and.notify(done); - }); - }); - describe('#_impedanceTestCalculateChannel', function () { - it('reject with low invalid channel', function (done) { - ourBoard._impedanceTestCalculateChannel(0, false, false).should.be.rejected.and.notify(done); - }); - it('reject with high invalid channel', function (done) { - ourBoard._impedanceTestCalculateChannel(69, false, false).should.be.rejected.and.notify(done); - }); - it('reject with invalid data type pInput', function (done) { - ourBoard._impedanceTestCalculateChannel(1, 'taco', false).should.be.rejected.and.notify(done); - }); - it('reject with invalid data type nInput', function (done) { - ourBoard._impedanceTestCalculateChannel(1, false, 'taco').should.be.rejected.and.notify(done); - }); - }); - describe('#_impedanceTestFinalizeChannel', function () { - it('reject with low invalid channel', function (done) { - ourBoard._impedanceTestFinalizeChannel(0, false, false).should.be.rejected.and.notify(done); - }); - it('reject with high invalid channel', function (done) { - ourBoard._impedanceTestFinalizeChannel(69, false, false).should.be.rejected.and.notify(done); - }); - it('reject with invalid data type pInput', function (done) { - ourBoard._impedanceTestFinalizeChannel(1, 'taco', false).should.be.rejected.and.notify(done); - }); - it('reject with invalid data type nInput', function (done) { - ourBoard._impedanceTestFinalizeChannel(1, false, 'taco').should.be.rejected.and.notify(done); - }); - }); -}); diff --git a/test/openBCICyton-radio-test.js b/test/openBCICyton-radio-test.js deleted file mode 100644 index 6696790..0000000 --- a/test/openBCICyton-radio-test.js +++ /dev/null @@ -1,950 +0,0 @@ -'use strict'; -const bluebirdChecks = require('./bluebirdChecks'); -const sinon = require('sinon'); // eslint-disable-line no-unused-vars -const chai = require('chai'); -const expect = chai.expect; -const should = chai.should(); // eslint-disable-line no-unused-vars -const OpenBCICyton = require('../openBCICyton'); -const k = require('openbci-utilities').Constants; -const chaiAsPromised = require('chai-as-promised'); -const sinonChai = require('sinon-chai'); -const dirtyChai = require('dirty-chai'); - -chai.use(chaiAsPromised); -chai.use(sinonChai); -chai.use(dirtyChai); - -describe('openbci-radios', function () { - this.timeout(2000); - var ourBoard, masterPortName; - - before(function (done) { - ourBoard = new OpenBCICyton(); - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - ourBoard = null; - masterPortName = portName; - done(); - }) - .catch(() => { - ourBoard = null; - masterPortName = k.OBCISimulatorPortName; - done(); - }); - }); - after(done => { - if (ourBoard) { - if (ourBoard['connected']) { - ourBoard.disconnect() - .then(() => { - done(); - }) - .catch(err => { - done(err); - }); - } else { - done(); - } - } else { - done(); - } - }); - - describe('#radioChannelSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not change the channel number if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - - it('should reject if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelSet(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should reject if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if a number is not sent as input', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(26).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should not change the channel if the board is not communicating with the host', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { - var newChannelNumber = 2; - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSet(newChannelNumber).then(channelNumber => { - expect(channelNumber).to.be.equal(newChannelNumber); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioChannelSetHostOverride', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - it('should not change the channel number if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); - }); - it('should reject if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelSetHostOverride(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should reject if a number is not sent as input', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if no channel number is presented as arg', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if the requested new channel number is lower than 0', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should reject if the requested new channel number is higher than 25', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(26).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { - var newChannelNumber = 2; - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelSetHostOverride(newChannelNumber).then(channelNumber => { - expect(channelNumber).to.be.equal(newChannelNumber); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioChannelGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not query if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - it('should not query if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioChannelGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not query if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should query if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().then(res => { - expect(res.channelNumber).to.be.within(k.OBCIRadioChannelMin, k.OBCIRadioChannelMax); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get message even if the board is not communicating with dongle', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioChannelGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioPollTimeSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - it('should not change the channel number if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); - }); - - it('should reject if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioPollTimeSet(1).then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - - it('should reject if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if a number is not sent as input', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet('1').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if no poll time is presented as arg', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new poll time is lower than 0', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(-1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should reject if the requested new poll time is higher than 255', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(256).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should not change the poll time if the board is not communicating with the host', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - - it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { - var newPollTime = 69; - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeSet(newPollTime).then(() => { - done(); - }).catch(err => { - done(err); - }); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioPollTimeGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not query if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - it('should not query if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioPollTimeGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not query if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should query if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().then(pollTime => { - expect(pollTime).to.be.greaterThan(0); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get failure message if the board is not communicating with dongle', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioBaudRateSet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not try to set baud rate if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); - }); - it('should reject if no input', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); - }); - it('should be rejected if input type incorrect', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); - }); - it('should not try to change the baud rate if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioBaudRateSet('default').then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not try to change the baud rate if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should set the baud rate to default if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('default').then(baudrate => { - expect(baudrate).to.be.equal(115200); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should set the baud rate to fast if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioBaudRateSet('fast').then(baudrate => { - expect(baudrate).to.be.equal(230400); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioSystemStatusGet', function () { - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - afterEach(() => bluebirdChecks.noPendingPromises()); - - it('should not get system status if not connected', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); - }); - it('should not get system status if streaming', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - ourBoard.radioSystemStatusGet().then(() => { - done('should have rejected'); - }).catch(() => { - done(); // Test pass - }); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should not get system status if not firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); - }); - }).catch(err => done(err)); - }); - it('should get up system status if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2' - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true(); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - it('should get down system status if firmware version 2', function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulate: true, - simulatorFirmwareVersion: 'v2', - simulatorBoardFailure: true - }); - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.false(); - done(); - }).catch(err => done(err)); - }); - }).catch(err => done(err)); - }); - }); - - describe('#radioTests', function () { - this.timeout(0); - before(function (done) { - ourBoard = new OpenBCICyton({ - verbose: true, - simulatorFirmwareVersion: 'v2', - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - ourBoard.connect(masterPortName).catch(err => done(err)); - - ourBoard.once('ready', () => { - done(); - }); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - it('should be able to get the channel number', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - // The channel number should be between 0 and 25. Those are hard limits. - ourBoard.radioChannelGet().then(res => { - expect(res.channelNumber).to.be.within(0, 25); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 1', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(1).then(channelNumber => { - expect(channelNumber).to.equal(1); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 2', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(2).then(channelNumber => { - expect(channelNumber).to.equal(2); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 25', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(25).then(channelNumber => { - expect(channelNumber).to.equal(25); - done(); - }).catch(err => done(err)); - }); - it('should be able to set the channel to 5', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioChannelSet(5).then(channelNumber => { - expect(channelNumber).to.equal(5); - done(); - }).catch(err => done(err)); - }); - it('should be able to override host channel number, verify system is down, set the host back and verify system is up', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - var systemChanNumber = 0; - var newChanNum = 0; - // var timey = Date.now() - // Get the current system channel - ourBoard.radioChannelGet() - .then(res => { - // Store it - systemChanNumber = res.channelNumber; - // console.log(`system channel number: ${res.channelNumber}`) - if (systemChanNumber === 25) { - newChanNum = 24; - } else { - newChanNum = systemChanNumber + 1; - } - // Call to change just the host - return ourBoard.radioChannelSetHostOverride(newChanNum); - }) - .then(newChanNumActual => { - expect(newChanNumActual).to.equal(newChanNum); - // timey = Date.now() - // console.log(`new chan ${newChanNumActual} got`, timey, '0ms') - return new Promise((resolve, reject) => { - setTimeout(function () { - // console.log(`get status`, Date.now(), `${Date.now() - timey}ms`) - ourBoard.radioSystemStatusGet().then(isUp => { - // console.log('resolving', Date.now(), `${Date.now() - timey}ms`) - resolve(isUp); - }) - .catch(err => { - reject(err); - }); - }, 270); // Should be accurate after 270 seconds - }); - }) - .then(isUp => { - // console.log(`isUp test`, Date.now(), `${Date.now() - timey}ms`) - expect(isUp).to.be.false(); - return ourBoard.radioChannelSetHostOverride(systemChanNumber); // Set back to good - }) - .then(newChanNumActual => { - // Verify we set it back to normal - expect(newChanNumActual).to.equal(systemChanNumber); - return ourBoard.radioSystemStatusGet(); - }) - .then(isUp => { - expect(isUp).to.be.true(); - done(); - }) - .catch(err => done(err)); - }); - it('should be able to get the poll time', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioPollTimeGet().should.eventually.be.greaterThan(0).and.notify(done); - }); - it('should be able to set the poll time', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioPollTimeSet(80).should.become(80).and.notify(done); - }); - it('should be able to change to default baud rate', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioBaudRateSet('default').should.become(115200).and.notify(done); - }); - it('should be able to change to fast baud rate', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioBaudRateSet('fast').then(newBaudRate => { - expect(newBaudRate).to.equal(230400); - return ourBoard.radioBaudRateSet('default'); - }).then(() => { - done(); - }).catch(err => done(err)); - }); - it('should be able to set the system status', function (done) { - // Don't test if not using v2 - if (!ourBoard.usingVersionTwoFirmware()) return done(); - ourBoard.radioSystemStatusGet().then(isUp => { - expect(isUp).to.be.true(); - done(); - }).catch(err => done(err)); - }); - }); -}); diff --git a/test/openBCICyton-test.js b/test/openBCICyton-test.js deleted file mode 100644 index 89fa6df..0000000 --- a/test/openBCICyton-test.js +++ /dev/null @@ -1,2937 +0,0 @@ -'use strict'; -const bluebirdChecks = require('./bluebirdChecks'); -const sinon = require('sinon'); -const chai = require('chai'); -const expect = chai.expect; -const should = chai.should(); // eslint-disable-line no-unused-vars -const Cyton = require('../openBCICyton'); -const OpenBCIUtilities = require('openbci-utilities'); -const openBCIUtilities = OpenBCIUtilities.Utilities; -const k = OpenBCIUtilities.Constants; -const chaiAsPromised = require('chai-as-promised'); -const sinonChai = require('sinon-chai'); -const bufferEqual = require('buffer-equal'); -const fs = require('fs'); -const math = require('mathjs'); -const dirtyChai = require('dirty-chai'); -const Buffer = require('safe-buffer').Buffer; - -chai.use(chaiAsPromised); -chai.use(sinonChai); -chai.use(dirtyChai); - -describe('openbci-sdk', function () { - this.timeout(2000); - let ourBoard, masterPortName, realBoard, spy; - - before(function (done) { - ourBoard = new Cyton(); - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - ourBoard = null; - realBoard = true; - masterPortName = portName; - done(); - }) - .catch(() => { - ourBoard = null; - realBoard = false; - masterPortName = k.OBCISimulatorPortName; - done(); - }); - }); - after(done => { - if (ourBoard) { - if (ourBoard['connected']) { - ourBoard.disconnect() - .then(() => { - done(); - }) - .catch(err => { - done(err); - }); - } else { - done(); - } - } else { - done(); - } - }); - describe('#constructor', function () { - afterEach(() => { - ourBoard = null; - return bluebirdChecks.noPendingPromises(); - }); - it('constructs with require', function () { - let OpenBCICyton = require('../index').Cyton; - ourBoard = new OpenBCICyton({ - verbose: true - }); - expect(ourBoard.numberOfChannels()).to.equal(8); - }); - it('constructs with the correct default options', () => { - let board = new Cyton(); - expect(board.options.boardType).to.equal(k.OBCIBoardCyton); - expect(board.options.baudRate).to.equal(115200); - expect(board.options.hardSet).to.be.false(); - expect(board.options.sendCounts).to.be.false(); - expect(board.options.simulate).to.be.false(); - expect(board.options.simulatorBoardFailure).to.be.false(); - expect(board.options.simulatorDaisyModuleAttached).to.be.false(); - expect(board.options.simulatorDaisyModuleCanBeAttached).to.be.true(); - expect(board.options.simulatorFirmwareVersion).to.equal(k.OBCIFirmwareV1); - expect(board.options.simulatorHasAccelerometer).to.be.true(); - expect(board.options.simulatorInternalClockDrift).to.equal(0); - expect(board.options.simulatorInjectAlpha).to.be.true(); - expect(board.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz60); - expect(board.options.simulatorSampleRate).to.equal(k.OBCISampleRate250); - expect(board.options.simulatorSerialPortFailure).to.be.false(); - expect(board.options.sntpTimeSync).to.be.false(); - expect(board.options.sntpTimeSyncHost).to.equal('pool.ntp.org'); - expect(board.options.verbose).to.be.false(); - expect(board.sampleRate()).to.equal(250); - expect(board.numberOfChannels()).to.equal(8); - expect(board.isConnected()).to.be.false(); - expect(board.isStreaming()).to.be.false(); - it('should still get proper values if no info object', function () { - board.info = null; - expect(board.sampleRate()).to.equal(250); - expect(board.numberOfChannels()).to.equal(8); - }); - }); - it('should be able to set ganglion mode', () => { - let board = new Cyton({ - boardType: 'ganglion' - }); - (board.options.boardType).should.equal('ganglion'); - }); - it('should be able to set set daisy mode', () => { - let ourBoard1 = new Cyton({ - boardType: 'daisy' - }); - let ourBoard2 = new Cyton({ - boardtype: 'daisy' - }); - (ourBoard1.options.boardType).should.equal('daisy'); - (ourBoard2.options.boardType).should.equal('daisy'); - ourBoard1.info = null; - it('should get value for daisy', () => { - ourBoard1.sampleRate().should.equal(125); - }); - it('should get value for daisy', () => { - ourBoard1.numberOfChannels().should.equal(16); - }); - }); - it('should be able to change baud rate', () => { - let ourBoard1 = new Cyton({ - baudRate: 9600 - }); - (ourBoard1.options.baudRate).should.equal(9600); - let ourBoard2 = new Cyton({ - baudrate: 9600 - }); - (ourBoard2.options.baudRate).should.equal(9600); - }); - it('should be able to enter simulate mode from the constructor', () => { - let board = new Cyton({ - simulate: true - }); - expect(board.options.simulate).to.be.true(); - }); - it('should be able to set the simulator to board failure mode', () => { - let ourBoard1 = new Cyton({ - simulatorBoardFailure: true - }); - expect(ourBoard1.options.simulatorBoardFailure).to.be.true(); - let ourBoard2 = new Cyton({ - simulatorboardfailure: true - }); - expect(ourBoard2.options.simulatorBoardFailure).to.be.true(); - }); - it('should be able to attach the daisy board in the simulator', () => { - let ourBoard1 = new Cyton({ - simulatorDaisyModuleAttached: true - }); - expect(ourBoard1.options.simulatorDaisyModuleAttached).to.be.true(); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatordaisymoduleattached: true - }); - expect(ourBoard2.options.simulatorDaisyModuleAttached).to.be.true(); - }); - it('should be able to start the simulator with firmware version 2', () => { - let ourBoard1 = new Cyton({ - simulatorFirmwareVersion: 'v2' - }); - (ourBoard1.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorfirmwareversion: 'v2' - }); - (ourBoard2.options.simulatorFirmwareVersion).should.equal(k.OBCIFirmwareV2); - }); - it('should be able to put the simulator in raw aux mode', () => { - let ourBoard1 = new Cyton({ - simulatorHasAccelerometer: false - }); - expect(ourBoard1.options.simulatorHasAccelerometer).to.be.false(); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorhasaccelerometer: false - }); - expect(ourBoard2.options.simulatorHasAccelerometer).to.be.false(); - }); - it('should be able to make the internal clock of the simulator run slow', () => { - let ourBoard1 = new Cyton({ - simulatorInternalClockDrift: -1 - }); - expect(ourBoard1.options.simulatorInternalClockDrift).to.be.lessThan(0); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorinternalclockdrift: -1 - }); - expect(ourBoard2.options.simulatorInternalClockDrift).to.be.lessThan(0); - }); - it('should be able to make the internal clock of the simulator run fast', () => { - let ourBoard1 = new Cyton({ - simulatorInternalClockDrift: 1 - }); - expect(ourBoard1.options.simulatorInternalClockDrift).to.be.greaterThan(0); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorinternalclockdrift: 1 - }); - expect(ourBoard2.options.simulatorInternalClockDrift).to.be.greaterThan(0); - }); - it('should be able to not inject alpha waves into the simulator', function () { - let ourBoard1 = new Cyton({ - simulatorInjectAlpha: false - }); - expect(ourBoard1.options.simulatorInjectAlpha).to.be.false(); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorinjectalpha: false - }); - expect(ourBoard2.options.simulatorInjectAlpha).to.be.false(); - }); - it('can turn 50Hz line noise on', function () { - let ourBoard1 = new Cyton({ - simulatorInjectLineNoise: '50Hz' - }); - expect(ourBoard1.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorinjectlinenoise: '50Hz' - }); - expect(ourBoard2.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz50); - }); - it('can turn no line noise on', function () { - ourBoard = new Cyton({ - simulatorInjectLineNoise: 'none' - }); - (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseNone); - }); - it('defaults to 60Hz line noise when bad input', function () { - ourBoard = new Cyton({ - simulatorInjectLineNoise: '20Hz' - }); - (ourBoard.options.simulatorInjectLineNoise).should.equal(k.OBCISimulatorLineNoiseHz60); - }); - it('can enter simulate mode with different sample rate', function () { - ourBoard = new Cyton({ - simulate: true, - simulatorSampleRate: 69 - }); - (ourBoard.options.simulate).should.equal(true); - (ourBoard.options.simulatorSampleRate).should.equal(69); - (ourBoard.sampleRate()).should.equal(69); - }); - it('should be able to attach the daisy board in the simulator', () => { - let ourBoard1 = new Cyton({ - simulatorSerialPortFailure: true - }); - expect(ourBoard1.options.simulatorSerialPortFailure).to.be.true(); - // Verify multi case support - let ourBoard2 = new Cyton({ - simulatorserialportfailure: true - }); - expect(ourBoard2.options.simulatorSerialPortFailure).to.be.true(); - }); - it('should be able to enter sync mode', function () { - let ourBoard = new Cyton({ - sntpTimeSync: true - }); - expect(ourBoard.options.sntpTimeSync).to.be.true(); - - return new Promise((resolve, reject) => { - ourBoard.once('sntpTimeLock', resolve); - ourBoard.once('error', reject); - }).then(() => { - ourBoard.sntpStop(); - }, err => { - ourBoard.sntpStop(); - return Promise.reject(err); - }); - }); - it('should be able to change the ntp pool host', function () { - let expectedPoolName = 'time.apple.com'; - let ourBoard1 = new Cyton({ - sntpTimeSyncHost: expectedPoolName - }); - expect(ourBoard1.options.sntpTimeSyncHost).to.equal(expectedPoolName); - // Verify multi case support - let ourBoard2 = new Cyton({ - sntptimesynchost: expectedPoolName - }); - expect(ourBoard2.options.sntpTimeSyncHost).to.equal(expectedPoolName); - }); - it('should be able to change the ntp pool port', function () { - let expectedPortNumber = 73; - let ourBoard1 = new Cyton({ - sntpTimeSyncPort: expectedPortNumber - }); - expect(ourBoard1.options.sntpTimeSyncPort).to.equal(expectedPortNumber); - // Verify multi case support - let ourBoard2 = new Cyton({ - sntptimesyncport: expectedPortNumber - }); - expect(ourBoard2.options.sntpTimeSyncPort).to.equal(expectedPortNumber); - }); - it('should report when sntp fails', function (done) { - let ourBoard = new Cyton({ - sntpTimeSync: true, - sntpTimeSyncHost: 'no\'where' - }); - ourBoard.once('error', () => { - done(); - }); - ourBoard.once('sntpTimeLock', () => { - ourBoard.sntpStop(); - done('got a time lock with nowhere'); - }); - }); - it('can enter verbose mode', function () { - ourBoard = new Cyton({ - verbose: true - }); - (ourBoard.options.verbose).should.equal(true); - }); - it('should start in current stream state in the init mode', () => { - ourBoard = new Cyton(); - - ourBoard.curParsingMode.should.equal(k.OBCIParsingReset); - }); - it('configures impedance testing letiables correctly', function () { - ourBoard = new Cyton(); - (ourBoard.impedanceTest.active).should.equal(false); - (ourBoard.impedanceTest.isTestingNInput).should.equal(false); - (ourBoard.impedanceTest.isTestingPInput).should.equal(false); - (ourBoard.impedanceTest.onChannel).should.equal(0); - (ourBoard.impedanceTest.sampleNumber).should.equal(0); - }); - it('configures sync object correctly', function () { - ourBoard = new Cyton(); - expect(ourBoard.sync.curSyncObj).to.be.null(); - expect(ourBoard.sync.eventEmitter).to.be.null(); - expect(ourBoard.sync.objArray.length).to.equal(0); - (ourBoard.sync.sntpActive).should.equal(false); - (ourBoard.sync.timeOffsetMaster).should.equal(0); - (ourBoard.sync.timeOffsetAvg).should.equal(0); - expect(ourBoard.sync.timeOffsetArray.length).to.equal(0); - }); - it('configures impedance array with the correct amount of channels for default', function () { - ourBoard = new Cyton(); - (ourBoard.impedanceArray.length).should.equal(8); - }); - it('configures impedance array with the correct amount of channels for daisy', function () { - ourBoard = new Cyton({ - boardType: 'daisy' - }); - (ourBoard.impedanceArray.length).should.equal(16); - }); - it('configures impedance array with the correct amount of channels for ganglion', function () { - ourBoard = new Cyton({ - boardType: 'ganglion' - }); - (ourBoard.impedanceArray.length).should.equal(4); - }); - it('should throw if passed an invalid option', function (done) { - try { - ourBoard = new Cyton({ - foo: 'bar' - }); - done('did not throw'); - } catch (e) { done(); } - }); - }); - describe('#simulator', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('can enable simulator after constructor', function (done) { - ourBoard = new Cyton({ - verbose: true - }); - ourBoard.simulatorEnable().should.be.fulfilled.and.notify(done); - }); - it('should start sim and call disconnected', function (done) { - ourBoard = new Cyton({ - verbose: true - }); - let disconnectStub = sinon.stub(ourBoard, 'disconnect').returns(Promise.resolve()); - let isConnectedStub = sinon.stub(ourBoard, 'isConnected').returns(true); - ourBoard.options.simulate.should.equal(false); - ourBoard.simulatorEnable().then(() => { - disconnectStub.should.have.been.calledOnce(); - disconnectStub.restore(); - isConnectedStub.restore(); - ourBoard.options.simulate.should.equal(true); - done(); - }, done); - }); - it('should not enable the simulator if already simulating', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.simulatorEnable().should.be.rejected.and.notify(done); - }); - it('can disable simulator', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.simulatorDisable().should.be.fulfilled.and.notify(done); - }); - it('should not disable simulator if not in simulate mode', function (done) { - ourBoard = new Cyton({ - verbose: true - }); - ourBoard.simulatorDisable().should.be.rejected.and.notify(done); - }); - it('should disable sim and call disconnected', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true - }); - ourBoard.connect(k.OBCISimulatorPortName).catch(done); - ourBoard.on('ready', function () { - expect(ourBoard.isSimulating()).to.equal(true); - let disconnectSpy = sinon.spy(ourBoard, 'disconnect'); - ourBoard.options.simulate.should.equal(true); - ourBoard.simulatorDisable().then(() => { - disconnectSpy.should.have.been.calledOnce(); - disconnectSpy.restore(); - ourBoard.options.simulate.should.equal(false); - done(); - }, done); - }); - }); - it('should be able to propagate constructor options to simulator', function (done) { - ourBoard = new Cyton({ - verbose: true, - simulate: true, - simulatorBoardFailure: true, - simulatorDaisyModuleAttached: true, - simulatorDaisyModuleCanBeAttached: false, - simulatorFirmwareVersion: k.OBCIFirmwareV2, - simulatorHasAccelerometer: false, - simulatorInternalClockDrift: -1, - simulatorInjectAlpha: false, - simulatorFragmentation: k.OBCISimulatorFragmentationOneByOne, - simulatorLatencyTime: 314, - simulatorBufferSize: 2718, - simulatorInjectLineNoise: k.OBCISimulatorLineNoiseNone, - simulatorSampleRate: 16, - simulatorSerialPortFailure: true - }); - - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', () => { - let simOptions = ourBoard.serial.options; - expect(simOptions).to.be.an('object'); - expect(simOptions.accel).to.be.false(); - expect(simOptions.alpha).to.be.false(); - expect(simOptions.boardFailure).to.be.true(); - expect(simOptions.daisy).to.be.true(); - expect(simOptions.daisyCanBeAttached).to.be.false(); - expect(simOptions.drift).to.be.below(0); - expect(simOptions.firmwareVersion).to.be.equal(k.OBCIFirmwareV2); - expect(simOptions.fragmentation).to.be.equal(k.OBCISimulatorFragmentationOneByOne); - expect(simOptions.latencyTime).to.be.equal(314); - expect(simOptions.bufferSize).to.be.equal(2718); - expect(simOptions.lineNoise).to.be.equal(k.OBCISimulatorLineNoiseNone); - expect(simOptions.sampleRate).to.be.equal(16); - expect(simOptions.serialPortFailure).to.be.true(); - expect(simOptions.verbose).to.be.true(); - ourBoard.disconnect().then(done).catch(done); - }); - }).catch(err => done(err)); - }); - it('should be able to set info for cyton board', function (done) { - ourBoard.simulatorDisable() - .then(() => { - ourBoard.overrideInfoForBoardType('cyton'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); - done(); - }) - .catch((err) => { - if (err.message === 'Not simulating') { - ourBoard.overrideInfoForBoardType('cyton'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); - done(); - } else { - done(err); - } - }); - }); - it('should be able to set info for daisy board', function (done) { - ourBoard.simulatorDisable() - .then(() => { - ourBoard.overrideInfoForBoardType('daisy'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardDaisy); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsDaisy); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate125); - done(); - }) - .catch((err) => { - if (err.message === 'Not simulating') { - ourBoard.overrideInfoForBoardType('daisy'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardDaisy); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsDaisy); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate125); - done(); - } else { - done(err); - } - }); - }); - it('should set info to default on bad input string', function (done) { - ourBoard.simulatorDisable() - .then(() => { - ourBoard.overrideInfoForBoardType('taco'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); - done(); - }) - .catch((err) => { - if (err.message === 'Not simulating') { - ourBoard.overrideInfoForBoardType('cyton'); - expect(ourBoard.getBoardType()).to.be.equal(k.OBCIBoardCyton); - expect(ourBoard.numberOfChannels()).to.be.equal(k.OBCINumberOfChannelsCyton); - expect(ourBoard.sampleRate()).to.be.equal(k.OBCISampleRate250); - done(); - } else { - done(err); - } - }); - }); - }); - describe('#debug', function () { - before(function (done) { - ourBoard = new Cyton({ - debug: true - }); - ourBoard.connect(k.OBCISimulatorPortName).catch(done); - ourBoard.once('ready', () => { - sinon.spy(console, 'log'); - done(); - }); - }); - after(function (done) { - console.log.restore(); - ourBoard.disconnect().then(done, done); - }); - after(() => bluebirdChecks.noPendingPromises()); - it('outputs a packet when written', done => { - console.log.reset(); - ourBoard.write(k.OBCIStreamStop).catch(done); - setTimeout(() => { - console.log.should.have.been.calledWithMatch(k.OBCIStreamStop); - done(); - }, k.OBCIWriteIntervalDelayMSLong * 2); - }); - it('outputs a packet when received', done => { - console.log.reset(); - ourBoard.sdStop().catch(done); - ourBoard.once('eot', () => { - console.log.should.have.been.calledWithMatch('$'); - done(); - }); - }); - }); - describe('#boardTests', function () { - this.timeout(3000); - before(function () { - ourBoard = new Cyton({ - simulate: !realBoard, - verbose: false, - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - spy = sinon.spy(ourBoard, '_writeAndDrain'); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - afterEach(function () { - if (spy) spy.reset(); - ourBoard.removeAllListeners('sample'); - ourBoard.removeAllListeners('ready'); - ourBoard.removeAllListeners('rawDataPacket'); - }); - describe('#connect/disconnect/streamStart/streamStop', function () { - it('rejects if already disconnected', function () { - return ourBoard.disconnect().should.be.rejected; - }); - it('rejects if already connected', function (done) { - ourBoard.connect(masterPortName).catch(err => done(err)); - - ourBoard.once('ready', () => { - ourBoard.connect(masterPortName) - .then(() => { - return ourBoard.disconnect(); - }) - .then(() => { - done('should have failed to connect'); - }) - .catch((err) => { - if (err.message === 'already connected!') { - return ourBoard.disconnect(); - } else { - return Promise.reject(err); - } - }) - .then(() => { - done(); - }) - .catch((err) => { - done(err); - }); - }); - }); - it('gets the ready signal from the board and sends a stop streaming command before disconnecting', function (done) { - // spy = sinon.spy(ourBoard,"_writeAndDrain") - - ourBoard.connect(masterPortName).catch(err => done(err)); - - ourBoard.once('ready', function () { - ourBoard.streamStart().catch(err => done(err)); // start streaming - - ourBoard.once('sample', (sample) => { // wait till we get a sample - ourBoard.disconnect().then(() => { // call disconnect - // console.log('Device is streaming: ' + ourBoard.isStreaming() ? 'true' : 'false') - setTimeout(() => { - spy.should.have.been.calledWithMatch(k.OBCIStreamStop); - let conditionalTimeout = realBoard ? 300 : 0; - setTimeout(() => { - done(); - }, conditionalTimeout); - }, 4 * k.OBCIWriteIntervalDelayMSShort); // give plenty of time - }).catch(err => done(err)); - }); - }); - }); - it('rawDataPacket is emitted', function (done) { - ourBoard.connect(masterPortName).catch(err => done(err)); - // for the ready signal test - ourBoard.once('ready', function () { - ourBoard.streamStart().catch(err => done(err)); // start streaming - - ourBoard.once('rawDataPacket', (rawDataPacket) => { // wait till we get a raw data packet - ourBoard.disconnect().then(() => { // call disconnect - done(); - }).catch(err => done(err)); - }); - }); - }); - it('daisy not attached in soft reset, daisy requested by user in options, module tries to attach and is successful', function (done) { - if (ourBoard.isSimulating()) { - // Turn hardSet on - ourBoard.options.hardSet = true; - // Set the options to daisy boardType - ourBoard.options.boardType = k.OBCIBoardDaisy; - // The simulator does not have a daisy - ourBoard.options.simulatorDaisyModuleAttached = false; - // The simulator is able to attach daisy - ourBoard.options.simulatorDaisyModuleCanBeAttached = true; - const failTestWithErr = (err) => { - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(err); - }).catch(() => { - done(err); - }); - }; - const hardSetFuncOnTime = () => { - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - // Remove the premature ready function because it won't fire - ourBoard.removeListener('ready', readyFuncPreMature); - // If the board was able to attach the daisy - ourBoard.once('ready', readyFuncSuccess); - // If the board was unable to attach the daisy. - ourBoard.once('error', errorFuncTestFailure); // should not happen - }; - const errorFuncTestFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - failTestWithErr('failed to attach daisy, should emit error'); - }; - const readyFuncPreMature = () => { - ourBoard.removeListener('hardSet', hardSetFuncOnTime); - failTestWithErr('the board should not have been ready yet'); - }; - const readyFuncSuccess = () => { - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - // Remove because it won't fire - ourBoard.removeListener('error', errorFuncTestFailure); - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(); - }).catch(() => { - done(); - }); - }; - - ourBoard.once('ready', readyFuncPreMature); - ourBoard.once('hardSet', hardSetFuncOnTime); - ourBoard.connect(masterPortName).catch(err => done(err)); - } else { - done(); - } - }); - it('daisy not attached in soft reset, daisy requested by user in options, module tries to attach and fails', function (done) { - if (ourBoard.isSimulating()) { - // Turn hardSet on - ourBoard.options.hardSet = true; - // Set the options to daisy boardType - ourBoard.options.boardType = k.OBCIBoardDaisy; - // The simulator does NOT have a daisy - ourBoard.options.simulatorDaisyModuleAttached = false; - // The simulator is NOT able to attach daisy - ourBoard.options.simulatorDaisyModuleCanBeAttached = false; - const failTestWithErr = (err) => { - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(err); - }).catch(() => { - done(err); - }); - }; - const hardSetFuncOnTime = () => { - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - // Remove the premature ready function because it won't fire - ourBoard.removeListener('ready', readyFuncPreMature); - // If the board was able to attach the daisy - ourBoard.once('ready', readyFuncTestFailure); - // If the board was unable to attach the daisy. - ourBoard.once('error', errorFuncTestSuccess); // should not happen - }; - const errorFuncTestSuccess = () => { - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - ourBoard.removeListener('ready', readyFuncTestFailure); - done(); - }).catch(() => { - ourBoard.removeListener('ready', readyFuncTestFailure); - done(); - }); - }; - const readyFuncPreMature = () => { - ourBoard.removeListener('hardSet', hardSetFuncOnTime); - failTestWithErr('the board should not have been ready yet'); - }; - const readyFuncTestFailure = () => { - failTestWithErr('failed to attach daisy when requested, ready should not be emitted'); - }; - - ourBoard.once('ready', readyFuncPreMature); - ourBoard.once('hardSet', hardSetFuncOnTime); - ourBoard.connect(masterPortName).catch(err => done(err)); - } else { - done(); - } - }); - it('daisy attached in soft reset, default board (not daisy) requested by user in options, module tries to remove and succeeds', function (done) { - if (ourBoard.isSimulating()) { - // Turn hardSet on - ourBoard.options.hardSet = true; - // Set the options to daisy boardType - ourBoard.options.boardType = k.OBCIBoardCyton; - // The simulator has a daisy attached - ourBoard.options.simulatorDaisyModuleAttached = true; - - const failTestWithErr = (err) => { - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(err); - }).catch(() => { - done(err); - }); - }; - const hardSetFuncOnTime = () => { - // Verify the module is set to daisy mode - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - // Remove the premature ready function because it won't fire - ourBoard.removeListener('ready', readyFuncPreMature); - // If the board was able to remove the daisy - ourBoard.once('ready', readyFuncSuccess); // intended - // If the board was unable to remove the daisy. - ourBoard.once('error', errorFuncTestFailure); // should not happen - }; - const errorFuncTestFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - failTestWithErr('failed to attach daisy, should emit error'); - }; - const readyFuncPreMature = () => { - ourBoard.removeListener('hardSet', hardSetFuncOnTime); - failTestWithErr('the board should not have been ready yet'); - }; - const readyFuncSuccess = () => { - // Verify the module switched to default type - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - // Remove because it won't fire - ourBoard.removeListener('error', errorFuncTestFailure); - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(); - }).catch(() => { - done(); - }); - }; - - ourBoard.once('ready', readyFuncPreMature); - ourBoard.once('hardSet', hardSetFuncOnTime); - ourBoard.connect(masterPortName).catch(err => done(err)); - } else { - done(); - } - }); - it('daisy attached in soft reset, daisy requested by user in options, module is successful', function (done) { - if (ourBoard.isSimulating()) { - // Turn hardSet on - ourBoard.options.hardSet = true; - // Set the options to daisy boardType - ourBoard.options.boardType = k.OBCIBoardDaisy; - // The simulator does have a daisy - ourBoard.options.simulatorDaisyModuleAttached = true; - // The simulator is able to attach daisy - ourBoard.options.simulatorDaisyModuleCanBeAttached = true; - const failTestWithErr = (err) => { - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(err); - }).catch(() => { - done(err); - }); - }; - let hardSetFuncFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - failTestWithErr('should not hardSet'); - }; - let errorFuncTestFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - failTestWithErr('should not error'); - }; - let readyFuncSuccess = () => { - ourBoard.removeListener('error', errorFuncTestFailure); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(); - }).catch(() => { - done(); - }); - }; - - ourBoard.once('error', errorFuncTestFailure); - ourBoard.once('ready', readyFuncSuccess); - ourBoard.once('hardSet', hardSetFuncFailure); - ourBoard.connect(masterPortName).catch(err => done(err)); - } else { - done(); - } - }); - it('no daisy attached in soft reset, default requested by user in options, module is successful', function (done) { - if (ourBoard.isSimulating()) { - // Turn hardSet on - ourBoard.options.hardSet = true; - // Set the options to default boardType - ourBoard.options.boardType = k.OBCIBoardCyton; - // The simulator does not have a daisy - ourBoard.options.simulatorDaisyModuleAttached = false; - // The simulator is able to attach daisy - ourBoard.options.simulatorDaisyModuleCanBeAttached = false; - const failTestWithErr = (err) => { - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(err); - }).catch(() => { - done(err); - }); - }; - let hardSetFuncFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - failTestWithErr('should not hard set'); - }; - let errorFuncTestFailure = () => { - ourBoard.removeListener('ready', readyFuncSuccess); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - failTestWithErr('should not emit error'); - }; - let readyFuncSuccess = () => { - ourBoard.removeListener('error', errorFuncTestFailure); - ourBoard.removeListener('hardSet', hardSetFuncFailure); - // Verify the module is still default - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - ourBoard.options.hardSet = false; - ourBoard.disconnect().then(() => { // call disconnect - done(); - }).catch(() => { - done(); - }); - }; - - ourBoard.once('error', errorFuncTestFailure); - ourBoard.once('ready', readyFuncSuccess); - ourBoard.once('hardSet', hardSetFuncFailure); - ourBoard.connect(masterPortName).catch(err => done(err)); - } else { - done(); - } - }); - }); - describe('#connected', function () { - beforeEach(function (done) { - ourBoard.connect(masterPortName).catch(done); - - ourBoard.once('ready', done); - }); - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(done, () => done()); - } else { - done(); - } - }); - it('is connected after connection', function () { - expect(ourBoard.isConnected()).to.be.true(); - }); - it('is no longer connected after clean disconnection', function (done) { - ourBoard.disconnect().then(() => { - expect(ourBoard.isConnected()).to.be.false(); - done(); - }, done); - }); - it('is no longer connected if stream closes itself', function (done) { - ourBoard.serial.close(() => { - expect(ourBoard.isConnected()).to.be.false(); - done(); - }); - }); - it('is no longer connected after a stream error', function () { - let errorDamper = () => true; - ourBoard.on('error', errorDamper); - ourBoard.serial.emit('error', new Error('test error')); - expect(ourBoard.isConnected()).to.be.false(); - ourBoard.removeListener('error', errorDamper); - }); - }); - describe('#write', function () { - beforeEach(function (done) { - ourBoard.connect(masterPortName).catch(done); - - ourBoard.once('ready', done); - }); - afterEach(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect() - .then(() => { - done(); - }) - .catch(() => { - done(); - }); - } else { - done(); - } - }); - it('rejects after clean disconnection', function (done) { - ourBoard.disconnect().then(() => { - ourBoard.write(k.OBCIMiscSoftReset).should.be.rejected.and.notify(done); - }, done); - }); - it('rejects if stream closes itself', function (done) { - ourBoard.serial.close(() => { - ourBoard.write(k.OBCIMiscSoftReset).should.be.rejected.and.notify(done); - }); - }); - it('rejects after a stream error', function (done) { - let errorDamper = () => true; - ourBoard.on('error', errorDamper); - ourBoard.serial.emit('error', new Error('test error')); - ourBoard.write(k.OBCIMiscSoftReset).should.be.rejected.and.notify(done); - ourBoard.removeListener('error', errorDamper); - }); - it('does not allow data to be sent after clean disconnection', function (done) { - let writeSpy1 = sinon.spy(ourBoard.serial, 'write'); - let byteToWrite = k.OBCISDLogStop; - let writeWhileConnected = function () { - ourBoard.write(byteToWrite).then(() => { - if (ourBoard.isConnected()) { - writeSpy1.reset(); - writeWhileConnected(); - } else { - done('wrote when not connected'); - } - }, err => { - if (ourBoard.isConnected()) { - done(err); - } else { - process.nextTick(() => { - ourBoard.connect(masterPortName).catch(done); - let writeSpy2 = sinon.spy(ourBoard.serial, 'write'); - ourBoard.once('ready', () => { - writeSpy2.should.equal(ourBoard.serial.write); - writeSpy1.should.have.not.been.called(); - writeSpy2.should.have.not.been.calledWith(byteToWrite); - writeSpy1.restore(); - writeSpy2.restore(); - done(); - }); - }); - } - }); - }; - writeWhileConnected(); - ourBoard.disconnect().catch(done); - }); - it('disconnects immediately, rejecting all buffered writes', function (done) { - let writeSpy = sinon.spy(ourBoard.serial, 'write'); - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); - ourBoard.write(k.OBCISDLogStop).should.have.been.rejected(); - - ourBoard.disconnect() - .then(() => { - writeSpy.should.have.not.been.called(); - writeSpy.restore(); - done(); - }) - .catch((err) => { - done(err); - }); - }); - }); - describe('#listPorts', function () { - it('returns a list of ports', function (done) { - ourBoard.listPorts().then(ports => { - if (ports.some(port => { - if (port.comName === masterPortName) { - return true; - } - })) { - done(); - } else { - done(); - } - }); - }); - }); - describe('#sdStart', function () { - before(function (done) { - ourBoard.connect(k.OBCISimulatorPortName) - .then(() => { - ourBoard.once('ready', done); - }) - .catch(err => done(err)); - }); - afterEach(function (done) { - ourBoard.sdStop() - .catch(done); - ourBoard.once('eot', () => { - done(); - }); - }); - after(function (done) { - ourBoard.disconnect() - .then(done) - .catch(err => done(err)); - }); - it('can start 14 seconds of logging with sd', function (done) { - ourBoard.sdStart('14sec') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 5 minutes of logging with sd', function (done) { - ourBoard.sdStart('5min') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 15 minutes of logging with sd', function (done) { - ourBoard.sdStart('15min') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 30 minutes of logging with sd', function (done) { - ourBoard.sdStart('30min') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 1 hour of logging with sd', function (done) { - ourBoard.sdStart('1hour') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 2 hours of logging with sd', function (done) { - ourBoard.sdStart('2hour') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 4 hours of logging with sd', function (done) { - ourBoard.sdStart('4hour') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 12 hours of logging with sd', function (done) { - ourBoard.sdStart('12hour') - .catch(err => done(err)); - ourBoard.once('eot', () => { - done(); - }); - }); - it('can start 24 hours of logging with sd', function (done) { - ourBoard.sdStart('24hour') - .catch(done); - ourBoard.once('eot', () => { - done(); - }); - }); - }); - describe('#sdStop', function () { - before(function (done) { - ourBoard.connect(k.OBCISimulatorPortName).catch(err => done(err)); - ourBoard.once('ready', done); - }); - it('can stop logging with sd', function (done) { - // console.log('yoyoyo') - ourBoard.sdStop().catch(err => done(err)); - ourBoard.once('eot', () => { - // check here in case write was delayed - spy.should.have.been.calledWith('j'); - done(); - }); - }); - }); - describe('#setBoardType', function () { - before(function (done) { - if (!ourBoard.isConnected()) { - ourBoard.connect(masterPortName) - .then(done) - .catch(done); - } else { - done(); - } - }); - after(function (done) { - ourBoard.disconnect() - .then(done) - .catch(done); - }); - it('should resolve for setting max channels to 8 when already 8', function (done) { - if (ourBoard.isSimulating()) { - ourBoard.serial.options.daisy = false; - ourBoard.hardSetBoardType('default') - .then((res) => { - expect(res).to.equal('no daisy to remove'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - done(); - }).catch(done); - } else { - done(); - } - }); - it('should resolve for setting max channels to 8', function (done) { - if (ourBoard.isSimulating()) { - ourBoard.serial.options.daisy = true; - ourBoard.hardSetBoardType('default') - .then((res) => { - expect(res).to.equal('daisy removed'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - done(); - }).catch(done); - } else { - done(); - } - }); - it('should resolve for setting max channels to 16 if daisy already attached', function (done) { - if (ourBoard.isSimulating()) { - ourBoard.serial.options.daisy = true; - ourBoard.hardSetBoardType('daisy') - .then((res) => { - expect(res).to.equal('daisy already attached'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - done(); - }).catch(done); - } else { - done(); - } - }); - it('should resolve for setting max channels to 16 if daisy able to be attached', function (done) { - if (ourBoard.isSimulating()) { - ourBoard.serial.options.daisy = false; - ourBoard.serial.options.daisyCanBeAttached = true; - ourBoard.hardSetBoardType('daisy') - .then((res) => { - expect(res).to.equal('daisy attached'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - done(); - }).catch(done); - } else { - done(); - } - }); - it('should reject when setting max channels to 16 if daisy not able to be attached', function (done) { - if (ourBoard.isSimulating()) { - ourBoard.serial.options.daisy = false; - ourBoard.serial.options.daisyCanBeAttached = false; - ourBoard.hardSetBoardType('daisy') - .then(done) - .catch((err) => { - expect(err.message).to.equal('unable to attach daisy'); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - done(); - }); - } else { - done(); - } - }); - it('should not write a command if invalid channel number', function (done) { - ourBoard.hardSetBoardType(0).should.be.rejected.and.notify(done); - }); - }); - describe('#channelOff', function () { - before(function (done) { - if (!ourBoard.isConnected()) { - ourBoard.connect(masterPortName) - .then(done) - .catch(err => done(err)); - } else { - done(); - } - }); - - it('should call the write function with proper command for channel 1', function (done) { - ourBoard.channelOff(1).then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCIChannelOff1); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for channel 16', function (done) { - // spy = sinon.spy(ourBoard,"_writeAndDrain") - - ourBoard.channelOff(16).then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCIChannelOff16); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should reject with invalid channel', function (done) { - ourBoard.channelOff(0).should.be.rejected.and.notify(done); - }); - it('should turn the realBoard channel off', function (done) { - ourBoard.channelOff(1).then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCIChannelOff1); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - }); - describe('#channelOn', function () { - before(function (done) { - if (!ourBoard.isConnected()) { - ourBoard.connect(masterPortName) - .then(done) - .catch(err => done(err)); - } else { - done(); - } - }); - - it('should call the write function with proper command for channel 2', function (done) { - ourBoard.channelOn(2).then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCIChannelOn2); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for channel 16', function (done) { - ourBoard.channelOn(16).then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCIChannelOn16); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should reject with invalid channel', function (done) { - ourBoard.channelOn(0).should.be.rejected.and.notify(done); - }); - }); - describe('#channelSet', function () { - this.timeout(6000); - before(function (done) { - if (!ourBoard.isConnected()) { - ourBoard.connect(masterPortName) - .then(done) - .catch(err => done(err)); - } else { - done(); - } - }); - it('should call the writeAndDrain function array of commands 9 times', function (done) { - setTimeout(() => { - spy.reset(); - ourBoard.channelSet(1, true, 24, 'normal', true, true, true) - .then(() => { - setTimeout(() => { - spy.callCount.should.equal(9); - done(); - }, 15 * k.OBCIWriteIntervalDelayMSShort); - }) - .catch(err => done(err)); - }, 10 * k.OBCIWriteIntervalDelayMSShort); // give some time for writer to finish - }); - - it('should be rejected', function (done) { - ourBoard.channelSet(1, true, 24, 'normal', 'taco', true, true).should.be.rejected.and.notify(done); - }); - }); - describe('#syncRegisterSettings', function () { - this.timeout(6000); - before(function (done) { - if (!ourBoard.isConnected()) { - ourBoard.connect(masterPortName) - .then(done) - .catch(err => done(err)); - } else { - done(); - } - }); - it('should send the correct register setting', function (done) { - ourBoard.syncRegisterSettings() - .then((channelSettings) => { - expect(channelSettings).to.not.equal(null); - done(); - }).catch(done); - }); - }); - describe('#testSignal', function () { - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('dc').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToDC); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('ground').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToGround); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('pulse1xFast').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xFast); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('pulse1xSlow').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse1xSlow); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('pulse2xFast').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xFast); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should call the write function with proper command for dc test signal', function (done) { - ourBoard.testSignal('pulse2xSlow').then(() => { - setTimeout(() => { - spy.should.have.been.calledWith(k.OBCITestSignalConnectToPulse2xSlow); - done(); - }, 5 * k.OBCIWriteIntervalDelayMSShort); - }); - }); - it('should reject with invalid test signal', function (done) { - ourBoard.testSignal('taco').should.be.rejected.and.notify(done); - }); - }); - describe('#impedanceTest Not Connected Rejects ', function () { - it('rejects all channeles when not streaming', function (done) { - ourBoard.impedanceTestAllChannels().should.be.rejected.and.notify(done); - }); - it('rejects array channels when not streaming', function (done) { - ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', '-', 'b', 'b']).should.be.rejected.and.notify(done); - }); - }); - describe('#impedancePrivates', function () { - describe('disconnected', function () { - before(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect() - .then(done) - .catch(err => done(err)); - } else { - done(); - } - }); - describe('#_impedanceTestSetChannel', function () { - it('should reject because not connected', function (done) { - ourBoard._impedanceTestSetChannel(0, false, false).should.be.rejected.and.notify(done); - }); - }); - }); - }); - }); - - /** - * Test the function that parses an incoming data buffer for packets - */ - describe('#_processDataBuffer', function () { - let _processQualifiedPacketSpy; - before(() => { - ourBoard = new Cyton({ - verbose: true - }); - _processQualifiedPacketSpy = sinon.spy(ourBoard, '_processQualifiedPacket'); - }); - after(() => { - ourBoard = null; - return bluebirdChecks.noPendingPromises(); - }); - - it('should do nothing when empty buffer inserted', () => { - let buffer = null; - - // Test the function - buffer = ourBoard._processDataBuffer(buffer); - - expect(buffer).to.be.null(); - }); - it('should return an unaltered buffer if there is less than a packets worth of data in it', () => { - let expectedString = 'AJ'; - let buffer = new Buffer(expectedString); - - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - - // Test the function - buffer = ourBoard._processDataBuffer(buffer); - - // Convert the buffer to a string and ensure that it equals the expected string - buffer.toString().should.equal(expectedString); - - // Make sure that the spy was not infact called. - _processQualifiedPacketSpy.should.not.have.been.called(); - }); - it('should identify a packet', () => { - let buffer = openBCIUtilities.samplePacketReal(0); - - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledOnce(); - - // The buffer should not have anything in it any more - expect(buffer).to.be.null(); - }); - it('should extract a buffer and preserve the remaining data in the buffer', () => { - let expectedString = 'AJ'; - let extraBuffer = new Buffer(expectedString); - // declare the big buffer - let buffer = new Buffer(k.OBCIPacketSize + extraBuffer.length); - // Fill that new big buffer with buffers - openBCIUtilities.samplePacketReal(0).copy(buffer, 0); - extraBuffer.copy(buffer, k.OBCIPacketSize); - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.called(); - // The buffer should have the epxected number of bytes left - buffer.length.should.equal(expectedString.length); - // Convert the buffer to a string and ensure that it equals the expected string - buffer.toString().should.equal(expectedString); - }); - - it('should be able to extract multiple packets from a single buffer', () => { - // We are going to extract multiple buffers - let expectedNumberOfBuffers = 3; - // declare the big buffer - let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers); - // Fill that new big buffer with buffers - openBCIUtilities.samplePacketReal(0).copy(buffer, 0); - openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); - openBCIUtilities.samplePacketReal(2).copy(buffer, k.OBCIPacketSize * 2); - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledThrice(); - // The buffer should not have anything in it any more - expect(buffer).to.be.null(); - }); - - it('should be able to get multiple packets and keep extra data on the end', () => { - let expectedString = 'AJ'; - let extraBuffer = new Buffer(expectedString); - // We are going to extract multiple buffers - let expectedNumberOfBuffers = 2; - // declare the big buffer - let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); - // Fill that new big buffer with buffers - openBCIUtilities.samplePacketReal(0).copy(buffer, 0); - openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize); - extraBuffer.copy(buffer, k.OBCIPacketSize * 2); - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice(); - // The buffer should not have anything in it any more - buffer.length.should.equal(extraBuffer.length); - }); - - it('should be able to get multiple packets with junk in the middle', () => { - let expectedString = ','; - let extraBuffer = new Buffer(expectedString); - // We are going to extract multiple buffers - let expectedNumberOfBuffers = 2; - // declare the big buffer - let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length); - // Fill that new big buffer with buffers - openBCIUtilities.samplePacketReal(0).copy(buffer, 0); - extraBuffer.copy(buffer, k.OBCIPacketSize); - openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); - - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice(); - // The buffer should not have anything in it any more - bufferEqual(extraBuffer, buffer).should.be.true(); - buffer.length.should.equal(extraBuffer.length); - }); - - it('should be able to get multiple packets with junk in the middle and end', () => { - let expectedString = ','; - let extraBuffer = new Buffer(expectedString); - // We are going to extract multiple buffers - let expectedNumberOfBuffers = 2; - // declare the big buffer - let buffer = new Buffer(k.OBCIPacketSize * expectedNumberOfBuffers + extraBuffer.length * 2); - // Fill that new big buffer with buffers - openBCIUtilities.samplePacketReal(0).copy(buffer, 0); - extraBuffer.copy(buffer, k.OBCIPacketSize); - openBCIUtilities.samplePacketReal(1).copy(buffer, k.OBCIPacketSize + extraBuffer.byteLength); - extraBuffer.copy(buffer, k.OBCIPacketSize * 2 + extraBuffer.byteLength); - // Reset the spy if it exists - if (_processQualifiedPacketSpy) _processQualifiedPacketSpy.reset(); - // Call the function under test - buffer = ourBoard._processDataBuffer(buffer); - // Ensure that we extracted only one buffer - _processQualifiedPacketSpy.should.have.been.calledTwice(); - // The buffer should not have anything in it any more - bufferEqual(Buffer.concat([extraBuffer, extraBuffer], 2), buffer).should.be.true(); - buffer.length.should.equal(extraBuffer.length * 2); - }); - }); - - /** - * Test the function that routes raw packets for processing - */ - describe('#_processQualifiedPacket', function () { - let ourBoard; - let funcSpyTimeSyncSet; - - before(function () { - ourBoard = new Cyton({ - verbose: true - }); - // Put watchers on all functions - funcSpyTimeSyncSet = sinon.spy(ourBoard, '_processPacketTimeSyncSet'); - }); - beforeEach(function () { - funcSpyTimeSyncSet.reset(); - - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - }); - after(function () { - // ourBoard = null - }); - after(() => bluebirdChecks.noPendingPromises()); - - it('should process a time sync set packet with accel', function () { - let buffer = openBCIUtilities.samplePacketAccelTimeSyncSet(); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // We should call to sync up - funcSpyTimeSyncSet.should.have.been.calledOnce(); - funcSpyTimeSyncSet.should.have.been.calledWith(buffer); - }); - it('should process a time sync set packet with raw aux', function () { - let buffer = openBCIUtilities.samplePacketRawAuxTimeSyncSet(0); - - // Call the function under test - ourBoard._processQualifiedPacket(buffer); - - // We should call to sync up - funcSpyTimeSyncSet.should.have.been.calledOnce(); - funcSpyTimeSyncSet.should.have.been.calledWith(buffer); - }); - it('should not identify any packet', function () { - let buffer = openBCIUtilities.samplePacket(0); - - // Set the stop byte to some number not yet defined - buffer[k.OBCIPacketPositionStopByte] = 0xCF; - - // Call the function under test - ourBoard._processDataBuffer(buffer); - - // Nothing should be called - funcSpyTimeSyncSet.should.not.have.been.called(); - }); - it('should emit a dropped packet on dropped packet', function (done) { - // Set to default state - ourBoard.previousSampleNumber = -1; - let sampleNumber0 = openBCIUtilities.samplePacket(0); - ourBoard.once('droppedPacket', () => { - done(); - }); - let sampleNumber2 = openBCIUtilities.samplePacket(2); - // Call the function under test - ourBoard._processDataBuffer(sampleNumber0); - ourBoard._processDataBuffer(sampleNumber2); - }); - it('should emit a dropped packet on dropped packet with edge', function (done) { - // Set to default state - let count = 0; - ourBoard.previousSampleNumber = 253; - let buf1 = openBCIUtilities.samplePacket(254); - let countFunc = arr => { - count++; - }; - ourBoard.on('droppedPacket', countFunc); - let buf2 = openBCIUtilities.samplePacket(0); - let buf3 = openBCIUtilities.samplePacket(1); - // Call the function under test - ourBoard._processDataBuffer(buf1); - ourBoard._processDataBuffer(buf2); - ourBoard._processDataBuffer(buf3); - setTimeout(() => { - ourBoard.removeListener('droppedPacket', countFunc); - expect(count).to.equal(1); - done(); - }, 10); - }); - }); - - describe('#_processPacketTimeSyncSet', function () { - let timeSyncSetPacket; - let ourBoard; - before(() => { - ourBoard = new Cyton({ - verbose: false - }); - }); - beforeEach(() => { - timeSyncSetPacket = openBCIUtilities.samplePacketRawAuxTimeSyncSet(); - ourBoard.sync.timeOffsetArray = []; - }); - afterEach(() => { - ourBoard.sync.curSyncObj = null; - }); - after(() => bluebirdChecks.noPendingPromises()); - it('should emit and return bad object if no sync in progress', function () { - let timeSetPacketArrived = ourBoard.time(); - let expectedTimeSyncOffsetMaster = 72; - ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; - ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; - ourBoard.once('synced', (syncObj) => { - expect(syncObj).to.have.property('valid', false); - expect(syncObj).to.have.property('error', k.OBCIErrorTimeSyncIsNull); - expect(syncObj).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - let syncObject = ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - expect(syncObject).to.have.property('valid', false); - expect(syncObject).to.have.property('error', k.OBCIErrorTimeSyncIsNull); - expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - it('should emit and return bad synced object if no sent confirmation found', function () { - let timeSetPacketArrived = ourBoard.time(); - let expectedTimeSyncOffsetMaster = 72; - ourBoard.once('synced', (syncObj) => { - expect(syncObj).to.have.property('valid', false); - expect(syncObj).to.have.property('error', k.OBCIErrorTimeSyncNoComma); - expect(syncObj).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; - ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - let syncObject = ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - expect(syncObject).to.have.property('valid', false); - expect(syncObject).to.have.property('error', k.OBCIErrorTimeSyncNoComma); - expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - it('should emit and return bad synced object with invalid raw packet', function () { - let timeSetPacketArrived = ourBoard.time(); - let expectedTimeSyncOffsetMaster = 72; - let badPacket; - if (k.getVersionNumber(process.version) >= 6) { - // from introduced in node version 6.x.x - badPacket = Buffer.from(timeSyncSetPacket.slice(0, 30)); - } else { - badPacket = new Buffer(timeSyncSetPacket.slice(0, 30)); - } - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - ourBoard.sync.timeOffsetMaster = expectedTimeSyncOffsetMaster; - ourBoard.once('synced', (syncObj) => { - expect(syncObj).to.have.property('valid', false); - expect(syncObj.error).to.have.property('message', k.OBCIErrorInvalidByteLength); - expect(syncObj).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - let syncObject = ourBoard._processPacketTimeSyncSet(badPacket, timeSetPacketArrived); - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - expect(syncObject).to.have.property('valid', false); - expect(syncObject.error).to.have.property('message', k.OBCIErrorInvalidByteLength); - expect(syncObject).to.have.property('timeOffsetMaster', expectedTimeSyncOffsetMaster); - }); - it('should calculate round trip time as the difference between time sent and time set packet arrived', function (done) { - let timeSetPacketArrived = ourBoard.time(); - let expectedRoundTripTime = 20; // ms - ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found - // Make a new object! - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // Set the sent time - ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; - - ourBoard.once('synced', obj => { - expect(obj.timeRoundTrip).to.equal(expectedRoundTripTime); - done(); - }); - ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - }); - it('should calculate transmission time as the difference between round trip time and (sentConf - sent) when set arrived - sent conf is larger than threshold', function (done) { - let timeSetPacketArrived = ourBoard.time(); - let expectedRoundTripTime = 20; // ms - let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 1; // 9 ms - // Setup - ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found - // Make a new object! - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // Set the sent time - ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; - ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - - ourBoard.once('synced', obj => { - expect(obj.timeTransmission).to.equal(obj.timeRoundTrip - (obj.timeSyncSentConfirmation - obj.timeSyncSent)); - done(); - }); - ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - }); - it('should calculate transmission time as a percentage of round trip time when set arrived - sent conf is smaller than threshold', function (done) { - let timeSetPacketArrived = ourBoard.time(); - let expectedRoundTripTime = 20; // ms - let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS + 1; // 11 ms - // Setup - ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found - // Make a new object! - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // Set the sent time - ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; - ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - - ourBoard.once('synced', obj => { - expect(obj.timeTransmission).to.equal(obj.timeRoundTrip * k.OBCITimeSyncMultiplierWithSyncConf); - done(); - }); - ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - }); - it('should calculate offset time as a time packet arrived - transmission time - board time', function (done) { - let timeSetPacketArrived = ourBoard.time(); - let expectedRoundTripTime = 20; // ms - let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms - // Set the board time - let boardTime = 5000; - timeSyncSetPacket.writeInt32BE(boardTime, k.OBCIPacketPositionTimeSyncTimeStart); - - // Setup - ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found - // Make a new object! - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // Set the sent time - ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; - ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - - let expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); - - let expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; - - ourBoard.once('synced', obj => { - expect(obj.timeOffset, 'object timeOffset').to.equal(expectedTimeOffset); - expect(ourBoard.sync.timeOffsetMaster, 'master time offset').to.equal(expectedTimeOffset); - done(); - }); - ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - }); - it('should calculate offset time as an average of previous offset times', function (done) { - let timeSetPacketArrived = ourBoard.time(); - let expectedRoundTripTime = 20; // ms - let expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms - // Set the board time - let boardTime = 5000; - timeSyncSetPacket.writeInt32BE(boardTime, k.OBCIPacketPositionTimeSyncTimeStart); - - // Setup - ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found - // Make a new object! - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // Set the sent time - ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime; - ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf; - - let expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent); - - let expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime; - - let dif1 = 3; - ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif1); - let dif2 = 1; - ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif2); - - ourBoard.once('synced', obj => { - expect(obj.timeOffset, 'object timeOffset').to.equal(expectedTimeOffset); - - let expectedMasterTimeoffset = math.floor((obj.timeOffset + (obj.timeOffset + dif1) + (obj.timeOffset + dif2)) / 3); - expect(ourBoard.sync.timeOffsetMaster, 'master time offset').to.equal(expectedMasterTimeoffset); - done(); - }); - ourBoard._processPacketTimeSyncSet(timeSyncSetPacket, timeSetPacketArrived); - }); - }); - - describe('#time', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should use sntp time when sntpTimeSync specified in options', function (done) { - let board = new Cyton({ - verbose: true, - sntpTimeSync: true - }); - board.on('sntpTimeLock', function () { - let funcSpySntpNow = sinon.spy(board, '_sntpNow'); - board.time(); - funcSpySntpNow.should.have.been.calledOnce(); - funcSpySntpNow.restore(); - board.sntpStop(); - done(); - }); - }); - it('should use Date.now() for time when sntpTimeSync is not specified in options', function () { - let board = new Cyton({ - verbose: true - }); - let funcSpySntpNow = sinon.spy(board, '_sntpNow'); - - board.time(); - - funcSpySntpNow.should.not.have.been.called(); - - funcSpySntpNow.reset(); - - funcSpySntpNow = null; - }); - it('should emit sntpTimeLock event after sycned with ntp server', function (done) { - let board = new Cyton({ - verbose: true, - sntpTimeSync: true - }); - - board.once('sntpTimeLock', () => { - board.sntpStop(); - done(); - }); - }); - }); - describe('#sntpStart', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should be able to start ntp server', () => { - let board = new Cyton(); - expect(board.sntp.isLive()).to.be.false(); - return Promise.all([ - board.sntpStart() - .then(() => { - expect(board.sntp.isLive()).to.be.true(); - }), - new Promise(resolve => { - board.once('sntpTimeLock', resolve); - }) - ]).then(() => { - board.sntpStop(); - }); - }); - }); - describe('#sntpStop', function () { - this.timeout(5000); - let board; - before(done => { - board = new Cyton({ - sntpTimeSync: true - }); - board.once('sntpTimeLock', () => { - done(); - }); - }); - after(() => { - board.sntpStop(); - }); - after(() => bluebirdChecks.noPendingPromises()); - it('should be able to stop the ntp server and set the globals correctly', function () { - // Verify the before condition is correct - expect(board.options.sntpTimeSync).to.be.true(); - expect(board.sync.sntpActive).to.be.true(); - expect(board.sntp.isLive()).to.be.true(); - - // Call the function under test - board.sntpStop(); - - // Ensure the globals were set off - expect(board.options.sntpTimeSync).to.be.false(); - expect(board.sync.sntpActive).to.be.false(); - expect(board.sntp.isLive()).to.be.false(); - }); - }); - describe('#sntpGetOffset', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should get the sntp offset', function (done) { - let board = new Cyton({ - sntpTimeSync: true - }); - board.once('sntpTimeLock', () => { - board.sntpGetOffset().then(offset => { - board.sntpStop(); - done(); - }, done); - }); - }); - }); - describe('#_processParseBufferForReset', function () { - let ourBoard; - - before(() => { - ourBoard = new Cyton({ - verbose: true - }); - }); - beforeEach(() => { - ourBoard.info = { - boardType: 'burrito', - sampleRate: 60, - firmware: 'taco', - numberOfChannels: 200 - }; - }); - - after(() => { - ourBoard = null; - }); - after(() => bluebirdChecks.noPendingPromises()); - - it('should recognize firmware version 1 with no daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -LIS3DH Device ID: 0x38422$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV1); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); - }); - it('should recognize firmware version 1 with daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV1); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); - }); - it('should recognize firmware version 2 with no daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -LIS3DH Device ID: 0x38422 -Firmware: v2.0.0 -$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV2); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); - }); - it('should recognize firmware version 3 with no daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -LIS3DH Device ID: 0x38422 -Firmware: v3.0.1 -$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV3); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardCyton); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate250); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsCyton); - }); - it('should recognize firmware version 2 with daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -Firmware: v2.0.0 -$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV2); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); - }); - it('should recognize firmware version 3 with daisy', () => { - let buf = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -On Daisy ADS1299 Device ID: 0xFFFFF -LIS3DH Device ID: 0x38422 -Firmware: v3.1.69 -$$$`); - - ourBoard._processParseBufferForReset(buf); - - ourBoard.info.firmware.should.equal(k.OBCIFirmwareV3); - expect(ourBoard.getBoardType()).to.equal(k.OBCIBoardDaisy); - expect(ourBoard.sampleRate()).to.equal(k.OBCISampleRate125); - expect(ourBoard.numberOfChannels()).to.equal(k.OBCINumberOfChannelsDaisy); - }); - }); - - describe('#_processBytes', function () { - before(() => { - ourBoard = new Cyton({ - verbose: true - }); - }); - beforeEach(() => { - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - }); - afterEach(() => { - ourBoard.buffer = null; - }); - after(() => bluebirdChecks.noPendingPromises()); - - describe('#OBCIParsingReset', function () { - let _processParseBufferForResetSpy; - before(() => { - _processParseBufferForResetSpy = sinon.spy(ourBoard, '_processParseBufferForReset'); - }); - beforeEach(() => { - _processParseBufferForResetSpy.reset(); - }); - it('should wait till EOT ($$$) before starting parse', function () { - let buf1 = new Buffer(`OpenBCI V3 Simulator -On Board ADS1299 Device ID: 0x12345 -`); - let buf2 = new Buffer(`LIS3DH Device ID: `); - let buf3 = new Buffer(`0x38422 -$$$`); - - // Fake a soft reset send - ourBoard.curParsingMode = k.OBCIParsingReset; - - // Send the first buffer - ourBoard._processBytes(buf1); - // Verify the parse function was not called - _processParseBufferForResetSpy.should.not.have.been.called(); - // Verify the global buffer has the first buf in it - bufferEqual(ourBoard.buffer, buf1); - // Send another buffer without EOT - ourBoard._processBytes(buf2); - // Verify the parse function was not called - _processParseBufferForResetSpy.should.not.have.been.called(); - // Verify the global buffer has the first and second buf in it - bufferEqual(ourBoard.buffer, Buffer.concat([buf1, buf2])); - // Send another buffer without EOT - ourBoard._processBytes(buf3); - // Verify the parse function was called - _processParseBufferForResetSpy.should.have.been.calledOnce(); - // Verify the global buffer is empty - expect(ourBoard.buffer).to.be.null(); - }); - }); - - describe('#OBCIParsingTimeSyncSent', function () { - // let spy - before(() => { - // spy = sinon.spy(ourBoard.openBCIUtilities,"isTimeSyncSetConfirmationInBuffer") - }); - beforeEach(() => { - ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent; - ourBoard.sync.curSyncObj = openBCIUtilities.newSyncObject(); - // spy.reset() - }); - it('should call to find the time sync set character in the buffer', function () { - // Verify the log event is called - let buf = new Buffer(','); - // Call the processBytes function - ourBoard._processBytes(buf); - // Verify the function was called - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - - let emitted = false; - // Listen for the sample event - ourBoard.once('sample', () => { - emitted = true; - }); - // Make a new buffer - let buf1 = openBCIUtilities.samplePacketReal(1); - // Send the buffer in - ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null(); - expect(emitted).to.be.true(); - }); - it('should clear the buffer after a time sync set packet', function () { - let emitted = false; - // Verify the log event is called - let buf = new Buffer(','); - // Call the processBytes function - ourBoard._processBytes(buf); - // Verify the function was called - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - - // Listen for the sample event - ourBoard.once('synced', () => { - // Verify the buffer is cleared - emitted = true; - }); - // Make a new buffer - let buf1 = openBCIUtilities.samplePacketAccelTimeSyncSet(1); - // Send the buffer in - ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null(); - expect(emitted).to.be.true(); - }); - it('should call to find the time sync set character in the buffer after packet', function () { - let buf1 = openBCIUtilities.samplePacket(); - let buf2 = new Buffer(','); - - // Call the processBytes function - ourBoard._processBytes(Buffer.concat([buf1, buf2], buf1.length + 1)); - // Verify the function was called - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - // Verify the buffer is empty - // expect(ourBoard.buffer).to.be.null - // ourBoard.buffer.length.should.equal(0) - }); - it('should find time sync and emit two samples', function (done) { - let buf1 = openBCIUtilities.samplePacket(250); - let buf2 = new Buffer([0x2C]); - let buf3 = openBCIUtilities.samplePacket(251); - - let inputBuf = Buffer.concat([buf1, buf2, buf3], buf1.byteLength + 1 + buf3.byteLength); - - let sampleCounter = 0; - - let newSample = sample => { - // console.log(`sample ${JSON.stringify(sample)}`) - if (sampleCounter === 0) { - sample.sampleNumber.should.equal(250); - } else if (sampleCounter === 1) { - sample.sampleNumber.should.equal(251); - // bufferEqual(buf1, buffer).should.be.true - // ourBoard.buffer.length.should.equal(buf1.length) - ourBoard.removeListener('sample', newSample); - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal); - done(); - } - sampleCounter++; - }; - - ourBoard.on('sample', newSample); - - // Call the processBytes function - ourBoard._processBytes(inputBuf); - }); - it('should not find the packet if in packet', () => { - let buf1 = openBCIUtilities.samplePacket(250); - buf1[4] = 0x2C; // Inject a false packet - let buf2 = openBCIUtilities.samplePacket(251); - - // Call the processBytes function - ourBoard._processBytes(Buffer.concat([buf1, buf2], buf1.length + buf2.length)); - // Verify the time sync set was NOT called - expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingTimeSyncSent); - }); - }); - - describe('#OBCIParsingNormal', function () { - before(() => { - ourBoard.curParsingMode = k.OBCIParsingNormal; - }); - it('should emit a sample when inserted', function (done) { - let expectedSampleNumber = 0; - let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - - // Declare the event emitter prior to calling function - ourBoard.once('sample', sample => { - sample.sampleNumber.should.equal(expectedSampleNumber); - }); - - // Now call the function which should call the "sample" event - ourBoard._processBytes(buf1); - expect(ourBoard.buffer).to.be.null(); - done(); - }); - it('should get three packets even if one was sent in the last data emit', function () { - let expectedSampleNumber = 0; - let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); - // Pretend that half of buf1 got sent in the first serial flush - // and that the last half of it will arrive a lil later - let splitPoint = 15; - if (k.getVersionNumber(process.version) >= 6) { - // from introduced in node version 6.x.x - ourBoard.buffer = Buffer.from(buf1.slice(0, splitPoint)); - } else { - ourBoard.buffer = new Buffer(buf1.slice(0, splitPoint)); - } - let dataBuf = Buffer.concat([buf1.slice(splitPoint), buf2, buf3]); - - let sampleCounter = 0; - let newSample = sample => { - if (sampleCounter === expectedSampleNumber) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber); - } else if (sampleCounter === expectedSampleNumber + 1) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 1); - } else if (sampleCounter === expectedSampleNumber + 2) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 2); - ourBoard.removeListener('sample', newSample); - } - sampleCounter++; - }; - ourBoard.on('sample', newSample); - // Now call the function which should call the "sample" event - ourBoard._processBytes(dataBuf); - expect(ourBoard.buffer).to.be.null(); - }); - it('should keep extra data in the buffer', function () { - let expectedSampleNumber = 0; - let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); - // Pretend that half of buf1 got sent in the first serial flush - // and that the last half of it will arrive a lil later - let splitPoint = 15; - - ourBoard['buffer'] = null; - let bufFirstHalf, bufLastHalf; - if (k.getVersionNumber(process.version) >= 6) { - // from introduced in node version 6.x.x - bufFirstHalf = Buffer.from(buf3.slice(0, splitPoint)); - bufLastHalf = Buffer.from(buf3.slice(splitPoint)); - } else { - bufFirstHalf = new Buffer(buf3.slice(0, splitPoint)); - bufLastHalf = new Buffer(buf3.slice(splitPoint)); - } - - let sampleCounter = 0; - let newSample = sample => { - if (sampleCounter === expectedSampleNumber) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber); - } else if (sampleCounter === expectedSampleNumber + 1) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 1); - } else if (sampleCounter === expectedSampleNumber + 2) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 2); - ourBoard.removeListener('sample', newSample); - } - sampleCounter++; - }; - ourBoard.on('sample', newSample); - // Now call the function which should call the "sample" event - ourBoard._processBytes(Buffer.concat([buf1, buf2, bufFirstHalf])); - // Now verify there is data still in the global buffer by calling _processBytes on the last half - ourBoard._processBytes(bufLastHalf); - expect(ourBoard.buffer).to.be.null(); - }); - it('should throw out old data if it is incomplete and add to badPackets count', function () { - // Some how this packet go messed up and lodged in... This is the worst case, that the buffer has - // an incomplete packet. - ourBoard.buffer = new Buffer([0xA0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xC0]); - - // New buffer incoming - let expectedSampleNumber = 1; - let buf1 = openBCIUtilities.samplePacketReal(expectedSampleNumber); - let buf2 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 1); - let buf3 = openBCIUtilities.samplePacketReal(expectedSampleNumber + 2); - - // New data incoming! - let dataBuf = Buffer.concat([buf1, buf2, buf3]); - - let sampleCounter = expectedSampleNumber; - let newSample = sample => { - if (sampleCounter === expectedSampleNumber) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber); - } else if (sampleCounter === expectedSampleNumber + 1) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 1); - } else if (sampleCounter === expectedSampleNumber + 2) { - expect(sample.sampleNumber).to.equal(expectedSampleNumber + 2); - ourBoard.removeListener('sample', newSample); - } - sampleCounter++; - }; - ourBoard.on('sample', newSample); - // Now call the function which should call the "sample" event - ourBoard._processBytes(dataBuf); - // Verify that the old data was rejected - expect(ourBoard.buffer).to.be.null(); - }); - }); - - describe('#OBCIParsingEOT', function () { - beforeEach(() => { - ourBoard.curParsingMode = k.OBCIParsingEOT; - }); - it("should emit the 'eot' event", function (done) { - let buf = new Buffer('Tacos are amazing af$$$'); - - let eotEvent = data => { - expect(bufferEqual(data, buf)).to.be.true(); - ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); - done(); - }; - - ourBoard.once('eot', eotEvent); - - ourBoard._processBytes(buf); - }); - it("should emit the 'eot' event even if stuff comes in two serial flushes", function (done) { - let buf1 = new Buffer('Tacos are '); - let buf2 = new Buffer('amazing af$$$'); - - let eotEvent = data => { - bufferEqual(data, Buffer.concat([buf1, buf2], buf1.length + buf2.length)).should.be.true(); - ourBoard.curParsingMode.should.be.equal(k.OBCIParsingNormal); - done(); - }; - - ourBoard.once('eot', eotEvent); - - ourBoard._processBytes(buf1); - ourBoard._processBytes(buf2); - }); - }); - }); - - describe('#_finalizeNewSampleForDaisy', function () { - let ourBoard, randomSampleGenerator, sampleEvent, failTimeout; - before(() => { - ourBoard = new Cyton({ - verbose: true - }); - randomSampleGenerator = openBCIUtilities.randomSample(k.OBCINumberOfChannelsCyton, k.OBCISampleRate250, false, 'none'); - }); - beforeEach(() => { - // Clear the global let - ourBoard._lowerChannelsSampleObject = null; - ourBoard.info.missedPackets = 0; - }); - afterEach(() => { - if (sampleEvent) { - ourBoard.removeListener('sample', sampleEvent); - sampleEvent = null; - } - if (failTimeout) { - clearTimeout(failTimeout); - failTimeout = null; - } - }); - after(() => bluebirdChecks.noPendingPromises()); - it('should store the sample to a global letiable for next time', () => { - let oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) - - // Call the function under test - ourBoard._finalizeNewSampleForDaisy(oddSample); - - // Check to make sure the letiable is stored - expect(ourBoard._lowerChannelsSampleObject).to.equal(oddSample); - }); - it('should emit a sample on even sample if odd was before', function (done) { - let oddSample = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) - let evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) - - // The function to be called when sample event is fired - let sampleEvent = (sample) => { - // test pass here - done(); - }; - - // Subscribe to the sample event - ourBoard.once('sample', sampleEvent); - - // Call the function under test twice - ourBoard._finalizeNewSampleForDaisy(oddSample); - ourBoard._finalizeNewSampleForDaisy(evenSample); - - // Set a timeout to end the function, after giving enough time for the sample to be emitted if were going to - // be - failTimeout = setTimeout(() => { - // Fail condition - done("didn't emit a sample"); - }, 5); // 5ms should be plenty of time - }); - it('should not emit a sample if there is no lower sample object and this is an even sample number', function (done) { - let evenSample = randomSampleGenerator(1); // Previous was 1, so the next one will be 2 (even) - - // The function to be called when sample event is fired - sampleEvent = (sample) => { - // test fail condition - done('emitted a sample'); - }; - - console.log('_lowerChannelsSampleObject', ourBoard._lowerChannelsSampleObject); - - // Subscribe to the sample event - ourBoard.once('sample', sampleEvent); - - // Call the function under test - ourBoard._finalizeNewSampleForDaisy(evenSample); - - // Set a timeout to end the function, after giving enough time for the sample to be emitted if were going to - // be - failTimeout = setTimeout(() => { - // This is the condition where an odd was skipped so need to keep track of this as a missed packet - expect(ourBoard.info.missedPackets).to.equal(1); - done(); // Test pass here - }, 5); // 5ms should be plenty of time - }); - it('should not emit a sample if back to back odd samples', function (done) { - let oddSample1 = randomSampleGenerator(0); // Previous was 0, so the next one will be 1 (odd) - let oddSample2 = randomSampleGenerator(2); // Previous was 0, so the next one will be 1 (odd) - - // The function to be called when sample event is fired - sampleEvent = (sample) => { - // test fail condition - done('emitted a sample'); - }; - - // Subscribe to the sample event - ourBoard.once('sample', sampleEvent); - - // Call the function under test twice - ourBoard._finalizeNewSampleForDaisy(oddSample1); - ourBoard._finalizeNewSampleForDaisy(oddSample2); - - // Set a timeout to end the function, after giving enough time for the sample to be emitted if were going to - // be - failTimeout = setTimeout(() => { - // This is the condition where an even was skipped so need to keep track of this as a missed packet - expect(ourBoard.info.missedPackets).to.equal(1); - ourBoard.removeListener('sample', sampleEvent); - done(); // Test pass here - }, 5); // 5ms should be plenty of time - }); - }); - - describe('#usingVersionTwoFirmware', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should return true if firmware is version 2', () => { - ourBoard = new Cyton(); - ourBoard.info.firmware = 'v2'; - - expect(ourBoard.usingVersionTwoFirmware()).to.be.true(); - }); - it('should return false if not firmware version 2', () => { - ourBoard = new Cyton(); - - expect(ourBoard.usingVersionTwoFirmware()).to.be.false(); - }); - }); - - describe('#usingVersionThreeFirmware', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should return true if firmware is version 3', () => { - ourBoard = new Cyton(); - ourBoard.info.firmware = 'v3'; - - expect(ourBoard.usingVersionThreeFirmware()).to.be.true(); - }); - it('should return false if not firmware version 3', () => { - ourBoard = new Cyton(); - - expect(ourBoard.usingVersionThreeFirmware()).to.be.false(); - }); - }); - - describe('#usingAtLeastVersionTwoFirmware', function () { - after(() => bluebirdChecks.noPendingPromises()); - it('should return true if firmware is version 3', () => { - ourBoard = new Cyton(); - ourBoard.info.firmware = 'v2'; - - expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.true(); - }); - it('should return true if firmware is version 3', () => { - ourBoard = new Cyton(); - ourBoard.info.firmware = 'v3'; - - expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.true(); - }); - it('should return false if not firmware version 3', () => { - ourBoard = new Cyton(); - - expect(ourBoard.usingAtLeastVersionTwoFirmware()).to.be.false(); - }); - }); - - xdescribe('#hardwareValidation', function () { - this.timeout(20000); // long timeout for pleanty of stream time :) - let runHardwareValidation = true; - let wstream; - let board; - before(function (done) { - if (masterPortName === k.OBCISimulatorPortName) { - runHardwareValidation = false; - } - if (runHardwareValidation) { - board = new Cyton({ - verbose: true, - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - // Use the line below to output the - wstream = fs.createWriteStream('hardwareVoltageOutputAll.txt'); - - board.connect(masterPortName) - .catch(err => done(err)); - - board.once('ready', () => { - done(); - }); - } else { - done(); - } - }); - after(function () { - if (runHardwareValidation) { - board.disconnect(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - it('test all output signals', function (done) { - if (runHardwareValidation) { - board.streamStart() - .then(() => { - console.log('Started stream'); - console.log('--------'); - }) - .catch(err => done(err)); - - setTimeout(() => { - console.log('*-------'); - board.testSignal('pulse1xSlow'); - }, 3000); - setTimeout(() => { - console.log('**------'); - board.testSignal('pulse2xSlow'); - }, 5000); - setTimeout(() => { - console.log('***-----'); - board.testSignal('pulse1xFast'); - }, 7000); - setTimeout(() => { - console.log('****----'); - board.testSignal('pulse2xFast'); - }, 9000); - setTimeout(() => { - console.log('*****---'); - board.testSignal('none'); - }, 11000); - setTimeout(() => { - console.log('******--'); - board.testSignal('pulse1xSlow'); - }, 13000); - setTimeout(() => { - console.log('*******-'); - board.testSignal('none'); - }, 15000); - - board.on('sample', sample => { - openBCIUtilities.samplePrintLine(sample) - .then(line => { - wstream.write(line); - }); - }); - // This stops the test - setTimeout(() => { - console.log('********'); - done(); - }, 19000); - } else { - done(); - } - }); - }); -}); - -describe('#daisy', function () { - let ourBoard; - this.timeout(4000); - before(function (done) { - ourBoard = new Cyton({ - verbose: true, - simulatorFirmwareVersion: 'v2', - simulatorDaisyModuleAttached: true, - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - - let useSim = () => { - ourBoard.simulatorEnable() - .then(() => { - console.log(`has daisy module: ${ourBoard.options.simulatorDaisyModuleAttached}`); - return ourBoard.connect(k.OBCISimulatorPortName); - }) - .then(() => { - return ourBoard.softReset(); - }) - .catch(err => console.log(err)); - }; - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - return setTimeout(() => { - console.log('Issuing connect'); - ourBoard.connect(portName); - }, 500); - }) - .catch(() => { - useSim(); - }) - .then(() => { - // console.log('connected') - }) - .catch(err => { - console.log('Error: ' + err); - }); - - ourBoard.once('ready', () => { - done(); - }); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - it('can get samples with channel array of length 16 if daisy', function (done) { - let numberOfSamples = 130; - let sampleCount = 0; - - if (ourBoard.info.boardType !== k.OBCIBoardDaisy) { - return done(); - } - let samp = sample => { - expect(sample.channelData.length).to.equal(k.OBCINumberOfChannelsDaisy); - if (sampleCount <= numberOfSamples) { - sampleCount++; - } else { - ourBoard.disconnect() - .then(() => { - done(); - }); - ourBoard.removeListener('sample', samp); - } - }; - ourBoard.on('sample', samp); - ourBoard.streamStart() - .catch(err => { - done(err); - }); - // Attached the emitted - }); -}); - -describe('#syncWhileStreaming', function () { - let ourBoard; - this.timeout(4000); - before(function (done) { - ourBoard = new Cyton({ - verbose: true, - simulatorFirmwareVersion: 'v2', - simulatorFragmentation: k.OBCISimulatorFragmentationRandom - }); - let useSim = () => { - ourBoard.simulatorEnable() - .then(() => { - console.log(`sim firmware version: ${ourBoard.options.simulatorFirmwareVersion}`); - return ourBoard.connect(k.OBCISimulatorPortName); - }) - .then(() => { - return ourBoard.softReset(); - }) - .catch(err => console.log(err)); - }; - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - return setTimeout(() => { - console.log('Issuing connect'); - ourBoard.connect(portName); - }, 500); - }) - .catch(() => { - useSim(); - }) - .then(() => { - // console.log('connected') - }) - .catch(err => { - console.log('Error: ' + err); - }); - - ourBoard.once('ready', () => { - ourBoard.streamStart() - .then(() => { - done(); - }) - .catch(err => { - done(err); - }); - }); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - afterEach(() => { - this.buffer = null; - }); - describe('#syncClocks', function () { - this.timeout(4000); - it('can sync while streaming', done => { - let syncAfterSamples = 50; - let notSynced = true; - let syncFunc = obj => { - ourBoard.removeListener('sample', samp); - done(); - }; - let samp = sample => { - if (sample.sampleNumber >= syncAfterSamples && notSynced) { - notSynced = false; - // Call the first one - ourBoard.syncClocks() - .catch(() => { - ourBoard.removeListener('sample', samp); - ourBoard.removeListener('synced', syncFunc); - done(); - }); - } - }; - ourBoard.on('sample', samp); - ourBoard.once('synced', syncFunc); - }); - }); - describe('#syncClocksFull', function () { - this.timeout(4000); - it('can run a full clock sync', done => { - let notSynced = true; - let sampleFun = sample => { - if (notSynced) { - notSynced = false; - // Call the first one - ourBoard.syncClocksFull() - .then(syncObj => { - if (syncObj.valid) { - ourBoard.removeListener('sample', sampleFun); - done(); - } else { - ourBoard.removeListener('sample', sampleFun); - done('Not able to sync'); - } - }).catch(() => { - ourBoard.removeListener('sample', sampleFun); - done(); - }); - } - }; - ourBoard.on('sample', sampleFun); - }); - }); -}); - -describe('#syncErrors', function () { - let ourBoard; - this.timeout(4000); - before(function (done) { - ourBoard = new Cyton({ - verbose: true, - simulatorFirmwareVersion: 'v2' - }); - let useSim = () => { - ourBoard.simulatorEnable() - .then(() => { - return ourBoard.connect(k.OBCISimulatorPortName); - }) - .then(() => { - return ourBoard.softReset(); - }) - .catch(err => console.log(err)); - }; - ourBoard.autoFindOpenBCIBoard() - .then(portName => { - return setTimeout(() => { - console.log('Issuing connect'); - ourBoard.connect(portName); - }, 500); - }) - .catch(() => { - useSim(); - }) - .then(() => { - // console.log('connected'); - }) - .catch(err => { - console.log('Error: ' + err); - }); - - ourBoard.once('ready', () => { - done(); - }); - }); - after(function (done) { - if (ourBoard.isConnected()) { - ourBoard.disconnect().then(() => { - done(); - }).catch(() => done); - } else { - done(); - } - }); - after(() => bluebirdChecks.noPendingPromises()); - afterEach(() => { - this.buffer = null; - }); - describe('#syncClocksFull', function () { - it('should reject syncClocksFull request because of timeout', done => { - let notSynced = true; - let sampleFun = sample => { - if (notSynced) { - notSynced = false; - // Call the first one - ourBoard.syncClocksFull() - .then(syncObj => { - done('Should not be able to sync'); - }).catch(() => { - ourBoard.removeListener('sample', sampleFun); - done(); - }); - ourBoard.streamStop(); - } - }; - ourBoard.streamStart() - .catch((err) => { - ourBoard.removeListener('sample', sampleFun); - done(`could not start time sync with err: ${err}`); - }); - ourBoard.on('sample', sampleFun); - }); - }); -}); diff --git a/test/timingEventsAsPromises.js b/test/timingEventsAsPromises.js deleted file mode 100644 index 8ab0c88..0000000 --- a/test/timingEventsAsPromises.js +++ /dev/null @@ -1,108 +0,0 @@ -// Converts timing events to use promises, to gain bluebird's checks - -function instrumentTimingEvents (module, type) { - // replaces module[type] with a function that does the same thing but uses a promise - // assumes the first argument will be to a callback function - - // if this a setSomething function with a clearSomething partner, the partner will also be instrumented - var setName = type; - var clearName = null; - if (type.substring(0, 3) === 'set') { - clearName = 'clear' + type.substring(3); - } - - if (!module[setName]) return; - - // store original functions - var originalSet = module[setName]; - var originalClear = module[clearName]; - exports[setName + 'Ignored'] = originalSet; - - // dictionary to store promise details for each call - var events = {}; - - // setAsPromise() is the brunt of the function. It replaces the previous global function, - // and sets up a promise to resolve when the callback is called - var eventCount = 0; - var setAsPromise = function (callback) { - var args = [].slice.call(arguments); - - var eventNum = ++eventCount; - var eventHandle; - - if (setAsPromise._ignoreCount > 0) { - --setAsPromise._ignoreCount; - - return originalSet.apply(this, args); - } - - // actual callback is replaced by promise resolve - args[0] = function () { - if (!events[eventNum]) throw new Error(setName + ' ' + eventNum + ' disappeared'); - events[eventNum].resolve([].slice.call(arguments)); - }; - - eventHandle = originalSet.apply(this, args) || eventNum; - - // this portion is a function so that setInterval may be handled via recursion - function dispatch () { - var handlerDetails = { handle: eventHandle }; - - var promise = new Promise((resolve, reject) => { - handlerDetails.resolve = resolve; - handlerDetails.reject = reject; - }); - - handlerDetails.promise = promise; - events[eventNum] = handlerDetails; - - promise.then(function (argumentArray) { - if (type !== 'setInterval') { - delete events[eventNum]; - } else { - dispatch(); - } - - // call original handler - callback.apply(this, argumentArray); - }, () => { - originalClear(eventHandle); - delete events[eventNum]; - }); - - return promise; - } - - dispatch(); - - return eventNum; - }; - - // actually replace functions with instrumented ones - setAsPromise._ignoreCount = 0; - module[setName] = setAsPromise; - Object.defineProperty(setAsPromise, 'name', { value: setName + 'AsPromise' }); - - if (clearName) { - module[clearName] = (eventNum) => { - if (!events[eventNum]) { - originalClear(eventNum); - } else { - events[eventNum].reject(new Error('cleared')); - } - }; - Object.defineProperty(module[clearName], 'name', { value: clearName + 'AsPromise' }); - } -} - -instrumentTimingEvents(global, 'setTimeout'); -instrumentTimingEvents(global, 'setInterval'); -instrumentTimingEvents(global, 'setImmediate'); -// // Possible TODO: nextTick needs some exceptions included to prevent infinite recursion -// instrumentTimingEvents(process, 'nextTick'); - -// the next call to the passed function should not be promisified -// may be queued multiple times -exports.ignoreOnce = (ignored) => { - ignored._ignoreCount ++; -}; From ff8fb8136ac0b513727732401f2218d8e09c7372 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 4 Oct 2017 19:59:26 -0400 Subject: [PATCH 39/42] FIX: Remove cyton file and bump versions of dependencies --- index.js | 4 +- openBCICyton.js | 2310 ----------------------------------------------- package.json | 12 +- 3 files changed, 9 insertions(+), 2317 deletions(-) delete mode 100644 openBCICyton.js diff --git a/index.js b/index.js index 9202f29..ba8fe01 100644 --- a/index.js +++ b/index.js @@ -1 +1,3 @@ -module.exports.Cyton = require('./openBCICyton'); +module.exports.Cyton = require('openbci-cyton'); +module.exports.Ganglion = require('openbci-ganglion'); +module.exports.Wifi = require('openbci-wifi'); diff --git a/openBCICyton.js b/openBCICyton.js deleted file mode 100644 index 4293ff2..0000000 --- a/openBCICyton.js +++ /dev/null @@ -1,2310 +0,0 @@ -'use strict'; -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const SerialPort = require('serialport'); -const OpenBCIUtilities = require('openbci-utilities'); -const obciUtils = OpenBCIUtilities.Utilities; -const k = OpenBCIUtilities.Constants; -const obciDebug = OpenBCIUtilities.Debug; -const OpenBCISimulator = OpenBCIUtilities.Simulator; -const Sntp = require('sntp'); -const bufferEqual = require('buffer-equal'); -const math = require('mathjs'); -const _ = require('lodash'); -const Buffer = require('safe-buffer').Buffer; - -/** - * @typedef {Object} InitializationObject Board optional configurations. - * @property {Number} baudRate Baud Rate, defaults to 115200. Manipulating this is allowed if - * firmware on board has been previously configured. - * - * @property {String} boardType Specifies type of OpenBCI board. - * 3 Possible Boards: - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel OpenBCI board with Daisy Module. Total of 16 channels. - * `ganglion` - 4 Channel board - * (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016) - * - * @property {Boolean} hardSet Recommended if using `daisy` board! For some reason, the `daisy` is sometimes - * not picked up by the module so you can set `hardSet` to true which will ensure the daisy - * is picked up. (Default `false`) - * - * @property {Boolean} simulate Full functionality, just mock data. Must attach Daisy module by setting - * `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) - * - * @property {Boolean} simulatorBoardFailure Simulates board communications failure. This occurs when the RFduino on - * the board is not polling the RFduino on the dongle. (Default `false`) - * - * @property {Boolean} simulatorDaisyModuleAttached Simulates a daisy module being attached to the OpenBCI board. - * This is useful if you want to test how your application reacts to a user requesting 16 channels - * but there is no daisy module actually attached, or vice versa, where there is a daisy module - * attached and the user only wants to use 8 channels. (Default `false`) - * - * @property {Boolean} simulatorDaisyModuleCanBeAttached Allows the simulation of the a hot swapped daisy board. - * For example: You coule simulate if the board has only detected 8 channels and the user requested - * 16 channels. (Default `true`) - * - * @property {String} simulatorFirmwareVersion Allows simulator to be started with firmware version 2 features - * 2 Possible Options: - * `v1` - Firmware Version 1 (Default) - * `v2` - Firmware Version 2 - * - * @property {String} simulatorFragmentation Specifies how to break packets to simulate fragmentation, which - * occurs commonly in real devices. It is recommended to test code with this enabled. - * 4 Possible Options: - * `none` - do not fragment packets; output complete chunks immediately when produced (Default) - * `random` - output random small chunks of data interspersed with full buffers - * `fullBuffers` - allow buffers to fill up until the latency timer has expired - * `oneByOne` - output each byte separately - * - * @property {Number} simulatorLatencyTime The time in milliseconds to wait before sending partially full buffers, - * if `simulatorFragmentation` is specified. (Default `16`) - * - * @property {Number} simulatorBufferSize The size of a full buffer of data, if `simulatorFragmentation` is - * specified. (Default `4096`) - * - * @property {Boolean} simulatorHasAccelerometer Sets simulator to send packets with accelerometer data. (Default `true`) - * - * @property {Boolean} simulatorInjectAlpha Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) - * - * @property {String} simulatorInjectLineNoise Injects line noise on channels. - * 3 Possible Options: - * `60Hz` - 60Hz line noise (Default) [America] - * `50Hz` - 50Hz line noise [Europe] - * `none` - Do not inject line noise. - * - * @property {Number} simulatorSampleRate The sample rate to use for the simulator. Simulator will set to 125 if - * `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that - * setting and this sample rate will be used. (Default is `250`) - * - * @property {Boolean} simulatorSerialPortFailure Simulates not being able to open a serial connection. Most likely - * due to a OpenBCI dongle not being plugged in. - * - * @property {Boolean} sntpTimeSync Syncs the module up with an SNTP time server and uses that as single source - * of truth instead of local computer time. If you are running experiements on your local - * computer, keep this `false`. (Default `false`) - * - * @property {String} sntpTimeSyncHost The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`). - * - * @property {Number} sntpTimeSyncPort The port to access the ntp server. (Defaults `123`) - * - * @property {Boolean} verbose Print out useful debugging events. (Default `false`) - * - * @property {Boolean} debug Print out a raw dump of bytes sent and received. (Default `false`) - * - * @property {Boolean} sendCounts - Send integer raw counts instead of scaled floats. - * (Default `false`) -*/ - -/** - * Options object - * @type {InitializationObject} - * @private - */ -var _options = { - boardType: [k.OBCIBoardCyton, k.OBCIBoardDefault, k.OBCIBoardDaisy, k.OBCIBoardGanglion], - baudRate: 115200, - hardSet: false, - sendCounts: false, - simulate: false, - simulatorBoardFailure: false, - simulatorDaisyModuleAttached: false, - simulatorDaisyModuleCanBeAttached: true, - simulatorFirmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2, k.OBCIFirmwareV3], - simulatorFragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], - simulatorLatencyTime: 16, - simulatorBufferSize: 4096, - simulatorHasAccelerometer: true, - simulatorInternalClockDrift: 0, - simulatorInjectAlpha: true, - simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], - simulatorSampleRate: 250, - simulatorSerialPortFailure: false, - sntpTimeSync: false, - sntpTimeSyncHost: 'pool.ntp.org', - sntpTimeSyncPort: 123, - verbose: false, - debug: false -}; - -/** - * @description The initialization method to call first, before any other method. - * @param options {* | InitializationObject} (optional) - Board optional configurations. - * @constructor - * @author AJ Keller (@pushtheworldllc) - */ -function Cyton (options) { - if (!(this instanceof Cyton)) { - return new Cyton(options); - } - options = options || {}; - var opts = {}; - - /** Configuring Options */ - var o; - for (o in _options) { - var userOption = (o in options) ? o : o.toLowerCase(); - var userValue = options[userOption]; - delete options[userOption]; - - 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'); - - // Set to global options object - /** - * @type {InitializationObject} - */ - this.options = opts; - - /** - * @type {RawDataToSample} - * @private - */ - this._rawDataPacketToSample = k.rawDataToSampleObjectDefault(k.numberOfChannelsForBoardType(this.options.boardType)); - this._rawDataPacketToSample.scale = !this.options.sendCounts; - this._rawDataPacketToSample.protocol = k.OBCIProtocolSerial; - this._rawDataPacketToSample.verbose = this.options.verbose; - - /** Properties (keep alphabetical) */ - // Arrays - this.writeOutArray = []; - // Booleans - this._streaming = false; - // Buffers - this.buffer = null; - // Objects - this.impedanceTest = obciUtils.impedanceTestObjDefault(); - this.info = { - firmware: k.OBCIFirmwareV1, - missedPackets: 0 - }; - - this._lowerChannelsSampleObject = null; - this.serial = null; - this.sync = { - curSyncObj: null, - eventEmitter: null, - objArray: [], - sntpActive: false, - timeOffsetMaster: 0, - timeOffsetAvg: 0, - timeOffsetArray: [] - }; - this.writer = null; - // Numbers - this.badPackets = 0; - this.curParsingMode = k.OBCIParsingReset; - this.impedanceArray = obciUtils.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType)); - this.previousSampleNumber = -1; - this.sampleCount = 0; - this.timeOfPacketArrival = 0; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - // Strings - this.portName = null; - - // NTP - if (this.options.sntpTimeSync) { - // establishing ntp connection - this.sntpStart() - .catch(ignored => { - // try again once after a delay - return new Promise((resolve, reject) => { - setTimeout(resolve, 500); - }).then(() => this.sntpStart()); - }) - .then(() => { - if (this.options.verbose) console.log('SNTP: connected'); - }) - .catch(err => { - if (this.options.verbose) console.log(`Error [sntpStart] ${err}`); - this.emit('error', err); - }); - } -} - -util.inherits(Cyton, EventEmitter); - -/** - * @description The essential precursor method to be called initially to establish a - * serial connection to the OpenBCI board. - * @param portName - a string that contains the port name of the OpenBCIBoard. - * @returns {Promise} if the board was able to connect. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.connect = function (portName) { - return new Promise((resolve, reject) => { - if (this.isConnected()) return reject(Error('already connected!')); - this.overrideInfoForBoardType(this.options.boardType); - this.buffer = null; - /* istanbul ignore else */ - if (this.options.simulate || portName === k.OBCISimulatorPortName) { - this.options.simulate = true; - // If we are simulating, set portName to fake name - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('using faux board ' + portName); - this.serial = new OpenBCISimulator(this.portName, { - accel: this.options.simulatorHasAccelerometer, - alpha: this.options.simulatorInjectAlpha, - boardFailure: this.options.simulatorBoardFailure, - daisy: this.options.simulatorDaisyModuleAttached, - daisyCanBeAttached: this.options.simulatorDaisyModuleCanBeAttached, - drift: this.options.simulatorInternalClockDrift, - firmwareVersion: this.options.simulatorFirmwareVersion, - fragmentation: this.options.simulatorFragmentation, - latencyTime: this.options.simulatorLatencyTime, - bufferSize: this.options.simulatorBufferSize, - lineNoise: this.options.simulatorInjectLineNoise, - sampleRate: this.options.simulatorSampleRate, - serialPortFailure: this.options.simulatorSerialPortFailure, - verbose: this.options.verbose - }); - } else { - this.portName = portName; - if (this.options.verbose) console.log('using real board ' + portName); - this.serial = new SerialPort(portName, { - baudRate: this.options.baudRate - }, (err) => { - if (err) reject(err); - }); - } - - if (this.options.verbose) console.log('Serial port connected'); - - this.serial.on('data', data => { - this._processBytes(data); - }); - this.serial.once('open', () => { - if (this.options.verbose) console.log('Serial port open'); - new Promise(resolve => { - // TODO: document why this 300 ms delay is needed - setTimeout(resolve, this.options.simulate ? 50 : 300); - }).then(() => { - if (this.options.verbose) console.log('Sending stop command, in case the device was left streaming...'); - return this.write(k.OBCIStreamStop); - }).then(() => { - return new Promise(resolve => this.serial.flush(resolve)); - }).then(() => { - // TODO: document why this 250 ms delay is needed - return new Promise(resolve => setTimeout(resolve, 250)); - }).then(() => { - if (this.options.verbose) console.log('Sending soft reset'); - // TODO: this promise chain resolves early because - // A. some legacy code (in tests) sets the ready handler after this resolves - // and - // B. other legacy code (in tests) needs the simulator to reply with segmented packets, never fragmented - // which is C. not implemented yet except in a manner such that replies occur in the write handler, - // resulting in the EOT arriving before this resolves - // Fix one or more of the above 3 situations, then move resolve() to the next block. - resolve(); - return this.softReset(); - }).then(() => { - if (this.options.verbose) console.log("Waiting for '$$$'"); - }); - }); - this.serial.once('close', () => { - if (this.options.verbose) console.log('Serial Port Closed'); - // 'close' is emitted in _disconnected() - this._disconnected('port closed'); - }); - this.serial.once('error', (err) => { - if (this.options.verbose) console.log('Serial Port Error'); - this.emit('error', err); - this._disconnected(err); - }); - }); -}; - -/** - * @description Called once when for any reason the serial port is no longer open. - * @private - */ -Cyton.prototype._disconnected = function (err) { - this._streaming = false; - - clearTimeout(this.writer); - this.writer = null; - - this.serial.removeAllListeners('close'); - this.serial.removeAllListeners('error'); - this.serial.removeAllListeners('data'); - this.serial = null; - - this.emit('close'); - - while (this.writeOutArray.length > 0) { - var command = this.writeOutArray.pop(); - if (command.reject) command.reject(err); - } -}; - -/** - * @description Closes the serial port. Waits for stop streaming command to - * be sent if currently streaming. - * @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.disconnect = function () { - return Promise.resolve() - .then(() => { - if (this.isStreaming()) { - if (this.options.verbose) console.log('stop streaming'); - return this.streamStop(); - } else { - return Promise.resolve(); - } - }) - .then(() => { - if (!this.isConnected()) { - return Promise.reject(Error('no board connected')); - } else { - return new Promise((resolve) => { - // serial emitting 'close' will call _disconnected - this.serial.close(() => { - resolve(); - }); - }); - } - }); -}; - -/** - * @description Checks if the driver is connected to a board. - * @returns {boolean} - True if connected. - */ -Cyton.prototype.isConnected = function () { - if (!this.serial) return false; - return this.serial.isOpen(); -}; - -/** - * @description Checks if the board is currently sending samples. - * @returns {boolean} - True if streaming. - */ -Cyton.prototype.isSimulating = function () { - return this.options.simulate; -}; - -/** - * @description Checks if the board is currently sending samples. - * @returns {boolean} - True if streaming. - */ -Cyton.prototype.isStreaming = function () { - return this._streaming; -}; - -/** - * @description Sends a start streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board will start streaming. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.streamStart = function () { - return new Promise((resolve, reject) => { - if (this.isStreaming()) return reject(Error('Error [.streamStart()]: Already streaming')); - this._streaming = true; - this.write(k.OBCIStreamStart).then(resolve, reject); - }); -}; - -/** - * @description Sends a stop streaming command to the board. - * @returns {Promise} indicating if the signal was able to be sent. - * Note: You must have successfully connected to an OpenBCI board using the connect - * method. Just because the signal was able to be sent to the board, does not - * mean the board stopped streaming. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.streamStop = function () { - return new Promise((resolve, reject) => { - if (!this.isStreaming()) return reject(Error('Error [.streamStop()]: No stream to stop')); - this._streaming = false; - this.write(k.OBCIStreamStop).then(resolve, reject); - }); -}; - -/** - * @description To start simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to enter simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.simulatorEnable = function () { - return new Promise((resolve, reject) => { - if (this.options.simulate) return reject(Error('Already simulating')); // Are we already in simulate mode? - if (this.isConnected()) { - this.disconnect() // disconnect first - .then(() => { - this.options.simulate = true; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = true; - resolve(); - } - }); -}; - -/** - * @description To stop simulating an open bci board - * Note: Must be called after the constructor - * @returns {Promise} - Fulfilled if able to stop simulate mode, reject if not. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.simulatorDisable = function () { - return new Promise((resolve, reject) => { - if (!this.options.simulate) return reject(Error('Not simulating')); // Are we already not in simulate mode? - if (this.isConnected()) { - this.disconnect() - .then(() => { - this.options.simulate = false; - resolve(); - }) - .catch(err => reject(err)); - } else { - this.options.simulate = false; - resolve(); - } - }); -}; - -/** - * @description To be able to easily write to the board but ensure that we never send commands - * with less than a 10ms spacing between sends in early version boards. This uses - * an array and shifts off the entries until there are none left. - * @param dataToWrite - Either a single character or an Array of characters - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.write = function (dataToWrite) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) { - reject(Error('not connected')); - } else { - if (Array.isArray(dataToWrite)) { // Got an input array - var len = dataToWrite.length; - for (var i = 0; i < len; i++) { - this.writeOutArray.push({ cmd: dataToWrite[i], reject: reject }); - } - this.writeOutArray[this.writeOutArray.length - 1].resolve = resolve; - } else { - this.writeOutArray.push({ cmd: dataToWrite, reject: reject, resolve: resolve }); - } - - if (!this.writer) { // there is no writer started - var writerFunction = () => { - if (this.writeOutArray.length === 0) { - this.writer = null; - return; - } - - var command = this.writeOutArray.shift(); - var promise = this._writeAndDrain(command.cmd); - - promise.then(() => { - this.writer = setTimeout(writerFunction, this.writeOutDelay); - }, () => { - // write failed but more commands may be pending that need a result - writerFunction(); - }); - - if (command.reject) { - promise.catch(err => { - if (this.options.verbose) console.log('write failure: ' + err); - command.reject(err); - }); - } - if (command.resolve) promise.then(command.resolve); - }; - this.writer = setTimeout(writerFunction, this.writeOutDelay); - } - } - }); -}; - -/** - * @description Should be used to send data to the board - * @param data {Buffer | Buffer2} - The data to write out - * @returns {Promise} if signal was able to be sent - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._writeAndDrain = function (data) { - if (this.options.debug) obciDebug.debugBytes('>>>', data); - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Serial port not open')); - this.serial.write(data, (error) => { - if (error) { - console.log('Error [writeAndDrain]: ' + error); - reject(error); - } else { - this.serial.drain(function () { - resolve(); - }); - } - }); - }); -}; - -/** - * @description Automatically find an OpenBCI board. - * Note: This method is used for convenience and should be used when trying to - * connect to a board. If you find a case (i.e. a platform (linux, - * windows...) that this does not work, please open an issue and - * we will add support! - * @returns {Promise} - Fulfilled with portName, rejected when can't find the board. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.autoFindOpenBCIBoard = function () { - const serialPatterns = [ - { // mac - comName: /usbserial-D/ - }, - { // linux - comName: /^\/dev\/ttyUSB/, - manufacturer: /^FTDI$/, - serialNumber: /^FTDI_FT231X_USB_UART/, - vendorId: /^0x0403$/, - productId: /^0x6015$/ - } - ]; - return new Promise((resolve, reject) => { - /* istanbul ignore else */ - if (this.options.simulate) { - this.portName = k.OBCISimulatorPortName; - if (this.options.verbose) console.log('auto found sim board'); - resolve(k.OBCISimulatorPortName); - } else { - SerialPort.list((err, ports) => { - if (err) { - if (this.options.verbose) console.log('serial port err'); - reject(err); - } - // This is one big if statement - if (ports.some(port => { - return serialPatterns.some(patterns => { - for (var attribute in patterns) { - if (!String(port[attribute]).match(patterns[attribute])) { - return false; - } - } - this.portName = port.comName; - return true; - }); - })) { - if (this.options.verbose) console.log('auto found board'); - resolve(this.portName); - } else { - if (this.options.verbose) console.log('could not find board'); - reject(Error('Could not auto find board')); - } - }); - } - }); -}; - -/** - * @description Convenience method to determine if you can use firmware v2.x.x - * capabilities. - * @returns {boolean} - True if using firmware version 2 or greater. Should - * be called after a `.softReset()` because we can parse the output of that - * to determine if we are using firmware version 2. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.usingVersionTwoFirmware = function () { - if (this.options.simulate) { - return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV2; - } else { - return this.info.firmware === k.OBCIFirmwareV2; - } -}; - -/** - * @description Convenience method to determine if you can use firmware v2.x.x - * or greater capabilities. - * @returns {boolean} - True if using firmware version 2 or greater. Should - * be called after a `.softReset()` because we can parse the output of that - * to determine if we are using firmware version 2. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.usingAtLeastVersionTwoFirmware = function () { - return this.usingVersionTwoFirmware() || this.usingVersionThreeFirmware(); -}; - -/** - * @description Convenience method to determine if you can use firmware v2.x.x - * capabilities. - * @returns {boolean} - True if using firmware version 2 or greater. Should - * be called after a `.softReset()` because we can parse the output of that - * to determine if we are using firmware version 2. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.usingVersionThreeFirmware = function () { - if (this.options.simulate) { - return this.options.simulatorFirmwareVersion === k.OBCIFirmwareV3; - } else { - return this.info.firmware === k.OBCIFirmwareV3; - } -}; - -/** - * @description Used to set the system radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioChannelSet = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error('Don\'t query for the radio while streaming')); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - if (channelNumber === undefined || channelNumber === null) return reject(Error('Must input a new channel number to switch too!')); - if (!k.isNumber(channelNumber)) return reject(Error('Must input type Number')); - if (channelNumber > k.OBCIRadioChannelMax) return reject(Error(`New channel number must be less than ${k.OBCIRadioChannelMax}`)); - if (channelNumber < k.OBCIRadioChannelMin) return reject(Error(`New channel number must be greater than ${k.OBCIRadioChannelMin}`)); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (obciUtils.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(Error(`Error [radioChannelSet]: ${data}`)); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, channelNumber])).catch(reject); - }); -}; - -/** - * @description Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if - * your dongle and board are not on the right channel and bring down your radio system if you take your - * dongle and board are not on the same channel. Use with caution! The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `channelNumber` {Number} - The channel number you want to set to, 1-25. - * @since 1.0.0 - * @returns {Promise} - Resolves with the new channel number, rejects with err. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioChannelSetHostOverride = function (channelNumber) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't query for the radio while streaming")); - if (channelNumber === undefined || channelNumber === null) return reject(Error('Must input a new channel number to switch too!')); - if (!k.isNumber(channelNumber)) return reject(Error('Must input type Number')); - if (channelNumber > k.OBCIRadioChannelMax) return reject(Error(`New channel number must be less than ${k.OBCIRadioChannelMax}`)); - if (channelNumber < k.OBCIRadioChannelMin) return reject(Error(`New channel number must be greater than ${k.OBCIRadioChannelMin}`)); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(`${data.toString()}`); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (obciUtils.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); - } else { - reject(Error(`Error [radioChannelSet]: ${data}`)); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, channelNumber])).catch(reject); - }); -}; - -/** - * @description Used to query the OpenBCI system for it's radio channel number. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * an Object. See `returns` below. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in - * the condition that there system is experiencing board communications failure. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioChannelGet = function () { - // The function to run on timeout - var badCommsTimeout; - - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't query for the radio while streaming")); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is plugged in and using firmware v2')); - }, 500); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - if (obciUtils.isSuccessInBuffer(data)) { - resolve({ - channelNumber: data[data.length - 4], - data: data - }); - } else { - reject(Error(`Error [radioChannelGet]: ${data.toString()}`)); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])).catch(reject); - }); -}; - -/** - * @description Used to query the OpenBCI system for it's device's poll time. The function will reject if not - * connected to the serial port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve - * the poll time when fulfilled. It's important to note that if the board is not on, this function will always - * be rejected with a failure message. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves with the poll time, rejects with an error message. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioPollTimeGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't query for the poll time while streaming")); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is plugged in and using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (obciUtils.isSuccessInBuffer(data)) { - var pollTime = data[data.length - 4]; - resolve(pollTime); - } else { - reject(Error(`Error [radioPollTimeGet]: ${data}`)); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])).catch(reject); - }); -}; - -/** - * @description Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the - * Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this - * sets the interval at which the Device polls the Host for new information. Further the function should reject - * if currently streaming. Lastly and more important, if the board is not running the new firmware then this - * functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this - * function should resolve. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @param `pollTime` {Number} - The poll time you want to set for the system. 0-255 - * @since 1.0.0 - * @returns {Promise} - Resolves with new poll time, rejects with error message. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioPollTimeSet = function (pollTime) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't change the poll time while streaming")); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - if (pollTime === undefined || pollTime === null) return reject(Error('Must input a new poll time to switch too!')); - if (!k.isNumber(pollTime)) return reject(Error('Must input type Number')); - if (pollTime > k.OBCIRadioPollTimeMax) return reject(Error(`New polltime must be less than ${k.OBCIRadioPollTimeMax}`)); - if (pollTime < k.OBCIRadioPollTimeMin) return reject(Error(`New polltime must be greater than ${k.OBCIRadioPollTimeMin}`)); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is plugged in and using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (obciUtils.isSuccessInBuffer(data)) { - resolve(data[data.length - 4]); // Ditch the eot $$$ - } else { - reject(Error(`Error [radioPollTimeSet]: ${data}`)); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, pollTime])).catch(reject); - }); -}; - -/** - * @description Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the - * Host and the Board is the Device. Only the Device can initiate a communication between the two entities. - * There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then - * all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial - * data is sent from the Host to the Serial driver. The rate can either be set to default or fast. - * Further the function should reject if currently streaming. Lastly and more important, if the board is not - * running the new firmware then this functionality does not exist and thus this method will reject. - * If the board is using firmware 2+ then this function should resolve the new baud rate after closing the - * current serial port and reopening one. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @param speed {String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400) - * @returns {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioBaudRateSet = function (speed) { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't change the baud rate while streaming")); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - if (!k.isString(speed)) return reject(Error('Must input type String')); - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is plugged in and using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - if (this.options.verbose) console.log(data.toString()); - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - var eotBuf = new Buffer('$$$'); - var newBaudRateBuf; - for (var i = data.length; i > 3; i--) { - if (bufferEqual(data.slice(i - 3, i), eotBuf)) { - newBaudRateBuf = data.slice(i - 9, i - 3); - break; - } - } - var newBaudRateNum = Number(newBaudRateBuf.toString()); - if (newBaudRateNum !== k.OBCIRadioBaudRateDefault && newBaudRateNum !== k.OBCIRadioBaudRateFast) { - return reject(Error('Error parse mismatch, restart your system!')); - } - if (!this.isConnected()) { - reject(Error('Lost connection to device during baud set')); - } else if (obciUtils.isSuccessInBuffer(data)) { - // Change the sample rate here - if (this.options.simulate === false) { - this.serial.update({baudRate: newBaudRateNum}, err => { - if (err) return reject(err); - else resolve(newBaudRateNum); - }); - } else { - resolve(newBaudRateNum); - } - } else { - reject(Error(`Error [radioPollTimeGet]: ${data}`)); // The channel number is in the first byte - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - if (speed === k.OBCIRadioBaudRateFastStr) { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])).catch(reject); - } else { - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])).catch(reject); - } - }); -}; - -/** - * @description Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are - * in fact ready to start trying to connect and such. The function will reject if not connected to the serial - * port of the dongle. Further the function should reject if currently streaming. - * Lastly and more important, if the board is not running the new firmware then this functionality does not - * exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the - * same channel and powered, then this will resolve true. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.radioSystemStatusGet = function () { - var badCommsTimeout; - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to Dongle. Pro tip: Call .connect()')); - if (this.isStreaming()) return reject(Error("Don't check the radio status while streaming")); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your dongle is plugged in and using firmware v2')); - }, 1000); - - // Subscribe to the EOT event - this.once('eot', data => { - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - - if (this.options.verbose) console.log(data.toString()); - - if (obciUtils.isSuccessInBuffer(data)) { - resolve(true); - } else { - resolve(false); - } - }); - - this.curParsingMode = k.OBCIParsingEOT; - - // Send the radio channel query command - this._writeAndDrain(new Buffer([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])).catch(reject); - }); -}; - -/** - * @description List available ports so the user can choose a device when not - * automatically found. - * Note: This method is used for convenience essentially just wrapping up - * serial port. - * @author Andy Heusser (@andyh616) - * @returns {Promise} - On fulfill will contain an array of Serial ports to use. - */ -Cyton.prototype.listPorts = function () { - return new Promise((resolve, reject) => { - SerialPort.list((err, ports) => { - if (err) reject(err); - else { - ports.push({ - comName: k.OBCISimulatorPortName, - manufacturer: '', - serialNumber: '', - pnpId: '', - locationId: '', - vendorId: '', - productId: '' - }); - resolve(ports); - } - }); - }); -}; - -/** - * Get the board type. - * @return boardType: string - */ -Cyton.prototype.getBoardType = function () { - return k.boardTypeForNumberOfChannels(this._rawDataPacketToSample.channelSettings.length); -}; - -/** - * Get the core info object. - * @return {{firmware: string, missedPackets: number}} - */ -Cyton.prototype.getInfo = function () { - return this.info; -}; - -/** - * Set the info property for board type. - * @param boardType {String} - * `default` or `daisy`. Defaults to `default`. - */ -Cyton.prototype.overrideInfoForBoardType = function (boardType) { - switch (boardType) { - case k.OBCIBoardDaisy: - this._rawDataPacketToSample.channelSettings = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy); - this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsDaisy); - break; - case k.OBCIBoardCyton: - case k.OBCIBoardDefault: - default: - this._rawDataPacketToSample.channelSettings = k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton); - this.impedanceArray = obciUtils.impedanceArray(k.OBCINumberOfChannelsCyton); - break; - } -}; - -/** - * Used to sync the module and board to `boardType` - * @param boardType {String} - * Either `default` or `daisy` - * @return {Promise} - */ -Cyton.prototype.hardSetBoardType = function (boardType) { - if (this.isStreaming()) return Promise.reject(Error('Must not be streaming!')); - return new Promise((resolve, reject) => { - const eotFunc = (data) => { - switch (data.slice(0, data.length - k.OBCIParseEOT.length).toString()) { - case k.OBCIChannelMaxNumber8SuccessDaisyRemoved: - this.overrideInfoForBoardType(k.OBCIBoardCyton); - resolve('daisy removed'); - break; - case k.OBCIChannelMaxNumber16DaisyAlreadyAttached: - this.overrideInfoForBoardType(k.OBCIBoardDaisy); - resolve('daisy already attached'); - break; - case k.OBCIChannelMaxNumber16DaisyAttached: - this.overrideInfoForBoardType(k.OBCIBoardDaisy); - resolve('daisy attached'); - break; - case k.OBCIChannelMaxNumber16NoDaisyAttached: - this.overrideInfoForBoardType(k.OBCIBoardCyton); - reject(Error('unable to attach daisy')); - break; - case k.OBCIChannelMaxNumber8NoDaisyToRemove: - default: - this.overrideInfoForBoardType(k.OBCIBoardCyton); - resolve('no daisy to remove'); - break; - } - }; - if (boardType === k.OBCIBoardCyton || boardType === k.OBCIBoardDefault) { - this.curParsingMode = k.OBCIParsingEOT; - if (this.options.verbose) console.log('Attempting to hardset board type'); - this.once(k.OBCIEmitterEot, eotFunc); - this.write(k.OBCIChannelMaxNumber8) - .catch((err) => { - this.removeListener(k.OBCIEmitterEot, eotFunc); - reject(err); - }); - } else if (boardType === k.OBCIBoardDaisy) { - this.curParsingMode = k.OBCIParsingEOT; - this.once(k.OBCIEmitterEot, eotFunc); - this.write(k.OBCIChannelMaxNumber16) - .catch((err) => { - this.removeListener(k.OBCIEmitterEot, eotFunc); - reject(err); - }); - } else { - reject(Error('invalid board type')); - } - }); -}; - -/** - * @description Sends a soft reset command to the board - * @returns {Promise} - * Note: The softReset command MUST be sent to the board before you can start - * streaming. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.softReset = function () { - this.curParsingMode = k.OBCIParsingReset; - return this.write(k.OBCIMiscSoftReset); -}; - -/** - * @description To get the specified channelSettings register data from printRegisterSettings call - * @param channelNumber - a number - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ -// TODO: REDO THIS FUNCTION -Cyton.prototype.getSettingsForChannel = function (channelNumber) { - return k.channelSettingsKeyForChannel(channelNumber).then((newSearchingBuffer) => { - // this.searchingBuf = newSearchingBuffer - return this.printRegisterSettings(); - }); -}; - -/** - * @description Syncs the internal channel settings object with a cyton, this will take about - * over a second because there are delays between the register reads in the firmware. - * @returns {Promise.|*} Resolved once synced, rejects on error or 2 second timeout - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.syncRegisterSettings = function () { - // Set a timeout. Since poll times can be max of 255 seconds, we should set that as our timeout. This is - // important if the module was connected, not streaming and using the old firmware - let badCommsTimeout; - return new Promise((resolve, reject) => { - badCommsTimeout = setTimeout(() => { - reject(Error('Please make sure your radio system is up')); - }, 2500); - // Subscribe to the EOT event - this.once(k.OBCIEmitterEot, data => { - if (this.options.verbose) console.log(data.toString()); - this._rawDataPacketToSample.data = data; - try { - obciUtils.syncChannelSettingsWithRawData(this._rawDataPacketToSample); - resolve(this._rawDataPacketToSample.channelSettings); - } catch (e) { - reject(e); - } - // Remove the timeout! - clearTimeout(badCommsTimeout); - badCommsTimeout = null; - }); - this.curParsingMode = k.OBCIParsingEOT; - - this.write(k.OBCIMiscQueryRegisterSettings) - .catch((err) => { - clearTimeout(badCommsTimeout); - reject(err); - }); - }); -}; - -/** - * @description Send a command to the board to turn a specified channel off - * @param channelNumber - * @returns {Promise.} - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.channelOff = function (channelNumber) { - return k.commandChannelOff(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); -}; - -/** - * @description Send a command to the board to turn a specified channel on - * @param channelNumber - * @returns {Promise.|*} - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.channelOn = function (channelNumber) { - return k.commandChannelOn(channelNumber).then((charCommand) => { - // console.log('sent command to turn channel ' + channelNumber + ' by sending command ' + charCommand) - return this.write(charCommand); - }); -}; - -/** - * @description To send a channel setting command to the board - * @param channelNumber - Number (1-16) - * @param powerDown - Bool (true -> OFF, false -> ON (default)) - * turns the channel on or off - * @param gain - Number (1,2,4,6,8,12,24(default)) - * sets the gain for the channel - * @param inputType - String (normal,shorted,biasMethod,mvdd,temp,testsig,biasDrp,biasDrn) - * selects the ADC channel input source - * @param bias - Bool (true -> Include in bias (default), false -> remove from bias) - * selects to include the channel input in bias generation - * @param srb2 - Bool (true -> Connect this input to SRB2 (default), - * false -> Disconnect this input from SRB2) - * Select to connect (true) this channel's P input to the SRB2 pin. This closes - * a switch between P input and SRB2 for the given channel, and allows the - * P input to also remain connected to the ADC. - * @param srb1 - Bool (true -> connect all N inputs to SRB1, - * false -> Disconnect all N inputs from SRB1 (default)) - * Select to connect (true) all channels' N inputs to SRB1. This effects all pins, - * and disconnects all N inputs from the ADC. - * @returns {Promise} resolves if sent, rejects on bad input or no board - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.channelSet = function (channelNumber, powerDown, gain, inputType, bias, srb2, srb1) { - var arrayOfCommands = []; - return new Promise((resolve, reject) => { - k.getChannelSetter(channelNumber, powerDown, gain, inputType, bias, srb2, srb1) - .then((val) => { - arrayOfCommands = val.commandArray; - this._rawDataPacketToSample.channelSettings[channelNumber - 1] = val.newChannelSettingsObject; - if (this.usingAtLeastVersionTwoFirmware()) { - const buf = Buffer.from(arrayOfCommands.join('')); - return this._writeAndDrain(buf); - } else { - return this.write(arrayOfCommands); - } - }).then(resolve, reject); - }); -}; - -/** - * @description Apply the internal test signal to all channels - * @param signal - A string indicating which test signal to apply - * - `dc` - * - Connect to DC signal - * - `ground` - * - Connect to internal GND (VDD - VSS) - * - `pulse1xFast` - * - Connect to test signal 1x Amplitude, fast pulse - * - `pulse1xSlow` - * - Connect to test signal 1x Amplitude, slow pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, fast pulse - * - `pulse2xFast` - * - Connect to test signal 2x Amplitude, slow pulse - * - `none` - * - Reset to default - * @returns {Promise} - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.testSignal = function (signal) { - return new Promise((resolve, reject) => { - k.getTestSignalCommand(signal) - .then(command => { - return this.write(command); - }) - .then(() => resolve()) - .catch(err => reject(err)); - }); -}; - -/** - * @description - Sends command to turn on impedances for all channels and continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestContinuousStart = function () { - return new Promise((resolve, reject) => { - if (this.impedanceTest.active) return reject(Error('Error: test already active')); - if (this.impedanceTest.continuousMode) return reject(Error('Error: Already in continuous impedance test mode!')); - - this.impedanceTest.active = true; - this.impedanceTest.continuousMode = true; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, true)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); -}; - -/** - * @description - Sends command to turn off impedances for all channels and stop continuously calculate their impedances - * @returns {Promise} - Fulfills when all the commands are sent to the internal write buffer - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestContinuousStop = function () { - return new Promise((resolve, reject) => { - if (!this.impedanceTest.active) return reject(Error('Error: no test active')); - if (!this.impedanceTest.continuousMode) return reject(Error('Error: Not in continuous impedance test mode!')); - - this.impedanceTest.active = false; - this.impedanceTest.continuousMode = false; - - var chain = Promise.resolve(); - for (var i = 0; i < this.numberOfChannels(); i++) { - chain = chain - .then(() => k.getImpedanceSetter(i + 1, false, false)) - .then((commandsArray) => this.write(commandsArray)); - } - chain.then(resolve, reject); - }); -}; - -/** - * @description To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a - * little while to actually run (<8 seconds)! - * @returns {Promise} - Resovles when complete testing all the channels. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestAllChannels = function () { - var upperLimit = k.OBCINumberOfChannelsCyton; - - /* istanbul ignore if */ - if (this.options.daisy) { - upperLimit = k.OBCINumberOfChannelsDaisy; - } - - if (!this.isStreaming()) return Promise.reject(Error('Must be streaming!')); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > upperLimit) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - this.impedanceTestChannel(channelNumber) - .then(() => { - resolve(completeChannelImpedanceTest(channelNumber + 1)); - /* istanbul ignore next */ - }).catch(err => reject(err)); - } - }); - }; - - return completeChannelImpedanceTest(1); -}; - -/** - * @description To test specific input configurations of channels! - * @param arrayOfChannels - The array of configurations where: - * 'p' or 'P' is only test P input - * 'n' or 'N' is only test N input - * 'b' or 'B' is test both inputs (takes 66% longer to run) - * '-' to ignore channel - * EXAMPLE: - * For 8 channel board: ['-','N','n','p','P','-','b','b'] - * (Note: it doesn't matter if capitalized or not) - * @returns {Promise} - Fulfilled with a loaded impedance object. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestChannels = function (arrayOfChannels) { - if (!Array.isArray(arrayOfChannels)) return Promise.reject(Error('Input must be array of channels... See Docs!')); - if (!this.isStreaming()) return Promise.reject(Error('Must be streaming!')); - // Check proper length of array - if (arrayOfChannels.length !== this.numberOfChannels()) return Promise.reject(Error('Array length mismatch, should have ' + this.numberOfChannels() + ' but array has length ' + arrayOfChannels.length)); - - // Recursive function call - var completeChannelImpedanceTest = (channelNumber) => { - return new Promise((resolve, reject) => { - if (channelNumber > arrayOfChannels.length) { // Base case! - this.emit('impedanceArray', this.impedanceArray); - this.impedanceTest.onChannel = 0; - resolve(); - } else { - if (this.options.verbose) console.log('\n\nImpedance Test for channel ' + channelNumber); - - var testCommand = arrayOfChannels[channelNumber - 1]; - - if (testCommand === 'p' || testCommand === 'P') { - this.impedanceTestChannelInputP(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'n' || testCommand === 'N') { - this.impedanceTestChannelInputN(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else if (testCommand === 'b' || testCommand === 'B') { - this.impedanceTestChannel(channelNumber).then(() => { - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - }).catch(err => reject(err)); - } else { // skip ('-') condition - completeChannelImpedanceTest(channelNumber + 1).then(resolve, reject); - } - } - }); - }; - return completeChannelImpedanceTest(1); -}; - -/** - * @description Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestChannel = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, true); // Sends command for N input on channel number. - }) - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/** - * @description Run impedance test on a single channel, applying the test signal only to P input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestChannelInputP = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, true, false) // Sends command for P input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, true, false); // Calculates for P input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, true, false); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/** - * @description Run impedance test on a single channel, applying the test signal to N input. - * @param channelNumber - A Number, specifies which channel you want to test. - * @returns {Promise} - Fulfilled with a single channel impedance object. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.impedanceTestChannelInputN = function (channelNumber) { - this.impedanceArray[channelNumber - 1] = obciUtils.impedanceObject(channelNumber); - return new Promise((resolve, reject) => { - this._impedanceTestSetChannel(channelNumber, false, true) // Sends command for N input on channel number. - .then(channelNumber => { - return this._impedanceTestCalculateChannel(channelNumber, false, true); // Calculates for N input of channel number - }) - .then(channelNumber => { - return this._impedanceTestSetChannel(channelNumber, false, false); // Sends command to stop applying test signal to P and N channel - }) - .then(channelNumber => { - return this._impedanceTestFinalizeChannel(channelNumber, false, true); // Finalize the impedances. - }) - .then((channelNumber) => resolve(this.impedanceArray[channelNumber - 1])) - .catch(err => reject(err)); - }); -}; - -/* istanbul ignore next */ -/** - * @description To apply the impedance test signal to an input for any given channel - * @param channelNumber - Number - The channel you want to test. - * @param pInput - A bool true if you want to apply the test signal to the P input, false to not apply the test signal. - * @param nInput - A bool true if you want to apply the test signal to the N input, false to not apply the test signal. - * @returns {Promise} - With Number value of channel number - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._impedanceTestSetChannel = function (channelNumber, pInput, nInput) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected')); - - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tSending command to apply test signal to P input.'); - } else if (!pInput && nInput) { - console.log('\tSending command to apply test signal to N input.'); - } else if (pInput && nInput) { - console.log('\tSending command to apply test signal to P and N inputs.'); - } else { - console.log('\tSending command to stop applying test signal to both P and N inputs.'); - } - } - - if (!pInput && !nInput) { - this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()` - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort - } else { - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong - } - if (this.options.verbose) console.log('pInput: ' + pInput + ' nInput: ' + nInput); - // Get impedance settings to send the board - k.getImpedanceSetter(channelNumber, pInput, nInput).then((commandsArray) => { - return this.write(commandsArray); - }).then(() => { - /** - * If either pInput or nInput are true then we should start calculating impedance. Setting - * this.impedanceTest.active to true here allows us to route every sample for an impedance - * calculation instead of the normal sample output. - */ - if (pInput || nInput) this.impedanceTest.active = true; - resolve(channelNumber); - }, (err) => { - reject(err); - }); - }); -}; - -/** - * @description Calculates the impedance for a specified channel for a set time - * @param channelNumber - A Number, the channel number you want to test. - * @param pInput - A bool true if you want to calculate impedance on the P input, false to not calculate. - * @param nInput - A bool true if you want to calculate impedance on the N input, false to not calculate. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._impedanceTestCalculateChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tCalculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tCalculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tCalculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject(Error('Invalid channel number')); - if (typeof pInput !== 'boolean') return reject(Error("Invalid Input: 'pInput' must be of type Bool")); - if (typeof nInput !== 'boolean') return reject(Error("Invalid Input: 'nInput' must be of type Bool")); - this.impedanceTest.onChannel = channelNumber; - this.impedanceTest.sampleNumber = 0; // Reset the sample number - this.impedanceTest.isTestingPInput = pInput; - this.impedanceTest.isTestingNInput = nInput; - // console.log(channelNumber + ' In calculate channel pInput: ' + pInput + ' this.impedanceTest.isTestingPInput: ' + this.impedanceTest.isTestingPInput) - // console.log(channelNumber + ' In calculate channel nInput: ' + nInput + ' this.impedanceTest.isTestingNInput: ' + this.impedanceTest.isTestingNInput) - setTimeout(() => { // Calculate for 250ms - this.impedanceTest.onChannel = 0; - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tDone calculating impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tDone calculating impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tDone calculating impedance for P and N input.'); - } else { - console.log('\tNot calculating impedance for either P and N input.'); - } - } - if (pInput) this.impedanceArray[channelNumber - 1].P.raw = this.impedanceTest.impedanceForChannel; - if (nInput) this.impedanceArray[channelNumber - 1].N.raw = this.impedanceTest.impedanceForChannel; - resolve(channelNumber); - }, 400); - }); -}; - -/** - * @description Calculates average and gets textual value of impedance for a specified channel - * @param channelNumber - A Number, the channel number you want to finalize. - * @param pInput - A bool true if you want to finalize impedance on the P input, false to not finalize. - * @param nInput - A bool true if you want to finalize impedance on the N input, false to not finalize. - * @returns {Promise} - Resolves channelNumber as value on fulfill, rejects with error... - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._impedanceTestFinalizeChannel = function (channelNumber, pInput, nInput) { - /* istanbul ignore if */ - if (this.options.verbose) { - if (pInput && !nInput) { - console.log('\tFinalizing impedance for P input.'); - } else if (!pInput && nInput) { - console.log('\tFinalizing impedance for N input.'); - } else if (pInput && nInput) { - console.log('\tFinalizing impedance for P and N input.'); - } else { - console.log('\tNot Finalizing impedance for either P and N input.'); - } - } - return new Promise((resolve, reject) => { - if (channelNumber < 1 || channelNumber > this.numberOfChannels()) return reject(Error('Invalid channel number')); - if (typeof pInput !== 'boolean') return reject(Error("Invalid Input: 'pInput' must be of type Bool")); - if (typeof nInput !== 'boolean') return reject(Error("Invalid Input: 'nInput' must be of type Bool")); - - if (pInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].P); - if (nInput) obciUtils.impedanceSummarize(this.impedanceArray[channelNumber - 1].N); - - setTimeout(() => { - resolve(channelNumber); - }, 50); // Introduce a delay to allow for extra time in case of back to back tests - }); -}; - -/** - * @description Start logging to the SD card. If not streaming then `eot` event will be emitted with request - * response from the board. - * @param recordingDuration {String} - The duration you want to log SD information for. Limited to: - * '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour' - * @returns {Promise} - Resolves when the command has been written. - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.sdStart = function (recordingDuration) { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to the device')); - k.sdSettingForString(recordingDuration) - .then(command => { - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(command).then(resolve, reject); - }) - .catch(err => reject(err)); - }); -}; - -/** - * @description Sends the stop SD logging command to the board. If not streaming then `eot` event will be emitted - * with request response from the board. - * @returns {Promise} - Resolves when written - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.sdStop = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to the device')); - // If we are not streaming, then expect a confirmation message back from the board - if (!this.isStreaming()) { - this.curParsingMode = k.OBCIParsingEOT; - } - // this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone; - this.write(k.OBCISDLogStop).then(resolve, reject); - }); -}; - -/** - * @description Get the the current sample rate is. - * @returns {Number} The sample rate - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.sampleRate = function () { - if (this.options.simulate) { - return this.options.simulatorSampleRate; - } else { - switch (this.getBoardType()) { - case k.OBCIBoardDaisy: - return k.OBCISampleRate125; - case k.OBCIBoardCyton: - case k.OBCIBoardDefault: - default: - return k.OBCISampleRate250; - } - } -}; - -/** - * @description This function is used as a convenience method to determine how many - * channels the current board is using. - * @returns {Number} A number - * Note: This is dependent on if you configured the board correctly on setup options - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.numberOfChannels = function () { - return this._rawDataPacketToSample.channelSettings.length; -}; - -/** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.0.0 - * @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.syncClocks = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to the device')); - if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - this.sync.curSyncObj = obciUtils.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet).then(resolve, reject); - }); -}; - -/** - * @description Send the command to tell the board to start the syncing protocol. Must be connected, - * streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs - * don't overlap. - * **Note**: This functionality requires OpenBCI Firmware Version 2.0 - * @since 1.1.0 - * @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.syncClocksFull = function () { - return new Promise((resolve, reject) => { - if (!this.isConnected()) return reject(Error('Must be connected to the device')); - if (!this.isStreaming()) return reject(Error('Must be streaming to sync clocks')); - if (!this.usingAtLeastVersionTwoFirmware()) return reject(Error('Must be using greater than firmware version 2')); - var timeout = setTimeout(() => { - return reject(Error('syncClocksFull timeout after 500ms with no sync')); - }, 500); // Should not take more than 1s to sync up - this.sync.eventEmitter = syncObj => { - clearTimeout(timeout); - return resolve(syncObj); - }; - this.once('synced', this.sync.eventEmitter); - this.sync.curSyncObj = obciUtils.newSyncObject(); - this.sync.curSyncObj.timeSyncSent = this.time(); - this.curParsingMode = k.OBCIParsingTimeSyncSent; - this._writeAndDrain(k.OBCISyncTimeSet) - .catch(err => { - clearTimeout(timeout); - return reject(err); - }); - }); -}; - -/** - * @description Consider the '_processBytes' method to be the work horse of this - * entire framework. This method gets called any time there is new - * data coming in on the serial port. If you are familiar with the - * 'serialport' package, then every time data is emitted, this function - * gets sent the input data. The data comes in very fragmented, sometimes - * we get half of a packet, and sometimes we get 3 and 3/4 packets, so - * we will need to store what we don't read for next time. - * @param data - a buffer of unknown size - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processBytes = function (data) { - if (this.options.debug) obciDebug.debugBytes(this.curParsingMode + '<<', data); - - // Concat old buffer - var oldDataBuffer = null; - if (this.buffer) { - oldDataBuffer = this.buffer; - data = Buffer.concat([this.buffer, data], data.length + this.buffer.length); - } - - switch (this.curParsingMode) { - case k.OBCIParsingEOT: - if (obciUtils.doesBufferHaveEOT(data)) { - this.curParsingMode = k.OBCIParsingNormal; - this.emit(k.OBCIEmitterEot, data); - this.buffer = obciUtils.stripToEOTBuffer(data); - } else { - this.buffer = data; - } - break; - case k.OBCIParsingReset: - // Does the buffer have an EOT in it? - if (obciUtils.doesBufferHaveEOT(data)) { - this._processParseBufferForReset(data); - if (this.options.hardSet) { - if (!_.eq(this.getBoardType(), this.options.boardType)) { - this.emit(k.OBCIEmitterHardSet); - this.hardSetBoardType(this.options.boardType) - .then(() => { - this.emit(k.OBCIEmitterReady); - }) - .catch((err) => { - this.emit(k.OBCIEmitterError, err); - }); - } else { - this.curParsingMode = k.OBCIParsingNormal; - this.emit(k.OBCIEmitterReady); - this.buffer = obciUtils.stripToEOTBuffer(data); - } - } else { - 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; - this.emit(k.OBCIEmitterReady); - this.buffer = obciUtils.stripToEOTBuffer(data); - } - } else { - this.buffer = data; - } - break; - case k.OBCIParsingTimeSyncSent: - // If there is only one match - if (obciUtils.isTimeSyncSetConfirmationInBuffer(data)) { - if (this.options.verbose) console.log(`Found Time Sync Sent`); - this.sync.curSyncObj.timeSyncSentConfirmation = this.time(); - this.curParsingMode = k.OBCIParsingNormal; - } - this.buffer = this._processDataBuffer(data); - break; - case k.OBCIParsingNormal: - default: - this.buffer = this._processDataBuffer(data); - break; - } - - if (this.buffer && oldDataBuffer) { - if (bufferEqual(this.buffer, oldDataBuffer)) { - this.buffer = null; - } - } -}; - -/** - * @description Used to extract samples out of a buffer of unknown length - * @param dataBuffer {Buffer} - A buffer to parse for samples - * @returns {Buffer} - Any data that was not pulled out of the buffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processDataBuffer = function (dataBuffer) { - if (_.isNull(dataBuffer) || _.isUndefined(dataBuffer)) return null; - const output = obciUtils.extractRawDataPackets(dataBuffer); - - dataBuffer = output.buffer; - - this.timeOfPacketArrival = this.time(); - - _.forEach(output.rawDataPackets, (rawDataPacket) => { - // Emit that buffer - this.emit('rawDataPacket', rawDataPacket); - // Submit the packet for processing - this._processQualifiedPacket(rawDataPacket); - this._rawDataPacketToSample.rawDataPacket = rawDataPacket; - const sample = obciUtils.transformRawDataPacketToSample(this._rawDataPacketToSample); - this._finalizeNewSample(sample); - }); - - return dataBuffer; -}; - -/** - * @description Alters the global info object by parseing an incoming soft reset key - * @param dataBuffer {Buffer} - The soft reset data buffer - * @returns {Buffer} - If there is data left in the buffer, just it will be returned. - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processParseBufferForReset = function (dataBuffer) { - if (obciUtils.countADSPresent(dataBuffer) === 2) { - this.overrideInfoForBoardType(k.OBCIBoardDaisy); - } else { - this.overrideInfoForBoardType(k.OBCIBoardCyton); - } - - const firmware = obciUtils.getFirmware(dataBuffer); - if (firmware) { - if (firmware.major === 2) { - this.info.firmware = k.OBCIFirmwareV2; - } else { - this.info.firmware = k.OBCIFirmwareV3; - } - this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort; - } else { - this.info.firmware = k.OBCIFirmwareV1; - this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong; - } -}; - -/** - * @description Used to route qualified packets to their proper parsers - * @param rawDataPacketBuffer - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processQualifiedPacket = function (rawDataPacketBuffer) { - if (!rawDataPacketBuffer) return; - if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return; - var missedPacketArray = obciUtils.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]); - if (missedPacketArray) { - this.emit(k.OBCIEmitterDroppedPacket, missedPacketArray); - } - this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]; - var packetType = obciUtils.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]); - switch (packetType) { - case k.OBCIStreamPacketAccelTimeSyncSet: - case k.OBCIStreamPacketRawAuxTimeSyncSet: - this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival); - break; - default: - // Don't do anything if the packet is not defined - break; - } -}; - -/** - * @description A method used to compute impedances. - * @param sampleObject - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processImpedanceTest = function (sampleObject) { - var impedanceArray; - if (this.impedanceTest.continuousMode) { - // console.log('running in continuous mode...') - // obciUtils.debugPrettyPrint(sampleObject) - impedanceArray = obciUtils.impedanceCalculateArray(sampleObject, this.impedanceTest); - if (impedanceArray) { - this.emit('impedanceArray', impedanceArray); - } - } else if (this.impedanceTest.onChannel !== 0) { - // Only calculate impedance for one channel - impedanceArray = obciUtils.impedanceCalculateArray(sampleObject, this.impedanceTest); - if (impedanceArray) { - this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1]; - } - } -}; - -/** - * @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp - * @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket - * @param timeOfPacketArrival {Number} - The time the packet arrived. - * @private - * @returns {Object} - A time sync object. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._processPacketTimeSyncSet = function (rawPacket, timeOfPacketArrival) { - /** - * Helper function for creating a bad sync object - * @param err {object} - Can be any object - * @returns {{boardTime, correctedTransmissionTime, error, timeSyncSent, timeSyncSentConfirmation, timeSyncSetPacket, timeRoundTrip, timeTransmission, timeOffset, timeOffsetMaster, valid}|*} - */ - var getBadObject = (err) => { - var badObject = obciUtils.newSyncObject(); - badObject.timeOffsetMaster = this.sync.timeOffsetMaster; - // Create an error - badObject.error = err; - return badObject; - }; - - var syncObj = {}; - - if (this.sync.curSyncObj === null) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncIsNull); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncIsNull); - // Missed comma - } else if (this.curParsingMode === k.OBCIParsingTimeSyncSent) { - if (this.options.verbose) console.log(k.OBCIErrorTimeSyncNoComma); - // Set the output to bad object - syncObj = getBadObject(k.OBCIErrorTimeSyncNoComma); - } else { - try { - this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival; - if (this.options.verbose) console.log('Got time set packet from the board'); - let boardTime = obciUtils.getFromTimePacketTime(rawPacket); - this.sync.curSyncObj.boardTime = boardTime; - // if (this.options.verbose) { - // console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`) - // console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`) - // console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`) - // } - - // Calculate the time between sending the `<` to getting the set packet, call this the round trip length - this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent; - if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`); - - // If the sync sent conf and set packet arrive in different serial flushes - // ------------------------------------------ - // | | timeTransmission | < GOOD :) - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - // - // Assume it's good... - this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent); - - // If the conf and the set packet arrive in the same serial flush we have big problem! - // ------------------------------------------ - // | | | < BAD :( - // ------------------------------------------ - // ^ ^ ^ - // s s s - // e e e - // n n t packet - // t t confirmation - if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) { - // Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point - this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf); - if (this.options.verbose) console.log(`Had to correct transmission time`); - this.sync.curSyncObj.correctedTransmissionTime = true; - } - - // Calculate the offset #finally - this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime; - if (this.options.verbose) { - console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`); - console.log(`Board time: ${boardTime}`); - } - - // Add to array - if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) { - // Shift the oldest one out of the array - this.sync.timeOffsetArray.shift(); - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } else { - // Push the new value into the array - this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset); - } - - // Calculate the master time offset that we use averaging to compute - if (this.sync.timeOffsetArray.length > 1) { - var sum = this.sync.timeOffsetArray.reduce(function (a, b) { return a + b; }); - this.sync.timeOffsetMaster = Math.floor(sum / this.sync.timeOffsetArray.length); - } else { - this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset; - } - - this.sync.curSyncObj.timeOffsetMaster = this.sync.timeOffsetMaster; - - if (this.options.verbose) { - console.log(`Master offset ${this.sync.timeOffsetMaster} ms`); - } - - // Set the valid object to true - this.sync.curSyncObj.valid = true; - - // Make a byte by byte copy of event - syncObj = JSON.parse(JSON.stringify(this.sync.curSyncObj)); - - // Save obj to the global array - this.sync.objArray.push(syncObj); - } catch (err) { - // Log if verbose. - if (this.options.verbose) console.log(err.message); - syncObj = getBadObject(err); - } - } - // Fix the curParsingMode back to normal - this.curParsingMode = k.OBCIParsingNormal; - // Emit synced object - this.emit(k.OBCIEmitterSynced, syncObj); - // Set global to null - this.sync.curSyncObj = null; - // Return the object - return syncObj; -}; - -/** - * @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are - * being tested. - * @param sampleObject {Object} - A sample object that follows the normal standards. - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._finalizeNewSample = function (sampleObject) { - sampleObject._count = this.sampleCount++; - if (!sampleObject.valid) { - this.badPackets++; - this.emit(k.OBCIEmitterDroppedPacket, [this.previousSampleNumber + 1]); - } else if (this.impedanceTest.active) { - this._processImpedanceTest(sampleObject); - } else { - // With the daisy board attached, lower channels (1-8) come in packets with odd sample numbers and upper - // channels (9-16) come in packets with even sample numbers - if (_.eq(this.getBoardType(), k.OBCIBoardDaisy)) { - // Send the sample for downstream sample compaction - this._finalizeNewSampleForDaisy(sampleObject); - } else { - this.emit(k.OBCIEmitterSample, sampleObject); - } - } -}; - -/** - * @description This function is called every sample if the boardType is Daisy. The function stores odd sampleNumber - * sample objects to a private global variable called `._lowerChannelsSampleObject`. The method will emit a - * sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be - * emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the - * missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row. - * @param sampleObject {Object} - The sample object to finalize - * @private - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype._finalizeNewSampleForDaisy = function (sampleObject) { - if (obciUtils.isOdd(sampleObject.sampleNumber)) { - // Check for the skipped packet condition - if (this._lowerChannelsSampleObject) { - // The last packet was odd... missed the even packet - this.info.missedPackets++; - } - this._lowerChannelsSampleObject = sampleObject; - } else { - // Make sure there is an odd packet waiting to get merged with this packet - if (this._lowerChannelsSampleObject) { - // Merge these two samples - var mergedSample = obciUtils.makeDaisySampleObject(this._lowerChannelsSampleObject, sampleObject); - // Set the _lowerChannelsSampleObject object to null - this._lowerChannelsSampleObject = null; - // Emite the new merged sample - this.emit('sample', mergedSample); - } else { - // Missed the odd packet, i.e. two evens in a row - this.info.missedPackets++; - } - } -}; - -/** - * @description Stateful method for querying the current offset only when the last - * one is too old. (defaults to daily) - * @returns {Promise} A promise with the time offset - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.sntpGetOffset = function () { - this.options.sntpTimeSync = true; - - if (!this.options.sntpTimeSync) return Promise.reject(Error('sntp not enabled')); - - return new Promise((resolve, reject) => { - Sntp.offset({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, function (err, offset) { - if (err) reject(err); - else resolve(offset); - }); - }); -}; - -/** - * @description Allows users to utilize all features of sntp if they want to... - */ -Cyton.prototype.sntp = Sntp; - -/** - * @description This gets the time plus offset - * @private - */ -Cyton.prototype._sntpNow = Sntp.now; - -/** - * @description This starts the SNTP server and gets it to remain in sync with the SNTP server - * @returns {Promise} - A promise if the module was able to sync with ntp server. - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.sntpStart = function (options) { - this.options.sntpTimeSync = true; - - // Sntp.start doesn't actually report errors (2016-10-29) - // so functionality is first detected via sntpGetOffset - return this.sntpGetOffset().then(() => { - return new Promise((resolve, reject) => { - Sntp.start({ - host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org - port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP) - clockSyncRefresh: 30 * 60 * 1000, // Resync every 30 minutes - timeout: 500 // Assume packet has been lost after 500 milliseconds - }, () => { - this.sync.sntpActive = true; - this.emit('sntpTimeLock'); - resolve(); - }); - }); - }); -}; - -/** - * @description Stops the sntp from updating. - */ -Cyton.prototype.sntpStop = function () { - Sntp.stop(); - this.options.sntpTimeSync = false; - this.sync.sntpActive = false; -}; - -/** - * @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time - * @returns {Number} - The time - * @author AJ Keller (@pushtheworldllc) - */ -Cyton.prototype.time = function () { - if (this.options.sntpTimeSync) { - return this._sntpNow(); - } else { - return Date.now(); - } -}; - -/** - * @description This prints the total number of packets that were not able to be read - * @author AJ Keller (@pushtheworldllc) - */ -/* istanbul ignore next */ -Cyton.prototype.printPacketsBad = function () { - if (this.badPackets > 1) { - console.log('Dropped a total of ' + this.badPackets + ' packets.'); - } else if (this.badPackets === 1) { - console.log('Dropped a total of 1 packet.'); - } else { - console.log('No packets dropped.'); - } -}; - -/** - * @description This prints the total bytes in - * @author AJ Keller (@pushtheworldllc) - */ -/* istanbul ignore next */ -Cyton.prototype.printBytesIn = function () { - if (this.bytesIn > 1) { - console.log('Read in ' + this.bytesIn + ' bytes.'); - } else if (this.bytesIn === 1) { - console.log('Read one 1 packet in.'); - } else { - console.log('Read no packets.'); - } -}; - -/** - * @description Nice convenience method to print some session details - * @author AJ Keller (@pushtheworldllc) - */ -/* istanbul ignore next */ -Cyton.prototype.debugSession = function () { - this.printBytesIn(); - this.printPacketsBad(); -}; - -module.exports = Cyton; diff --git a/package.json b/package.json index e255437..f62fa6f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "openbci", - "version": "2.2.1", - "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", + "version": "3.0.0", + "description": "The official Node.js SDK for the all OpenBCI Biosensor Boards.", "main": "index.js", "scripts": { - "start": "node index", "test": "semistandard | snazzy && mocha test", "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov", "test-lint": "semistandard | snazzy" @@ -16,9 +15,10 @@ "author": "AJ Keller (www.openbci.com)", "license": "MIT", "dependencies": { - "openbci-cyton": "^1.0.2", - "openbci-utilities": "^0.2.0", - "openbci-wifi": "^0.1.2" + "openbci-cyton": "^1.0.5", + "openbci-ganglion": "^1.0.0", + "openbci-utilities": "^0.2.5", + "openbci-wifi": "^0.2.2" }, "directories": { "test": "test" From 05a40365e43a09cbc785b881453f0fd6a7bf2c10 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Sun, 19 Nov 2017 22:33:45 -0800 Subject: [PATCH 40/42] ADD: new docs --- changelog.md => CHANGELOG.md | 0 CODE_OF_CONDUCT.md | 69 ++ CONTRIBUTING.md | 27 + README.md | 1268 +++------------------------------- ROADMAP.md | 14 + examples/python/handoff.py | 92 +-- images/openbci_large.png | Bin 0 -> 35139 bytes package.json | 6 +- 8 files changed, 247 insertions(+), 1229 deletions(-) rename changelog.md => CHANGELOG.md (100%) create mode 100755 CODE_OF_CONDUCT.md create mode 100755 CONTRIBUTING.md create mode 100755 ROADMAP.md create mode 100644 images/openbci_large.png diff --git a/changelog.md b/CHANGELOG.md similarity index 100% rename from changelog.md rename to CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..b0ab3a3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,69 @@ +# OpenBCI NodeJS Code of Conduct + +## Purpose + +It is our hope that any one is able to contribute to OpenBCI NodeJS regardless of their background. Thus, we hope to provide a safe, welcoming, and warmly geeky environment for everybody, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [info@pushtheworld.us](mailto:info@pushtheworld.us). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 0000000..9bc0265 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +:tada::clinking_glasses: First off, thanks for taking the time to contribute! :tada::clinking_glasses: + +Contributions are always welcome, no matter how small. + +The following is a small set of guidelines for how to contribute to the project + +## Where to start + +### Code of Conduct +This project adheres to the Contributor Covenant [Code of Conduct](CODE_OF_CONDUCT.md). +By participating you are expected to adhere to these expectations. Please report unacceptable behaviour to [info@pushtheworld.us](mailto:info@pushtheworld.us) + +### Contributing on Github + +If you're new to Git and want to learn how to fork this repo, make your own additions, and include those additions in the master version of this project, check out this [great tutorial](http://blog.davidecoppola.com/2016/11/howto-contribute-to-open-source-project-on-github/). + +### Community + +This project is maintained by the [OpenBCI](www.openbci.com) and [NeuroTechX](www.neurotechx.com) community. Join the NeuroTechX Slack to check out our #devices channel, where discussions about OpenBCI takes place. + +## How can I contribute? + +If there's a feature you'd be interested in building, go ahead and we'll support you as much as we can. When you're finished submit a pull request to the master branch referencing the specific issue you addressed. + +If you find a bug, or have a suggestion on how to improve the project, just fill out a [Github issue](../../issues) diff --git a/README.md b/README.md index 8e3224d..e9e0373 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# OpenBCI NodeJS SDK + +

+ banner +

+

+ Provide a single source to program all OpenBCI biosensors in NodeJS +

+ [![Stories in Ready](https://badge.waffle.io/OpenBCI/OpenBCI_NodeJS.png?label=ready&title=Ready)](https://waffle.io/OpenBCI/OpenBCI_NodeJS) [![Join the chat at https://gitter.im/OpenBCI/OpenBCI_NodeJS](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/OpenBCI/OpenBCI_NodeJS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/OpenBCI/OpenBCI_NodeJS.svg?branch=master)](https://travis-ci.org/OpenBCI/OpenBCI_NodeJS) @@ -6,1242 +15,129 @@ [![npm](https://img.shields.io/npm/dm/openbci.svg?maxAge=2592000)](http://npmjs.com/package/openbci) [![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/Flet/semistandard) -# OpenBCI Node.js SDK - -A Node.js module for OpenBCI ~ written with love by [Push The World!](http://www.pushtheworldllc.com) - -We are proud to support all functionality of the Cyton (8 and 16 Channel boards). For the Ganglion (4 channel) please visit [OpenBCI_NodeJS_Ganglion](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion). Push The World is actively developing and maintaining this module. - -The purpose of this module is to **get connected** and **start streaming** as fast as possible. - -Python researcher or developer? Check out how easy it is to [get access to the entire API in the Python example](examples/python)! - -### Table of Contents: ---- - -1. [TL;DR](#tldr) -2. [Installation](#install) -3. [Cyton (32bit Board)](#cyton) - 1. [About](#about) - 2. [General Overview](#general-overview) - 3. [SDK Reference Guide](#sdk-reference-guide) - * [Constructor](#constructor) - * [Methods](#method) - * [Events](#event) - * [Constants](#constants) -6. [Interfacing With Other Tools](#interfacing-with-other-tools) -7. [Developing](#developing) -8. [Testing](#developing-testing) -9. [Contribute](#contribute) -10. [License](#license) -11. [Roadmap](#roadmap) - -### Installation: -``` -npm install openbci -``` -#### serialport dependency -If you encounter this error when trying to run: -``` -Error: The module '/path/to/your/project/node_modules/serialport/build/Release/serialport.node' -was compiled against a different Node.js version using -NODE_MODULE_VERSION 48. This version of Node.js requires -NODE_MODULE_VERSION 51. Please try re-compiling or re-installing -the module (for instance, using `npm rebuild` or`npm install`). -``` -...the issue can be resolved by running: -``` -npm rebuild --build-from-source -``` -### TL;DR: -Get connected and [start streaming right now with the example code](examples/getStreaming/getStreaming.js). - -#### Cyton (8 and 16 channel boards) -```ecmascript 6 -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` - .then(() => { - ourBoard.on('ready',() => { - ourBoard.streamStart(); - ourBoard.on('sample',(sample) => { - /** Work with sample */ - for (let i = 0; i < ourBoard.numberOfChannels(); i++) { - console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts."); - // prints to the console - // "Channel 1: 0.00001987 Volts." - // "Channel 2: 0.00002255 Volts." - // ... - // "Channel 8: -0.00001875 Volts." - } - }); - }); -}); -``` - -# Cyton - -## About: -Want to know if the module really works? Check out some projects and organizations using it: - -* [_OpenEXP_](https://github.com/openexp/OpenEXP): an open-source desktop app for running experiments and collecting behavioral and physiological data. -* [_Thinker_](http://www.pushtheworldllc.com/): a project building the world's first brainwave-word database. -* [_NeuroJS_](https://github.com/NeuroJS): a community dedicated to Neuroscience research using JavaScript, they have several great examples. - -Still not satisfied it works?? Check out this [detailed report](http://s132342840.onlinehome.us/pushtheworld/files/voltageVerificationTestPlanAndResults.pdf) that scientifically validates the output voltages of this module. - -How are you still doubting and not using this already? Fine, go look at some of the [800 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it! - -## General Overview: - -Python researcher or developer? Check out how easy it is to [get access to the entire API in the Python example](examples/python)! - -Initialization --------------- - -Initializing the board: - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -``` -Go [checkout out the get streaming example](examples/getStreaming/getStreaming.js)! - -For initializing with options, such as verbose print outs: - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton({ - verbose: true -}); -``` - -Or if you don't have a board and want to use synthetic data: - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton({ - simulate: true -}); -``` - -Have a daisy?: -```js -var Cyton = require('openbci').Cyton; -var ourBoard = new Cyton({ - boardType: `daisy`, - hardSet: true -}); -``` -Go [checkout out the get streaming with daisy example](examples/getStreamingDaisy/getStreamingDaisy.js)! - -Another useful way to start the simulator: -```js -const Cyton = require('openbci').Cyton; -const k = require('openbci-utilities').Constants; -const ourBoard = new Cyton(); -ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true - .then((boardSerial) => { - ourBoard.on('ready',() => { - /** Start streaming, reading registers, what ever your heart desires */ - }); - }).catch((err) => { - /** Handle connection errors */ - }); -``` - -or if you are using ES6: -```js -import { Cyton } from 'openbci'; -import { Constants } from 'openbci-utilities'; -const ourBoard = new Cyton(); -ourBoard.connect(Constants.OBCISimulatorPortName); -``` - -To debug, it's amazing, do: -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton({ - debug: true -}); -``` -Go [checkout out the debug example](examples/debug/debug.js)! - -'ready' event ------------- - -You MUST wait for the 'ready' event to be emitted before streaming/talking with the board. The ready happens asynchronously -so installing the 'sample' listener and writing before the ready event might result in... nothing at all. - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { - /** Start streaming, reading registers, what ever your heart desires */ - }); -}).catch(function(err) { - /** Handle connection errors */ -}); -``` - -Sample properties: ------------------- -* `startByte` (`Number` should be `0xA0`) -* `sampleNumber` (a `Number` between 0-255) -* `channelData` (channel data indexed at 0 filled with floating point `Numbers` in Volts) -* `accelData` (`Array` with X, Y, Z accelerometer values when new data available) -* `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) - -The power of this module is in using the sample emitter, to be provided with samples to do with as you wish. - -You can also start the simulator by sending [`.connect(portName)`](#method-connect) with `portName` equal to [`'OpenBCISimulator'`](#constants-obcisimulatorportname). - -To get a ['sample'](#event-sample) event, you need to: -------------------------------------- -1. Call [`.connect(serialPortName)`](#method-connect) -2. Install the ['ready'](#event-ready) event emitter on resolved promise -3. In callback for ['ready'](#event-ready) emitter, call [`streamStart()`](#method-stream-start) -4. Install the ['sample'](#event-sample) event emitter -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(function() { - ourBoard.on('ready',function() { - ourBoard.streamStart(); - ourBoard.on('sample',function(sample) { - /** Work with sample */ - }); - }); -}).catch(function(err) { - /** Handle connection errors */ -}); -``` -Close the connection with [`.streamStop()`](#method-stream-stop) and disconnect with [`.disconnect()`](#method-disconnect) -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.streamStop().then(ourBoard.disconnect()); -``` - -Time Syncing ------------- -You must be using OpenBCI firmware version 2 in order to do time syncing. After you [`.connect()`](#method-connect) and send a [`.softReset()`](#method-soft-reset), you can call [`.usingVersionTwoFirmware()`](#method-using-version-two-firmware) to get a boolean response as to if you are using `v1` or `v2`. - -Now using firmware `v2`, the fun begins! We synchronize the Board's clock with the module's time. In firmware `v2` we leverage samples with time stamps and _ACKs_ from the Dongle to form a time synchronization strategy. Time syncing has been verified to +/- 4ms and a test report is on the way. We are still working on the synchronize of this module and an NTP server, this is an open call for any NTP experts out there! With a global NTP server you could use several different devices and all sync to the same time server. That way you can really do some serious cloud computing! - -Keep your resync interval above 50ms. While it's important to resync every couple minutes due to drifting of clocks, please do not try to sync without getting the last sync event! We can only support one sync operation at a time! - -Using local computer time: -```js -const Cyton = require('openbci').Cyton; -const k = require('openbci-utilities').Constants; -const ourBoard = new Cyton({ - verbose:true -}); - -const resyncPeriodMin = 5; // re sync every five minutes -const secondsInMinute = 60; -let sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! -let timeSyncPossible = false; - -// Call to connect -ourBoard.connect(portName).then(() => { - ourBoard.on('ready',() => { - // Get the sample rate after 'ready' - sampleRate = ourBoard.sampleRate(); - // 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(); - - ourBoard.streamStart() - .then(() => { - /** Start streaming command sent to board. */ - }) - .catch(err => { - console.log(`stream start: ${err}`); - }); - }); - - // PTW recommends sample driven - ourBoard.on('sample',sample => { - // Resynchronize every every 5 minutes - if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) { - ourBoard.syncClocksFull() - .then(syncObj => { - // Sync was successful - if (syncObj.valid) { - // Log the object to check it out! - console.log(`syncObj`,syncObj); - - // Sync was not successful - } else { - // Retry it - console.log(`Was not able to sync, please retry?`); - } - }); - } - - if (sample.timeStamp) { // true after the first sync - console.log(`NTP Time Stamp ${sample.timeStamp}`); - } - - }); -}) -.catch(err => { - console.log(`connect: ${err}`); -}); -``` - -Auto-finding boards -------------------- -You must have the OpenBCI board connected to the PC before trying to automatically find it. - -If a port is not automatically found, then call [`.listPorts()`](#method-list-ports) to get a list of all serial ports this would be a good place to present a drop down picker list to the user, so they may manually select the serial port name. - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.autoFindOpenBCIBoard().then(portName => { - if(portName) { - /** - * Connect to the board with portName - * i.e. ourBoard.connect(portName)..... - */ - } else { - /**Unable to auto find OpenBCI board*/ - } -}); -``` - -Note: [`.autoFindOpenBCIBoard()`](#method-auto-find-open-bci-board) will return the name of the Simulator if you instantiate with option `simulate: true`. - -Auto Test - (Using impedance to determine signal quality) ---------------------------------------------------------- -Measuring impedance is a vital tool in ensuring great data is collected. - -**_IMPORTANT!_** Measuring impedance takes time, so *only test what you must* - -Your OpenBCI board will have electrodes hooked up to either a P input, N input or in some cases both inputs. - -To test specific inputs of channels: - -1. Connect to board. -2. Start streaming. -3. Install the ['impedanceArray'](#event-impedance-array) event -4. Call [`.impedanceTestChannels()`](#method-impedance-test-channels) with your configuration array - -A configuration array looks like, for an 8 channel board, `['-','N','n','p','P','-','b','b']` - -Where there are the same number of elements as channels and each element can be either: - -* `p` or `P` (only test P input) -* `n` or `N` (only test N input) -* `b` or `B` (test both inputs) (takes 66% longer to run then previous two `p` or `n`) -* `-` (ignore channel) - -Without further ado, here is an example: -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { - ourBoard.streamStart(); - ourBoard.once('impedanceArray', impedanceArray => { - /** Work with impedance Array */ - }); - ourBoard.impedanceTestChannels(['n','N','n','p','P','p','b','B']).catch(err => console.log(err)); - }); -}).catch(function(err) { - /** Handle connection errors */ -}); -``` - -But wait! What is this `impedanceArray`? An Array of Objects, for each object: -``` -[{ - channel: 1, - P: { - raw: -1, - text: 'init' - }, - N: { - raw: -1, - text: 'init' - } -}, -{ - // Continues for each channel up to the amount of channels on board (8 or 16) -},...]; -``` - -Where: - -* *channel* is the channel number (`impedanceArray[0]` is channel 1, `impedanceArray[6]` is channel 7) -* *P* is the P input data (Note: P is capitalized) - * *raw* is an impedance value resulting from the Goertzel algorithm. - * *text* is a text interpretation of the `average` - * **Good** impedance is < 5k Ohms - * **Ok** impedance is 5 to 10k Ohms - * **Bad** impedance is > 10k Ohms - * **None** impedance is > 1M Ohms -* *N* is the N input data (Note: N is capitalized) (see above for what N object consists of) - -To run an impedance test on all inputs, one channel at a time: - -1. Connect to board -2. Start streaming -3. Install the ['impedanceArray'](#event-impedance-array) -4. Call [`.impedanceTestAllChannels()`](#method-impedance-test-all-channels) - -**Note: Takes up to 5 seconds to start measuring impedances. There is an unknown number of samples taken. Not always 60!** - -For example: - -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.streamStart(); - ourBoard.on('impedanceArray', impedanceArray => { - /** Work with impedance */ - }); - ourBoard.impedanceTestAllChannels(); -} -``` +## Welcome! -See Reference Guide for a complete list of impedance tests. +First and foremost, Welcome! :tada: Willkommen! :confetti_ball: Bienvenue! :balloon::balloon::balloon: -## SDK Reference Guide: ---------------- -### Constructor: +Thank you for visiting the OpenBCI NodeJS SDK repository. -#### Cyton (options) +This document (the README file) is a hub to give you some information about the project. Jump straight to one of the sections below, or just scroll down to find out more. -Create new instance of an Cyton board. +* [What are we doing? (And why?)](#what-are-we-doing) +* [Who are we?](#who-are-we) +* [What do we need?](#what-do-we-need) +* [How can you get involved?](#get-involved) +* [Get in touch](#contact-us) +* [Find out more](#find-out-more) +* [Understand the jargon](#glossary) -**_options (optional)_** +## What are we doing? -Board optional configurations. +### The problem -* `baudRate` {Number} - Baud Rate, defaults to 115200. Manipulating this is allowed if firmware on board has been previously configured. -* `boardType` {String} - Specifies type of OpenBCI board (3 possible boards) - * `default` - 8 Channel OpenBCI board (Default) - * `daisy` - 8 Channel board with Daisy Module - 16 Channels -* `hardSet` {Boolean} - Recommended if using `daisy` board! For some reason, the `daisy` is sometimes not picked up by the module so you can set `hardSet` to true which will ensure the daisy is picked up. (Default `false`) -* `simulate` {Boolean} - Full functionality, just mock data. Must attach Daisy module by setting `simulatorDaisyModuleAttached` to `true` in order to get 16 channels. (Default `false`) -* `simulatorBoardFailure` {Boolean} - Simulates board communications failure. This occurs when the RFduino on the board is not polling the RFduino on the dongle. (Default `false`) -* `simulatorDaisyModuleAttached` {Boolean} - Simulates a daisy module being attached to the OpenBCI board. This is useful if you want to test how your application reacts to a user requesting 16 channels but there is no daisy module actually attached, or vice versa, where there is a daisy module attached and the user only wants to use 8 channels. (Default `false`) -* `simulatorFirmwareVersion` {String} - Allows the simulator to use firmware version 2 features. (2 Possible Options) - * `v1` - Firmware Version 1 (Default) - * `v2` - Firmware Version 2 -* `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which occurs commonly in real devices. It is recommended to test code with this enabled. (4 Possible Options) - * `none` - do not fragment packets; output complete chunks immediately when produced (Default) - * `random` - output random small chunks of data interspersed with full buffers - * `fullBuffers` - allow buffers to fill up until latency timer has expired - * `oneByOne` - output each byte separately -* `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers of data, if `simulatorFragmentation` is specified. (Default `16`) -* `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is specified. (Default `4096`) -* `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`) -* `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`) -* `simulatorInjectLineNoise` {String} - Injects line noise on channels. (3 Possible Options) - * `60Hz` - 60Hz line noise (Default) [America] - * `50Hz` - 50Hz line noise [Europe] - * `none` - Do not inject line noise. -* `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that setting and this sample rate will be used. (Default is `250`) -* `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely due to a OpenBCI dongle not being plugged in. -* `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source of truth instead of local computer time. If you are running experiments on your local computer, keep this `false`. (Default `false`) -* `sntpTimeSyncHost` - {String} The sntp server to use, can be either sntp or ntp (Defaults `pool.ntp.org`). -* `sntpTimeSyncPort` - {Number} The port to access the sntp server (Defaults `123`) -* `verbose` {Boolean} - Print out useful debugging events (Default `false`) -* `debug` {Boolean} - Print out a raw dump of bytes sent and received (Default `false`) +* There are a bunch of NodeJS repos for the Ganglion, Cyton and Wifi Shield +* Examples are spread out across all these NodeJS repos +* Some examples across NodeJS repos have the same code +* There is no common interface for these NodeJS repos +* NodeJS is a powerful tool for -**Note, we have added support for either all lowercase OR camel case for the options, use whichever style you prefer.** +So, if even the very best developers want to use NodeJS with their OpenBCI boards, they are left scratching their heads with where to begin. integrate the current easy to use Cyton and Ganglion NodeJS drivers, they are still burdened by the limitations of the physical hardware on the OpenBCI system. -### Methods: +### The solution -#### .autoFindOpenBCIBoard() +The OpenBCI NodeJS SDK will: -Automatically find an OpenBCI board. +* Allow NodeJS users to import one module and use any board they choose +* Provide examples of using NodeJS to port data to other apps like python and lab streaming layer +* Provide a no low level device specific code to prevent the need to rewrite new examples for each board +* Provide examples of filtering and different functions to transform raw data +* Provide a common interface to all openbci boards to increase the speed at which new boards can be integrated -**Note: This will always return an Array of `COM` ports on Windows** +Using this repo provides a building block for developing with NodeJS. The goal for the NodeJS library is to ***provide a single source to program all OpenBCI biosensors in NodeJS*** -**_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux or `OpenBCISimulator` if `this.options.simulate === true`. +## Who are we? -#### .channelOff(channelNumber) +The founder of the OpenBCI NodeJS SDK is [AJ Keller][link_aj_keller]. If we look back in time, we see this library took shape when the [Cyton][link_shop_cyton] board was the only [OpenBCI][link_openbci] board around. Then the [Ganglion][link_shop_ganglion] came around which required it's own [nodejs libary][link_nodejs_ganglion]! When the [wifi shield][link_shop_wifi] was in development, AJ created the [wifi nodejs driver][link_nodejs_wifi] which was had a lot of overlap with Cyton and Ganglion nodejs drivers. Therefore AJ pulled out the common code between all three NodeJS modules and created the [nodejs utilities][link_node_utilities] which as of today is also available to use in the browser. -Turn off a specified channel +The contributors to these repos are people using NodeJS mainly for their data acquisition purposes. For example, the entire OpenBCI GUI is dependent on the NodeJS ecosystem to provide cross platform support. -**_channelNumber_** +## What do we need? -A number (1-16) specifying which channel you want to turn off. +**You**! In whatever way you can help. -**_Returns_** a promise, fulfilled if the command was sent to the write queue. +We need expertise in programming, user experience, software sustainability, documentation and technical writing and project management. -#### .channelOn(channelNumber) +We'd love your feedback along the way. -Turn on a specified channel +Our primary goal is to provide a single source to program all OpenBCI biosensors in NodeJS, and we're excited to support the professional development of any and all of our contributors. If you're looking to learn to code, try out working collaboratively, or translate you skills to the digital domain, we're here to help. -**_channelNumber_** +## Get involved -A number (1-16) specifying which channel you want to turn on. +If you think you can help in any of the areas listed above (and we bet you can) or in any of the many areas that we haven't yet thought of (and here we're *sure* you can) then please check out our [contributors' guidelines](CONTRIBUTING.md) and our [roadmap](ROADMAP.md). -**_Returns_** a promise, fulfilled if the command was sent to the write queue. +Please note that it's very important to us that we maintain a positive and supportive environment for everyone who wants to participate. When you join us we ask that you follow our [code of conduct](CODE_OF_CONDUCT.md) in all interactions both on and offline. -#### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) -Send a channel setting command to the board. +## Contact us -**_channelNumber_** +If you want to report a problem or suggest an enhancement we'd love for you to [open an issue](../../issues) at this github repository because then we can get right on it. But you can also contact [AJ][link_aj_keller] by email (pushtheworldllc AT gmail DOT com) or on [twitter](https://twitter.com/aj-ptw). -Determines which channel to set. It's a 'Number' (1-16) +You can also hang out, ask questions and share stories in the [OpenBCI NodeJS room](https://gitter.im/OpenBCI/OpenBCI_NodeJS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) on Gitter. -**_powerDown_** +## Find out more -Powers the channel up or down. It's a 'Bool' where `true` turns the channel off and `false` turns the channel on (default) +You might be interested in: -**_gain_** +* Purchase a [Cyton][link_shop_cyton] | [Ganglion][link_shop_ganglion] | [WiFi Shield][link_shop_wifi_shield] from [OpenBCI][link_openbci] +* Get taught how to use OpenBCI devices by [Push The World][link_ptw] BCI Consulting -Sets the gain for the channel. It's a 'Number' that is either (1,2,4,6,8,12,24(default)) +And of course, you'll want to know our: -**_inputType_** +* [Contributors' guidelines](CONTRIBUTING.md) +* [Roadmap](ROADMAP.md) -Selects the ADC channel input source. It's a 'String' that **MUST** be one of the following: "normal", "shorted", "biasMethod" , "mvdd" , "temp" , "testsig", "biasDrp", "biasDrn". +## Glossary -**_bias_** +OpenBCI boards are commonly referred to as _biosensors_. A biosensor converts biological data into digital data. -Selects if the channel shall include the channel input in bias generation. It's a 'Bool' where `true` includes the channel in bias (default) or `false` it removes it from bias. - -**_srb2_** - -Select to connect (`true`) this channel's P input to the SRB2 pin. This closes a switch between P input and SRB2 for the given channel, and allows the P input to also remain connected to the ADC. It's a 'Bool' where `true` connects this input to SRB2 (default) or `false` which disconnect this input from SRB2. - -**_srb1_** - -Select to connect (`true`) all channels' N inputs to SRB1. This effects all pins, and disconnects all N inputs from the ADC. It's a 'Bool' where `true` connects all N inputs to SRB1 and `false` disconnects all N inputs from SRB1 (default). - -**_Returns_** a promise fulfilled if proper commands sent to the write queue, rejects on bad input or no board. - -**Example** -```js -ourBoard.channelSet(2,false,24,'normal',true,true,false); -// sends ['x','2','0','6','0','1','1','0','X'] to the command queue -``` - -#### .connect(portName) - -The essential precursor method to be called initially to establish a serial connection to the OpenBCI board. - -**_portName_** - -The system path of the OpenBCI board serial port to open. For example, `/dev/tty` on Mac/Linux or `COM1` on Windows. - -**_Returns_** a promise, fulfilled by a successful serial connection to the board. - -#### .debugSession() - -Calls all [`.printPacketsBad()`](#method-print-packets-bad), [`.printPacketsRead()`](#method-print-packets-read), [`.printBytesIn()`](#method-print-bytes-in) - -#### .disconnect() - -Closes the serial port opened by [`.connect()`](#method-connect). Waits for stop streaming command to be sent if currently streaming. - -**_Returns_** a promise, fulfilled by a successful close of the serial port object, rejected otherwise. - -### .getInfo() - -Get the core info object. It's the object that actually drives the parsing of data. - -**_Returns_** Object - {{boardType: string, sampleRate: number, firmware: string, numberOfChannels: number, missedPackets: number}} - -### .getSettingsForChannel(channelNumber) - -Gets the specified channelSettings register data from printRegisterSettings call. - -**_channelNumber_** - -A number specifying which channel you want to get data on. Only 1-8 at this time. - -**Note, at this time this does not work for the daisy board** - -**_Returns_** a promise, fulfilled if the command was sent to the board and the `.processBytes()` function is ready to reach for the specified channel. - -### .hardSetBoardType(boardType) - -Used to sync the module and board to `boardType`. - -**Note: This has the potential to change the way data is parsed** +The [Ganglion][link_shop_ganglion] has 4 channels, meaning the Ganglion can take four simultaneous voltage readings. -**_boardType_** - -A String indicating the number of channels. - -* `default` - Default board: Sample rate is `250Hz` and number of channels is `8`. -* `daisy` - Daisy board: Sample rate is `125Hz` and number of channels is `16`. - -**_Returns_** a promise, fulfilled if both the board and module are the requested `boardType`, rejects otherwise. - -### .impedanceTestAllChannels() - -To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a little while to actually run (<8 seconds)! - -Don't forget to install the ['impedanceArray'](#event-impedance-array) emitter to receive the impendances! - -**Note, you must be connected in order to set the test commands. Also this method can take up to 5 seconds to send all commands!** - -**_Returns_** a promise upon completion of test. - -#### .impedanceTestChannels(arrayOfCommands) - -**_arrayOfCommands_** - -The array of configurations where there are the same number of elements as channels and each element can be either: - -* `p` or `P` (only test P input) -* `n` or `N` (only test N input) -* `b` or `B` (test both inputs) (takes 66% longer to run then previous two `p` or `n`) -* `-` (ignore channel) - -Don't forget to install the `impedanceArray` emitter to receive the impendances! - -**Note, you must be connected in order to set the test commands. Also this method can take up to 5 seconds to send all commands!** - -**_Returns_** a promise upon completion of test. +The [Cyton][link_shop_cyton] has 8 channels and [Cyton with Daisy][link_shop_cyton_daisy] has 16 channels. -#### .impedanceTestChannel(channelNumber) +Generally speaking, the Cyton records at a high quality with less noise. Noise is anything that is not signal. -Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs. +## Thank you -**_channelNumber_** +Thank you so much (Danke schön! Merci beaucoup!) for visiting the project and we do hope that you'll join us on this amazing journey to make programming with OpenBCI fun and easy. -A Number, specifies which channel you want to test. - -**_Returns_** a promise that resolves a single channel impedance object. - -**Example** -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(function(boardSerial) { - ourBoard.on('ready',function() { - ourBoard.streamStart(); - ourBoard.impedanceTestChannel(1) - .then(impedanceObject => { - /** Do something with impedanceObject! */ - }) - .catch(err => console.log(err)); - }); -}).catch(function(err) { - /** Handle connection errors */ -}); -``` -Where an impedance for this method call would look like: -```json -{ - "channel": 1, - "P": { - "raw": 2394.45, - "text": "good" - }, - "N": { - "raw": 7694.45, - "text": "ok" - } -} -``` - -#### .impedanceTestChannelInputP(channelNumber) - -Run impedance test on a single channel, applying the test signal only to P input. - -**_channelNumber_** - -A Number, specifies which channel you want to test. - -**_Returns_** a promise that resolves a single channel impedance object. - -**Example** -```js -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(() => { - ourBoard.on('ready', () => { - ourBoard.streamStart(); - ourBoard.impedanceTestChannelInputP(1) - .then(impedanceObject => { - /** Do something with impedanceObject! */ - }) - .catch(err => console.log(err)); - }); -}).catch(function(err) { - /** Handle connection errors */ -}); -``` -Where an impedance for this method call would look like: -```json -{ - "channel": 1, - "P": { - "raw": 2394.45, - "text": "good" - }, - "N": { - "raw": -1, - "text": "init" - } -} +## Installation: ``` - -#### .impedanceTestChannelInputN(channelNumber) - -Run impedance test on a single channel, applying the test signal only to N input. - -**_channelNumber_** - -A Number, specifies which channel you want to test. - -**_Returns_** a promise that resolves a single channel impedance object. - -**Example** -```ecmascript 6 -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton(); -ourBoard.connect(portName).then(() => { - ourBoard.on('ready',() => { - ourBoard.streamStart(); - ourBoard.impedanceTestChannelInputN(1) - .then(impedanceObject => { - /** Do something with impedanceObject! */ - }) - .catch(err => console.log(err)); - }); -}).catch(err => console.log(err)); - -``` -Where an impedance for this method call would look like: -```json -{ - "channel": 1, - "P": { - "raw": -1, - "text": "init" - }, - "N": { - "raw": 7694.45, - "text": "ok" - } -} -``` - -#### .impedanceTestContinuousStart() - -Sends command to turn on impedances for all channels and continuously calculate their impedances. - -**_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer - -#### .impedanceTestContinuousStop() - -Sends command to turn off impedances for all channels and stop continuously calculate their impedances. - -**_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer - -#### .isConnected() - -Checks if the driver is connected to a board. -**_Returns_** a boolean, true if connected - -#### .isStreaming() - -Checks if the board is currently sending samples. -**_Returns_** a boolean, true if streaming - -#### .listPorts() - -List available ports so the user can choose a device when not automatically found. - -**_Returns_** a promise, fulfilled with a list of available serial ports. - -#### .numberOfChannels() - -Get the current number of channels available to use. (i.e. 8 or 16). - -**Note: This is dependent on if you configured the board correctly on setup options. Specifically as a daisy.** - -**_Returns_** a number, the total number of available channels. - -### .overrideInfoForBoardType(boardType) - -Set the info property for board type. - -**Note: This has the potential to change the way data is parsed** - -**_boardType_** - -A String indicating the number of channels. - -* `default` - Default board: Sample rate is `250Hz` and number of channels is `8`. -* `daisy` - Daisy board: Sample rate is `125Hz` and number of channels is `16`. - -### .printBytesIn() - -Prints the total number of bytes that were read in this session to the console. - -#### .printPacketsBad() - -Prints the total number of packets that were not able to be read in this session to the console. - -#### .printPacketsRead() - -Prints the total number of packets that were read in this session to the console. - -#### .printRegisterSettings() - -Prints all register settings for the ADS1299 and the LIS3DH on the OpenBCI board. - -**_Returns_** a promise, fulfilled if the command was sent to the write queue. - -#### .radioBaudRateSet(speed) - -Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial data is sent from the Host to the Serial driver. The rate can either be set to default or fast. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the new baud rate after closing the current serial port and reopening one. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_speed_** - -{String} - The baud rate that to switch to. Can be either `default` (115200) or `fast` (230400). - -**_Returns_** {Promise} - Resolves a {Number} that is the new baud rate, rejects on error. - -#### .radioChannelGet() - -Used to query the OpenBCI system for it's radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve an Object. See `returns` below. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_Returns_** {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in the condition that there system is experiencing board communications failure. - -#### .radioChannelSet(channelNumber) - -Used to set the system radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_channelNumber_** - -{Number} - The channel number you want to set to, 1-25. - -**_Returns_** {Promise} - Resolves with the new channel number, rejects with err. - -#### .radioChannelSetHostOverride(channelNumber) - -Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if your dongle and board are not on the right channel and bring down your radio system if you take your dongle and board are not on the same channel. Use with caution! The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_channelNumber_** - -{Number} - The channel number you want to set to, 1-25. - -**_Returns_** {Promise} - Resolves with the new channel number, rejects with err. - -#### .radioPollTimeGet() - -Used to query the OpenBCI system for it's device's poll time. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the poll time when fulfilled. It's important to note that if the board is not on, this function will always be rejected with a failure message. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_Returns_** {Promise} - Resolves with the new poll time, rejects with err. - -#### .radioPollTimeSet(pollTime) - -Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this sets the interval at which the Device polls the Host for new information. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_pollTime_** - -{Number} - The poll time you want to set to, 0-255. - -**_Returns_** {Promise} - Resolves with the new channel number, rejects with err. - -#### .radioSystemStatusGet() - -Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are in fact ready to start trying to connect and such. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the same channel and powered, then this will resolve true. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_Returns_** {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise. - -#### .sampleRate() - -Get the current sample rate. - -**Note: This is dependent on if you configured the board correctly on setup options. Specifically as a daisy.** - -**_Returns_** a number, the current sample rate. - -#### .sdStart(recordingDuration) - -Start logging to the SD card. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. - -**_recordingDuration_** - -The duration you want to log SD information for. Opens a new SD file to write into. Limited to: - - * `14sec` - 14 seconds - * `5min` - 5 minutes - * `15min` - 15 minutes - * `30min` - 30 minutes - * `1hour` - 1 hour - * `2hour` - 2 hour - * `4hour` - 4 hour - * `12hour` - 12 hour - * `24hour` - 24 hour - -**Note: You must have the proper type of SD card inserted into the board for logging to work.** - -**_Returns_** resolves if the command was added to the write queue. - -#### .sdStop() - -Stop logging to the SD card and close any open file. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. The success message contains a lot of useful information about what happened when writing to the SD card. - -**_Returns_** resolves if the command was added to the write queue. - -#### .simulatorEnable() - -To enter simulate mode. Must call [`.connect()`](#method-connect) after. - -**Note, must be called after the constructor.** - -**_Returns_** a promise, fulfilled if able to enter simulate mode, reject if not. - -#### .simulatorDisable() - -To leave simulate mode. - -**Note, must be called after the constructor.** - -**_Returns_** a promise, fulfilled if able to stop simulate mode, reject if not. - -#### .sntp - -Extends the popular SNTP package on [npmjs](https://www.npmjs.com/package/sntp) - -#### .sntpGetOffset() - -Stateful method for querying the current offset only when the last one is too old. (defaults to daily) - -**_Returns_** a promise with the time offset - -#### .sntpStart() - -This starts the SNTP server and gets it to remain in sync with the SNTP server. - -**_Returns_** a promise if the module was able to sync with NTP server. - -#### .sntpStop() - -Stops the SNTP from updating - -#### .softReset() - -Sends a soft reset command to the board. - -**Note, this method must be sent to the board before you can start streaming. This triggers the initial 'ready' event emitter.** - -**_Returns_** a promise, fulfilled if the command was sent to the write queue. - -#### .streamStart() - -Sends a start streaming command to the board. - -**Note, You must have called and fulfilled [`.connect()`](#method-connect) AND observed a `'ready'` emitter before calling this method.** - -**_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. - -#### .streamStop() - -Sends a stop streaming command to the board. - -**Note, You must have called and fulfilled [`.connect()`](#method-connect) AND observed a `'ready'` emitter before calling this method.** - -**_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable. - -#### .syncClocks() - -Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using version +2 firmware. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_Returns_** {Promise} resolves if the command was sent to the write queue, rejects if unable. - -#### .syncClocksFull() - -Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using v2 firmware. Uses the `synced` event to ensure multiple syncs don't overlap. - -**Note, this functionality requires OpenBCI Firmware Version 2.0** - -**_Returns_** {Promise} resolves if `synced` event is emitted, rejects if not connected or using firmware v2. Resolves with a synced object: -```js -{ - "boardTime": 0, // The time contained in the time sync set packet. - "correctedTransmissionTime": false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation. - "timeSyncSent": 0, // The time the `<` was sent to the Dongle. - "timeSyncSentConfirmation": 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle. - "timeSyncSetPacket": 0, // The time the set packet was received from the Board. - "timeRoundTrip": 0, // Simply timeSyncSetPacket - timeSyncSent. - "timeTransmission": 0, // Estimated time it took for time sync set packet to be sent from Board to Driver. - "timeOffset": 0, // The map (or translation) from boardTime to module time. - "timeOffsetMaster": 0, // The map (or translation) from boardTime to module time averaged over time syncs. - "valid": false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below. -} -``` - -**Example** - -Syncing multiple times to base the offset of the average of the four syncs. - -```javascript -const Cyton = require('openbci').Cyton; -const ourBoard = new Cyton({ - verbose:true -}); -let portName = /* INSERT PORT NAME HERE */; -let samples = []; // Array to store time synced samples into -let timeSyncActivated = false; - -ourBoard.connect(portName) - .then(() => { - ourBoard.on('ready',() => { - ourBoard.streamStart() - .then(() => { - /** Could also call `.syncClocksFull()` here */ - }) - .catch(err => { - console.log(`Error starting stream ${err}`); - }) - }); - ourBoard.on('sample',sample => { - /** If we are not synced, then do that! */ - if (timeSyncActivated === false) { - timeSyncActivated = true; - ourBoard.syncClocksFull() - .then(syncObj => { - if (syncObj.valid) { - console.log('1st sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('2nd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('3rd sync done'); - } - return ourBoard.syncClocksFull(); - }) - .then(syncObj => { - if (syncObj.valid) { - console.log('4th sync done'); - } - /* Do awesome time syncing stuff */ - }) - .catch(err => { - console.log(`sync err ${err}`); - }); - } - if (sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) { - /** If you only want to log samples with time stamps */ - samples.push(sample); - } - }); -}) -.catch(err => { - console.log(`connect ${err}`); -}); - -``` - -#### .syncRegisterSettings() - -Syncs the internal channel settings object with a cyton, this will take about over a second because there are delays between the register reads in the firmware. - -**_Returns_** a promise, fulfilled with channel settings array, where each element is a standard channel setting object, once the channel settings have been synced and reject on error. - -#### .testSignal(signal) - -Apply the internal test signal to all channels. - -**_signal_** - -A String indicating which test signal to apply - - * `dc` - Connect to DC signal - * `ground` - Connect to internal GND (VDD - VSS) - * `pulse1xFast` - Connect to test signal 1x Amplitude, fast pulse - * `pulse1xSlow` - Connect to test signal 1x Amplitude, slow pulse - * `pulse2xFast` - Connect to test signal 2x Amplitude, fast pulse - * `pulse2xSlow` - Connect to test signal 2x Amplitude, slow pulse - * `none` - Reset to default - -**_Returns_** a promise, if the commands were sent to write buffer. - -#### .time() - -Uses `._sntpNow()` time when sntpTimeSync specified `true` in options, or else Date.now() for time. - -**_Returns_** time since UNIX epoch in ms. - -#### .usingVersionTwoFirmware() - -Convenience method to determine if you can use firmware v2.x.x capabilities. - -**Note, should be called after a [`.softReset()`](#method-soft-reset) because we can parse the output of that to determine if we are using firmware version 2.** - -**_Returns_** a boolean, true if using firmware version 2 or greater. - -#### .write(dataToWrite) - -Send commands to the board. Due to the OpenBCI board firmware 1.0, a 10ms spacing **must** be observed between every command sent to the board. This method handles the timing and spacing between characters by adding characters to a global write queue and pulling from it every 10ms. If you are using firmware version +2.0 then you no spacing will be used. - -**_dataToWrite_** - -Either a single character or an Array of characters - -**_Returns_** a promise, fulfilled if the board has been connected and `dataToWrite` has been added to the write queue, rejected if there were any problems. - -**Example** - -Sends a single character command to the board. -```js -// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously -ourBoard.write('a'); -``` - -Sends an array of bytes -```js -// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously -ourBoard.write(['x','0','1','0','0','0','0','0','0','X']); -``` - -Taking full advantage of the write queue. The following would be sent at t = 0, 10ms, 20ms, 30ms -```js -ourBoard.write('t'); -ourBoard.write('a'); -ourBoard.write('c'); -ourBoard.write('o'); -``` - -### Events: - -#### .on('close', callback) - -Emitted when the serial connection to the board is closed. - -#### .on('droppedPacket', callback) - -Emitted when a packet (or packets) are dropped. Returns an array. - -#### .on('eot', callback) - -Emitted when there is an EOT a.k.a. '$$$' with a buffer filled with the data. - -#### .on('error', callback) - -Emitted when there is an on the serial port. - -### .on('hardSet', callback) - -Emitted when the module detects the board is not configured as the options for the module intended and tries to save itself. i.e. when the `daisy` option is `true` and a soft reset message is parsed and the module determines that a daisy was not detected, the module will emit `hardSet` then send an attach daisy command to recover. Either `error` will be emitted if unable to attach or `ready` will be emitted if success. - -### .on('impedanceArray', callback) - -Emitted when there is a new impedanceArray available. Returns an array. - -#### .on('query', callback) - -Emitted resulting in a call to [`.getChannelSettings()`](#method-get-settings-for-channel) with the channelSettingsObject - -#### .on('rawDataPacket', callback) - -Emitted when there is a new raw data packet available. - -#### .on('ready', callback) - -Emitted when the board is in a ready to start streaming state. - -#### .on('sample', callback) - -Emitted when there is a new sample available. - -#### .on('synced', callback) - -Emitted when there is a new sample available. - -### Constants: - -To use the constants file simply: -```js -const k = require('openbci-utilities').Constants; - -console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console. +npm install openbci ``` -### .OBCISimulatorPortName - -The name of the simulator port. - -## Interfacing With Other Tools: - -### LabStreamingLayer - -[LabStreamingLayer](https://github.com/sccn/labstreaminglayer) is a tool for streaming or recording time-series data. It can be used to interface with [Matlab](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Matlab), [Python](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Python), [Unity](https://github.com/xfleckx/LSL4Unity), and many other programs. - -To use LSL with the NodeJS SDK, go to our [labstreaminglayer example](https://github.com/OpenBCI/OpenBCI_NodeJS/tree/master/examples/labstreaminglayer), which contains code that is ready to start an LSL stream of OpenBCI data. - -Follow the directions in the [readme](https://github.com/OpenBCI/OpenBCI_NodeJS/blob/master/examples/labstreaminglayer/readme.md) to get started. - - ## Developing: ### Running: ``` -npm install -``` - -### Testing: - -``` -npm test +npm install --all ``` -## Contribute: - -1. Fork it! -2. Branch off of `development`: `git checkout development` -2. Create your feature branch: `git checkout -b my-new-feature` -3. Make changes -4. If adding a feature, please add test coverage. -5. Ensure tests all pass. (`npm test`) -6. Commit your changes: `git commit -m 'Add some feature'` -7. Push to the branch: `git push origin my-new-feature` -8. Submit a pull request. Make sure it is based off of the `development` branch when submitting! :D - ## License: MIT -## Roadmap: - -1. Ganglion integration (3.x) -2. Compatible with node streams (3.x) -3. Remove factory paradigm from main file (3.x) -5. ES6/ES7 total adoption (3.x) -4. Browser support (with browser serialport) (x.x) +[link_aj_keller]: https://github.com/aj-ptw +[link_shop_wifi_shield]: https://shop.openbci.com/collections/frontpage/products/wifi-shield?variant=44534009550 +[link_shop_ganglion]: https://shop.openbci.com/collections/frontpage/products/pre-order-ganglion-board +[link_shop_cyton]: https://shop.openbci.com/collections/frontpage/products/cyton-biosensing-board-8-channel +[link_shop_cyton_daisy]: https://shop.openbci.com/collections/frontpage/products/cyton-daisy-biosensing-boards-16-channel +[link_nodejs_cyton]: https://github.com/openbci/openbci_nodejs_cyton +[link_nodejs_ganglion]: https://github.com/openbci/openbci_nodejs_ganglion +[link_nodejs_wifi]: https://github.com/openbci/openbci_nodejs_wifi +[link_nodejs_utilities]: https://github.com/openbci/openbci_nodejs_utilities +[link_ptw]: https://www.pushtheworldllc.com +[link_openbci]: http://www.openbci.com +[link_mozwow]: http://mozillascience.github.io/working-open-workshop/index.html +[link_wifi_get_streaming]: examples/getStreaming/getStreaming.js +[link_openleaderscohort]: https://medium.com/@MozOpenLeaders +[link_mozsci]: https://science.mozilla.org diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100755 index 0000000..8a57642 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,14 @@ +# Roadmap + +## OpenBCI WiFi NodeJS + +Make programming with OpenBCI reliable, easy, research grade and fun! + +## Short term - what we're working on now + +- Add examples of data extraction +- Documentation + +## Medium term + +- Emotion detection diff --git a/examples/python/handoff.py b/examples/python/handoff.py index 7cc2a85..a095cfb 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(nb_chan + 1) + + 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/images/openbci_large.png b/images/openbci_large.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3e7eeec59a2b801d60f16e063c1c8465923a11 GIT binary patch literal 35139 zcmeEtWmg={6K{~<4#8zv+C>lAsIoE=s-HeVZU6TmzkqO0e6#nw}WdmKddSuD20)t;4 z7e5q{xqSK`Jhku@3OOvPEH-mm$9JYef0T=Ik>FF$6Zx>xl2EU5J00<%f|Neh! z09==F!LOyAY(B|kYoV=%SMskW_?b}@16CB{AIX(NioWs6V2b{>5uP>((h$m2nTJjN zpFRVpJh{dVTB=qXKBv4L8zEn{8fvv5lCb{wqL*hkD$}p2hVYcP{n-?459IDpyrlmG z(Bm^SkJJLqM&2xdemC^rJz#!tf!#R^T53KkB+jaBd0^6wM@##UG%MRtttdVfb~`9l z$Ae34_CLbJjVJn-RP&!OPTi|RIxQ^!w{->T0Xp=lHt1Y(e6axLKk2}Md|MtV8>TB` z7{pg5x0oDX6#pAf-xK|8%t8=ug|*In6IpxQO7S;e!2v6rrN^IhnQ+WbHyBd5bqA8qbuTpBKb=A9yPi6%xMK1f+SBg}^)>tz z=yVwKXgJeI zOl-=F+wi*B=I;MgpsLA~<3?{hLs#%;pDv$jNKbh)TDeQHFUg_l*7#6WI^H5q0o%UB zWrH2c&}lmrwFU(=-*Ce>S@!{8$Qq3GZI21(epSuv62lH=tq;a~&-|Jv`^@o2DRb;U$ zsg=_ZiUT(?D#26CmAwF)|7nIOB(5-v>hoRLCiQi#etK}wM#xN_O|h4SSHOTJaoYMV ztqY=%IQ;v|Tv}(5J?=Rd9Iz0xHI04nA-kl!I%Bz&9+^h2z3qzZh!G^rt>)*f^b}qK z09@eNyA;MKp%M>Dewlq%Bh@IP$#Ud}u$^L`1T8ps8EYTX78+sRRd_Tq%}6z||2ZvU z$H1F~!iH5jn`(tFDyOw6XfC6K!%g%Vw08Vl8d^r!zexh z@=d?U3;+=dmPxKwouX8veCP?g2OCI@kyOA(4loM9X9jdTVw_<)iZb%REW^_*5&eM?yN%8rahMS35q7v$n{g%G4`0FwP4mD z{hLW2-U&AG=sgS*fLT)9y3zm-xVrEFi#K7>6z(!}lHtoO=herj2~ymHH(mMxBie03 z_P=4tNW~!x=bpsyh7uuwsJW+#RWySA8y+%qfeu%jM z+-XelPfbX&7Amei1U6m+Kw8a+f{V%+?Kk0+2nA(XwGF37>sD;5v=d(rax93o!2M_YgS&p?fCxirc#-H8bpkVn|B($sc?N$HCi%pK*iVD@U)U_r zBEGs$dFvg_P5G3mEGzfFuE-o$P?-NUg)L9cR2(47VMvJb8=5Z}?V{lY>R_D_9BJK9 zQ6UuMkx7(%D|qk9|BrBclJO-ZT8K%sgj)Mt?!QUO=ii2*?G=?zo6Mu6 z62%C=@JmK*u_OQQJv%B3ezhMz0N@b!UJZPsY02Ca&R@d~)NZ{WqpX0ruvxLyJF`Y? zN@ddh`JWAnDGFcOtvvj%4Ku}F@qgX(ZZ(;o>fV1kvDdq@mL*p7YB#sf)0j$41%Nv} zI*X591Y_oYHVM?}+?HJ$X^Zn2hXDRAJkj@QIF4Vm+7aMARDFvdG%OEgs<(rZ;+kr) zD=}AN$~0TSd9AhCd)9xw2JH5 z<3oFG{|21rWZxAhRwo;z>{NgVVtgGQGwM0v;kV9^I<(;?at+sWEA3EPTr`za4WbbE z=(fdQC@Pgv3O$Wy6yrIeFr6a}dyH#q4d+hrt3X|hlOeu=F?viS=bZCUK8r|bQCneiv1+`2qe(0A6 z#{3^xO#46lIEl4uw}*Zb94tB>f^!*f`Qx1(Tl!7Q$(1rExZ}8ZkHAi7%vqURj>@uo ziE)p5I}!{})$kVlsJW2UNBcO=ho_Vc%N72V*glK8n%>KI7BA0Lc;{SaeFlt$dlB_t z{BmfTMlf$J=EB+_?&!F?ZsJlIj2)JciUZ#-BfRa5lw9B+!jdP?^bk7@duJHNCT-6J zo*cO%mMyv&t1{BKeOn0;P4dB>FSt7e_L`Uvfr@q_aOD1P^u&LEK)d(J@8J9Tw!gO- zph}(u;<>@=g{}5fOa_KOxTFt19MjlE(I1B)0tF6zxzehQ85!m4`UBy))}wdq)a9i- zF*;R&`|D80=gyxG{A_ze{E5)^y&%nN!?I4}6p`znr}h=bFQk~fm4YRQo6FA8IkR&{ zkeWrGz2JF<=M&kyk@oZ1%Bq*(+!Tb#NwN<(3S+f~HzFiIR|S|_G|2F}`A(eRw6#dg?)jaIrqm&_-F1i9OK`kCO zUwmuSG~H|x?ua1m%3tb_z|kKc=` z3j|L)PJwa;7(=dF0nM<^;mF^6I@XC&NWQnw%OM_2yBZ?7*V*?DkPRO4()>a(;}J4~ z-Ek)8W3~{J7FzVot+Qik>Q4aw=$YcM5Mk3MT;s_Ha^8EaO5g;#+hoGX`MK~Nt;0a ztXMr9N0>ThDQA*92gKI>#zzfrGFcfQRbJ5u8S%%oju71pH#j9h@6(?5)8`DEw~r4- zkIW0GzAfWs$H`&7^NMxg1}@>^pIwAM@nczAALlWTvwzAbv=>q)hrW!2cx3unzhSL} zaEZ)is8EJVKhHg3NwEHUueK_O(flfEnkOcBWgiYI+OHwU|rz5 zX&43{7$tt8{rr3AH0DT})lf$&vepN{P+fQCJ6_XhSMArKx$74%3Cb@CBDoq)u~+z5 zq<=FK!+$BGEVg0l5akaK+X_`pNFIyTsi{cML7N+w$c&wXF7j9FyDpy`!a-j;)`V~r z10m@cl+G_Cl})+S@z+T`LLqmG2L2mS^FtnY^4%DzhbSHym^{77#Q zEM}K6#mA9lDFY}~n_{s#zLsLF)O@$P0U!jqfrzh+;D1cR@=Pbqa0yNB-T6SkdLQ}t zE?X2)ct@kaeWM>nQbm2Qywp7(`0sF#LW}K>!Mzp-RG<;H|F{`!s@!!^#-d;&ciK z8N2n4*)y`z~5nDbFldXhtw_qCO3i z<7@-K+qhBpU_K;ckv5YH4~4m10P*07RUn}QMSv>IkF=Oqkn4y7qi7R@aAW&U)jEca z&-dxdOTsWAYnIg&JpAKBCdQYkVZm3-Du0>hOB;ku=dC$&up*;KgmE< zl93GA4%>;JPHESmrvSsV&&D!~PRb0={jt#8w6Z+B%{=u38gYhOucr}q9YT7ReD9ow z!iBonX1x+mjjZ=Y&=$lGDg+P`J*70FCObw2puPLfe}dVE{nAffJa}h+nqclF$$*Rq zgotaO)HNGWV1T%2s&Xuu5!47>qeKn*WI1CR|nd>uq1+_`!}uZM@t8d(2{aYGG*y){ORv} z8_dzS`EqP|wowx=COJ_|$`a)+j#(?cdFRgAb@a9(Q;cxo$V+V+D554TJKWr9@trswj8m<5DsT{9 zgb+shbn2iHQkLEyCaFH{JCTiRW(ngPOkO1@#47co-1t$fJKEvKTFfVsXiTpa5Seb& zR+$7#Z7SsYhRWAu+7oA$MF>CE+ibk*r2k>^_n2l>#=|w z^oHclMco1#t@lYq5Z%Lr;J>&I!(l2)s_K(I%|D4Lx0-D z`4U2Hyh#+|Axd~^s{1H`uqgG>$P*ZB#0)V}QBmi=p+Sh`u-c|BSV$@;*!@Z1LD?P*r=COl~%cSN0Z`_791=)|) zj-MP{$;|)Ci&VzV!BG&S=a(BVIUEyd#|-WVlEK2dgw*Ns+70+ z+hmkFfC#`x$-Exr^Ub>K%lQPmn7(U|NbIN4Lj3T!I<$IO;pvH8h07m>nDvi6#~4fo zju3})9p^0KStuz!PnH;#Tdd|y`=Z<>iLm24(&?Fb_W^$%1v@cZ5hn>!N5gfBS}~@z zl`=a;wh^7JJ{3>!GGgjZXTY_JIQrVqn1SPmryG6(IhP3zmY}OBPMH`o3iidd#sEbl^FoqMeydiM+LPhpthN)?D*aUt+h8;<;=?%7(tC*Uws^Ahner2HPJJYs zlCx+2SPKFrk9Vgo!JTodVAU_etwtEFSqDTJrWhT6S~HRB*D7?;pKaM!F5c(}AqX^t z>-Z+{&iq@Mb!%vD!Pv-_*hRQB=H&cOvceCnoXN_QeF{0zDxrU$FGH*th_Lk*)5f=B zI%U8C;i8CtJz4JX-=^z%?*q_=b4C*b;{yz2X@4oaPELc)RYL_@`as3;eNfo zn%E(Y4RAm{b6rMZWIM?<6%C$;ImUKe=KP0LR1O~hgN68ArW_9iP1oxck>J4+A8$D_ zUMcpjPc=EkEvlJ^WgmPE{3rf%Rs!{gF}~XLf$(@;$_U$6)|jERt;_kW^f}?19u$^N zxac1r-Pg&}G$57Q1-jR3wmAko9hZbO>2;ko+pxrD%!@N42sJof3`fa}1Twnk*2iH^ z1`@^i-TkN3%c>P|{7EQE-2#8A6-qT*7=Ha!FWO*p;}#eb9)Mlc(x{l6=)MeMChqGW zvdrlJSVGELbBuVVsxiBKc@Mc!=G1+~+2_XXRzgvS+p;l=zsB9j?RjzEj65!4zKHDk zK6@Fd7+ON^GlTOqqK_Wc5)Ly?ToHHP^w5=fluQsshHkl8pI+)=Gu`5qPmAbKFT5TD?kaRNDu4h(`H}K$dFC)F8sXc<| zFs~VaTCSKH5*&ZS!?VMdZX8tTHET*AP!)?UygE!fZX+?NA*J(Ze$9PJL_t{Tccfn{ z;+;1EUfzhaC$^>lxoAX8CX)`Z;s0rC$jh<3e(CbVmIXJ-^#DxGf9FzqTTymb$Lo9I8iej<9oU;b2+{=#>Ra}g zbuAuApNBsiw3O}PuvM^xk2qyT9=YS-Iz0U<<%5);>!j7NcbmFKnohe3VT;#PuN@lDzhAPCU1(mVu$T|y z^O03p`WP!*UIXn&89vaEz72WZV2G>UjkKk>cpH5YNVV*3a{~^r|w_>gxjX14qEa5hF(w$3lYRM-W5zzfY>MjIk4#Dq|zH zeND;1`9n1+NPxyoRkOj&O!7@IXI?I}j%Rc~;!w%7e;&r}gc)injNgQ#OQ7W8$<~fX z&8-i1C(CCOr%_|iOWXZLF*+g3QMX9rC(o2(=?`>n+=DmblPg!0-*KuKMx7FZ^e%d9 zk8x`P5;H!JeIJS2!(Dx1rMLvkKBq2uK`P%o5y7x;{;-=jYCx%+0NDvQ%R@r(%IY#~ z+j(-X2?x!SA+2y0o>?YVx9-N8r3ubSn;Q1Q#|}EDp@UiK`zXH~kpjZuH#Dm96-4*$ zu>jT*Dz1UA-ZY2pC0Y~XI|8Y`U|2`?xh_H0jUn5lo{aokF32!f85EIj3t2ToQP1=< zHJGvduuBZ9&}k3W05SBMaf-!3HC?wJb0vP2MDE*)kY_t4Q;THS(YF|5Ed0j5I;fvQ zSEKRYyM{b(uXtZPK@F_MaD}Z(uMZqCTdM>dE?okSAHQVDe{QEVaV{@Y-a6r9)4gPp zfi7-zhW!7*#oW$)I{i)M6yn@W6DtyxQ*IT)YB0qPvEC(Yx~f(xjBpF65PEiur&}mf zEVfZ*CmxSOi*w2+4Pnlb34|YNB9XQ8-{Y@&;0Y6Z+f0;t_NF0=QojRXWlaL2R9Mt2 zeTasABZhr-qgQ$+W592~)Lb$aDm-K9cDt1fYYrN3O@y?O9$$Dg1{u|FFm+9qgSRn; z(!pp2%h~qWXKk&&;~qH~+b)OHDKbz(6s^H%ZME^T0?w>G+?=WzrF;?%xi(*@vux== zgz^Fq-AmTFX?_2??}6MpAr;#=?|)U^yLzpJb@WH_7K6 zH316`WxpWvTazH}P$hDDBj~e6g{}V% z`I`XhxzJyq^K<9NJ~=C5dCSGY!?Ud?04O_33#GsP+N(v(i(@8G-PpD}-j&hE+PUCwwRriI9`x+orxDG# z7!qkypxBK+DP>#P;VB8JUmspSM~{!e@@5k<&XM%{>0K1#o4M?aGa$23xbbi&u`$JB{ZDdFBQyHYO}*NlsT|y!oUI0)d@bU zPtm9gPShqnVo<6iH{KQeHG+sjInLg zz`~x6UtVN4a^DJx(CuI(f%VxpKySPK%4%=hMQevT2~laTcVaQ$=-`kSCx}b*N5hyX zWoB}ndp_xs3v`29ZSX=Mz0JPuiDe3)&yAFh5rwD_@79g+xfbB^L_nE3$QDm_vv%p@ zl4f$=7NJ|vOp|J=A7Rb=R(EY3=~2B{mg?}`XSa=*9qJOWIzPMtb=PB;`)lKCwoQyE zf&;ff+jSB(r`AJM;6q3FTg%3ti;*c7Z_{t(h z0K1v{N&F6;@uJaGy%BLPC1hch^Fy~7$K84$8u}9wN^{aijJ5lFI-`P9zvA5&kV}~@ zb0g;2GLlN@Axc=CtH2M}6hPDu)i@%bD)R9~BG;zveIQ?!4M%}b^_lS(0c|eJCDX{ueP08S(BoqDOp1O9z~|C9HU6HJ>;C36pG8=`^e@*R;@zdCxM; z@hxHQ^MDd(L9m(yl5nbHb+9BAZ$84 zAAa9;IPK?P8ad%tQSyEFQnTEs41F5LqSJ9)r0Xr^o8FS@!GcYm zu9MewU1A~69yZuKtcECuY0X4~DekP_XrwCx9jPtlDnGl%4|m&BWsoaR+p^ONih$?u z_+gEx{s@&2qY>$FwwBrIh+F%irxw@t(aQd<^t9?bOFe`oR>A&ju(>Icnx*uU4 z^|b91ViijYZ_HPsuN?an=uXBwCc-YCD85vuQ$I>eB3+f#s)xI&ad&UA zYg&AhdpY>^-VHGL8pfCU+BGkLCg_DH!A=mnv1bZdzn7Z62oS=%T8jxdhyau<^j-Ve zHlddLx`t10Svz1XIkTd&;L8g-kh`;iAV+CctUK&M@e^lJw5SX6Kh+r znBuFDu?fijv2*^ZrR~Ktia66oeImTb*`qFjOU%a6(c+i?HLFncYcgY%jG6e&Gvdqy znO58UWnE1QM>8tWm0_^$YvcED`yICIN^W(g7}K8Lhg`xFGRTh15f+TnCHWIG`T@hk zLnjy%CXS(`dfazg(sm%I+F!35we}Y`3#9SyDxr4<-zP2Tdmp6DOKW0fsTk~t$Sl?< z65r9hZkk%XOP(a++9*OGu5>pRGN={QbT%_k$ZBEx$69 z)Dtyr%v_4eEonI1O1U43Y?m#hCIY6MJ?#?hhossDqC5th!^ z)n|&zOjbqO<+~xSka_&MJaw2(WF>1|+oYaBy4q1PrKe}HZ1mnUZK@FiHVf2OpHzU( zy~e@1NS*8*G7Nr~-%zFp^46N=QSrpF_oqJ*cwFb@2Nc)5RJPUJIc4d`G+jRl1sq~h zgjDr~oE@a~w3er%d$c2_Me@0hE$2->IGt5&l$5IcA`N>9D48OKO_Sss*%OKPt5>mR z>-Y({72n#lM7N%^7fK2k69>i}4O%fE{DhtD(1^arZInEGLkWD3Mio6mKuI6reDDIZ zeV67>xgm}_-$xPuw4TD00wnyIyG1+RZoaDiX2jD-J#%~S+NF$@nT-=?*-^G3Xk&($ z#1Zg&Sn5imuqtn^mqJg7W11i{Y>2tJ=Cs~gXJeaI@9g@~aHi`hr1_P$W|~arJ7e!d z1?HIX!siy!Nx5iQb=Z8e$3o-;KMk1VBB>8(0uJ0c1kYn9+8WcuHJ8j!L8u$iQw~S; zTyR@4A0~%^Svye&h))s2*!AP2;Lhae8gr5o{Azd$^!6@uu6psc%A6xg(v3xxlP06n zP-HwYhcv=}@C2en({i|#lzwd0zw5N9y-vk3%YKy?t@ycA6+eDjbFmKtGKZ^m{V|PM zZ9`}G0k_>W5!+j%!YD|liW%Fe_C=;WuITj?aGStp*BW@`js`bpLLfH^Jk<>eHM(jN z8OB9pDdeb{4m>-kDwl7tm@_%-9e96JDH`-rHMw9`EX3;_a2u8+c;uhb#p`8!tRL74 z>y3qKCc91MRghewbN`O2N%?5=R#*-mm?I%L(fy7S+8DkRpoWwvrR-)2M(+W@ATc0l#8Fp?Z7hg@ZJT$G({3)*DuZ7spT+*-02B zjw%)vkPO35F))~qYgWc(W}_?tFt?Bol~LSGlho@Wl_*vMVLBnj*?B}v*Rbc2cr?O$ z&zWQ3CH9nCAolD#n{utnM1sU{dzGKx87Y|7#XFI?oQ5=Zz4%MsVtEB7XN(*o6qASw zX2(1HmF%D|JiV(S2Ubk=!B$}z)v2a|y7F$jeCm1TcHxQ5PXd`{71JncznWUFQ=TM{ z%;m92uHJ?Jw0;2(HR^d$7pCAHi8JF)BLkPyYr`tk@JgREoVqPbvWMr&Cr9hyY;|rv zIZXd;&$QDTHqeQ*SUJMK%<VJ0z|y({f>znAR+Irm<#)P{LXu#zYCTHQ;hZk{5SI#dPcgP(B$ zuL#$KeB^xF@0M;;@_66QAAaN&8YbfMGl{i4A|#Z%tv;gk(J&`eg5FBd^ z6IW~rj16CQk~aq>cJVBkzsx#XiT+%AlS$<|GWYt%fO2?l4pWao4Bzg(Of&A2A0c*z z<3!)_y*T0ck2h!KM<*1=l(>uo8QzqK~2r;7}@(j zdz{MigS!ZEYEM3Emn4#BTnEK(wbOQ;JoZL7(K^IE37&U;Hr%7en<^tE0>?`F>U?1* zGl+W=XPTuOQQRyV96a{T?gZEu}A0?KT}k8uDGZ{E0J zRLN2cK(coi4M)Pi5A3vVS+81zuV)TW7JK)|@9HK&3mnPO))|vx?g*+}WXfwrw_wU@;)hAhwE;x##n2*Sc$me1y$_a&+gXqLotW-?VwQ zYH?teI30+Y1iQL>y8Gun-*VaH6nvvF8~<*i)D@d8nV_D}|pK=5lNNYXx?q zWkrPe#2JQ7?}0h`@X#goL2!n!ls0)o_Y*;@ecHmK3^F<^26QGhe9(P<hoRCH`=vKq@>T5&$A97S)8y- zd`R*3nn<1W{pLNMmZi++Zqa034NAIWefZMO=yht8c~oNlS|HQ9>Jp%b79tVdmXuT4-0U{^it_wy0XpE zaC?q8QwN&=2D^JZNY#)-Wv>S=X3JqEOm3i$WqylEPzrED3`weXxz0Y#-xwZm_R>tn zyArf6lF@vYK_;`eqxJamr-%=tO+=KLvqMU8P6f(XI(d|CSI`i$Wv%-nyoZ>o{c}gE zq8kzD$3g^-L~X8*DBD|lZrj9z)k8KQS)$2Ro$j9}S2UwQ;y`jh8af*j@amNgrTijw zyM`4VNPK)qCnKC1C3DXT4?W#>D{OgAYF?&T9fThmrQjG`cyyp@Z(GU8H}5ff8417| zy}c138z>+DoKE+74nj+&a_p2j;s6GprG&t%nCGXv07$!s+5u{E+HhA&DSJ8x^66*FPH3vP%wcQrg5e}< zBLXwVU2+`X#FxQ#$sVM3WGT3}wnJhk5ucx<@-LD?9InO~fn?a^_K$#*fR<>g?n|%K zej1zJdktjI^7{PtsYsg{B`H1+vRo!tbc%vx|7z9fXI)dC(27EXgU}=Q)_)|<7oKf6 z%ugDQSObCrllvv+pH-V&VCsKLL+`L0&87?o=%D>69o=SveKM#CMKzntzH~t-<+Qk! z_wpZMz;LG0rt!fl2>p>je8tARPtyN&jCc5G?`)?xphogkx;?Rh^!WF&vSiZZC)H-# zXdE~0)H6#((lxP?Hc9#$_4uV-7)Z_CL(&U`#Sldr*a*XqPxqD?^X%jrKVg{dAw2Y$ ztz_-F2XhKw`~lJ_B6WFM+bVtM?-e(=4D?+W5wMkss5pHyi<9&Agx3IK9dhyPOts<` z(COp9VFi%7khlxx7>O-*b!>i_<7xwys4o%Uc8&>6iR%^RK5*gwGZjGNPe{Dxlu;K7 zqrE~+ZFbs_UK6kv{#zG6Y}zsA6lK3-)oEeT-TM7(3Kz4>X9!)UmhYf09vyLjtauEw z`xKB$)c#}@{3CY*?&0^OnQ)D(D30CwmvnIll{qFLkTI(srL8C zq}f7@0J2*g9l9avD=5RT;7`VqJ(GYCOnB=(`|8ru{P-Ki>NSQ5bd^zNLc{GH-;=N0 z(|>}vAGU+=h`8+U1TGFZNS+m*b4^il@!tJWfB~bV!dt_4zQke_e>)wW+Pw*Cxe@!~ zZt~jw>i~|)1#jcX=`PT2sr#2?=dRe678M~mcLW;vSKw&D4P{t;B)3LRX#&t8GILx7{WaE--LJa^m?e$^)(-(s)*Tu z+nOD>3^9R%DRQEjeyvW&xb|4P&%|3A0E3M~)e%E>8T5J;)4$?TzAHxDnju!-Xy&F> zJ=>vyl;eIy?Z5CJc3e_p&dWheW-tP*Y4J+6d^GK);%oC*dZ{D;{JC{NVDh%t5bUi$ zCxO|J9L9i>GL2#VDUt>^b!!ov|3nP5AE2=M1tp?XO-PT)=6Yz_ots(i>4 z5?Jp{rUQ&UKe*W<9G-!u6yeS#DK%#qgfsb?f;!-K6wWDA<$&1BM1Ve`_Hu+LkZxT%OOka2R0g z#f(_%@ukClDV6-t*Zo}tr5Uekg#ov8k9 zHZ75+)P~|-z|*YNli1M<#j{ld=wMV9iG3k4g;u0!N`dg*j%Ey`8$)4O7I<{r%zkrW zIB90GJIUYk_j~`P1NW8ZuWpztVQnM_*zz^3>0xEs5ucK_I`^z3)|wfp__U%wGq!us zs{BP8`%a-YGdN2w6@<58x5Kiz+k5ap5Uhst+7EZc&FWbBu%cU)r z;0?=I`ge6AVKjqkr~Hx(3{NUo;*=6reN{a<1rPoBP#$gpPJ^QwZ|1|_-s^Auys^RE zxBuE|&0VhX9!}lJhfmTyX;#l{)=N1Zmdz9#~k0o&3jPzxnQF<7=ddo#m+!w5`e2CvFZ zzS!`5$Ma9Hrw$sS>TxvkS!f@&l%GQZnFo4JL@2a8WmoH+a919^tB5x=X2Ku7g^0e& znTj{EhvI*4U%}53wL37NsLuY|&W2bFXD2|xRL>T_mrjbj$dS#0nU1~4pKLX>Blj-2=ZoOs5U?n4<1%HR@dXkUYW zl4D>^-=)-yKIPDtHs5_FNxv6DJcFr=+rP}ZU=Q#7lqskEzA*)}wze+fCR(K9_!HG@ zh-ZkYLB?!K6gheH$l%WnFGA91`JhKRXkEGtURj~cWBk3N!_8f^28TTwcf-2Qsc zzj?Jp$@-KM_^QA+z0~3hx9EDO+Ml0(WVv#?!9F`0%=66yPn~+=pPx2-FmV(23z@fK5z9zQVS`T?Q+xQ;Z1VT`(Wbt78U&AM z8QlCs%Y;?KI5;D=shj8HYlcsK;*-Nu`vzkqyaZq;ju!oFdi`GOM1c7kM|g`-7sN4kjcB>> zIqr`hg4ZiSFIP64N8@XC7m8%PJj#A~92(byLWLm>eorG^gic$zN~OAAcs;M*TqvQQ zr#Ri&6iU^T4fxv#YP`w%d)B@`KWEp8z>&NwIH=#Fw|Zucgo?mT?IW%#!Rv)fGDg7% z;LHt)7FAY=4Dmv%%xMDh2Nq`DyQz1!dFGkJIJ=?OG;)>_h^O-5rtZm$7Ca;=J2-B^ zWS@*}iWSYcNM}#n6X5N35=?4>3RlojHZ29C+#b&Lz8v=)Qrd?6;U)lK+$G#60a@&wrq6O3Q&H}{kfwY85)(TeVvy7+ zy|Bnhivt8?Ej1B<%<${@?)kJ?kZ3Ccq-Wx>#0UYTv+4NyPf{yG91ty;H6!xs@m)qT z&bdlkF@e6z^*K!p@_!VHj9CXt_(rT`E1~g;aAZMA-k?(52n1IKp0Y?W%IJmua(U68 zN@c+(J>JT#j^9l?XQjtpRO(Xpz4@BsWbZ0-fH#Rl7Q>U5%yq~7o7kA%Ed~qzF|G#i z({He5tQG}!b%iRMTK%FJL!$<=mT6oh+rK)ceHN~N7_SI;pCkS!HVOYeElzR`n(HuK zmNPeGwnn_mNpB?a8hC415*D*fiq;JJqEBMVSmgU`hpmlfxjA^e8s!^+8_=U$;tMP& z0UfOgk=`wkQ2@SAN*R_2q-2xFmcLu1&DyRbRWlk8Pw466V)it4%YSJg6fi`MX=#kq z|93B~^CtItA4oigYoqqmwt4n_x;cFFaZskzlZ)6FDZS=1uZ1@V>FMp<0rJ@2B+qjW z2CqRmqC!2_k=@H$k&WJY&1(U;X9Y9S=$M*Mg)ySz0Oao^T`?_lma85uiZ=EmSiy!ZIhwR8Dg>`ErfC$NMDhc~Pp(S1n^ z&X14On5RZ)IR3ArS~Twza^a;0zWmd)i2*mYd_2tych z<)Kmk9T0v9I*tEOLp+^2FI9#+rcDI?cSvuiWus@I2uDZ+4zPYGd3EChuIUiikTl$$O}-px>$5QH&os+%M&*(b|6;gXk6MM6Sv(n|s9xFh zveM&01kNY*EiNV3Ig7Mav1tmulghX&bz^PpwcO+Vka-nUFcp(-&F1q1P~g!^qB4*C zB5D{B4glX5-XDcG$qyZbvyVR{1KGdD?6?A=gctim?t*b>dLQ0~gAs1K z@_F^3q_^D@_2N!-tWnk;dt-kjf;$YLL)$@8rM74#TdPhnG{}Vf6f_z4WAAkAi`mIhTK19XXaK=zqM;NT0ZW?m*57>pTlTevgay!w_s1MLK6( za(_ygpCS3m9JQ;>AE#jT;bENtDT916nR%seunFv!aMHW#c-bM~Bc-Cx+)jxhOpzR$ zh}B&2L5b;1?w-Y%!7Y<>QU!PlKi}3BCGHGfD&ldrG@#&Bv&|{oQg+(nZdABY`QKhU zeg`-pr_{!fa&u>GC6g8o`^boZxOLWQJg?hR=Z1`Y65o~=KhIjT<&0v9^D<~kc{5!& zr@^)p^-(f^KXvw(uyJ~)_=_n4LG!949$eX2)dbxB@#(awfAw5lOSsc%itONc5|&>*uIObtjbs%4QjqFGFu1U=%nVwX)zbEC_Agvrj(Bb z3jv2&?V(ib|32S>R8KtDB;qe$+C2JWw&*0OO}>Z11T6f9;N748Kdt0PE_aGw00l(Kux^6Inz2x)sca?6aMl65C>DUwmS{! z?s1^+`U=%sQ?&m!hI;=%J4(q{jx@E;V0VJlmRTpJ<{Euuo^xH@OR3GAYa?A(2Y7}k z!qjx5P!oA){Hx-vt`SMBqhFQ?%xZNa71hZyaon(-=uh-`>d~aSKA?@$3xffk4yo-R zPca+8sBI${J|mzF{CaPwfqhK_nEQ_^HGb3FQq(JE|;7c5YMAKiLD{UkQg&3G0S_+3^uC_VsY4FQXl;kxd4n0mxPFxv2I3kBDVt6GrBE zoa1furAttP7!kmAcu6vd#dEMGe)k?cCrgoisZXE-zEu+sBPx@97Qs1r{h?cq)^1U7Jprh$8y!9udQEQZCxDVu9W^A+0igZ6U~9QUIe@ zI=s23^>Ab>;BZ&O5K-0Ro;`&=%T-e2f?T&VqD!wEtm!r)C~2e0dc8p<{-D5^>rRq;G#ir1LEzwz(c6O3fO--;5x|{cMG3D5Ph2NYqn4-G(;(#}3kD27L2-mTVV(M34 z1|g7cx=)=T+&;AJA?-IJ#@@nZS_3+O^T{;gZwAOsj;SKCDY-5pbse3ORfL`@aSZJc z1tuZ#ebkMxzNp*oZKMO|!-4N=-UBhEXj`yot3b3m zE5y`qJ)H4#*tGS+X0%b3&5~mCej1=~$V*ZsEQYnLV?=^Mncm+wKs;;ANSJVXSXJt8 zbIA3@%^Fsb=7j+{;MVDX4l7@V9^ z+$e+mV-NJErfqt4kH>k9g_LD$o9`$?9qs&x>UV#EoYdZkg7*o~LEdCD$f61@UP~j& zD9wOFJPR9}ZZ^-qa+X2N_+E|{pbg`4JmQ&qCFXZ!Ca!r`I$Ho&o8IJl8kW)TH%DKPXy&_85-o@0KAflJ4s7Ffn^U(=OJ)dZ_MU_A# zV;E}pFQ9j~iy;-cItCCNnxTb8-Glw`9n~I>=@+DYKSmVy4X&Y^D0U&TK610u5Vg^o zfHDTB>*&*%*7SVD?_3?oagXchRM1byAdg*yQI3Ney61(7f@iN?HEIdu2vpXw;x{-|LDj|_s6XN;TVqtD%q(vW2 zz`glY6PLs_-U=m)IwJ*nKZrgl5#2VHwSBR<*bJ>j(>$QpNDz{I^= zHr2mlW4bz>Fr9CA^vO*~kmJ=hR@VkYaKJOt{~XG`dph$__Ad~L%3AP{kW+_hAv@Vv zta~Pe{cO$!Zh^FXO$G+6y4Gb`?oT`PwT|!+RjR1s|A*UW`TiDAU~;1id_j8E8O?g) zR8X6(Z;&Y{Gx~#fi@3C~6ppFHC~v$U00Vf_q&OFx*ZJvqr*_$8TOLGvWGNkS@i|^a zZrKw%)x`;PzRE^{G!j$|_*3D4Cm1S7X*kqu3;lj#(JVtMcu|Nu}eeOEgi&mrl*M*)_>8HVM6>##X$YSaoJPRPBv+HkHEkvnm4Wp>%i-GJXZ9E)z3S}c)-FEhRXOZz8>aXg zsQr&ai`EnP|33*Zid%wOZ;4Ufcqd1(jejT2fu#Q)**GD=Yh5+SgGZxWMyFLkj<*!M zt2-lX=?KC;PWzk#Y@B~Xo2WvuG;g+%I2>k^lAjA_-O%Xad_F{G<#e=f$n^8!A(Otm z*gLYf6UTUfO%z-MN4Taq7v4dZ&?B&>r?=$hC79*FmNdXB$63Ew5J$$mLTi8Iq;R&m1C z!0i*#y5~l8=SEEgB=X<+wm~E{A|^GfTu%8bb|NVqZ$#Ouc70XArH1doATM?KJhz3a*iSk9?pMa9MAe_K z$niWz=&VQr*TGGp9i9*C-cpnY_RDqsBjS7D%E1hMJe}M4v$(7VskdBZD0Lgj94r!l zAo}T^MG2@0qR;IRfmGSl5$_H$jb8@|!3Ls+-7TH_EZLy#AFU>$pL}aDeOnmnQk97K zX6JZ)t`5izZY>!FO4Pix>G$jEi36VAU{?}Z?@)aIe{)h#%8=R!&lsoP7^|DE8Z zqNZ(RD%t0Eo)$8rat7K#Qev?^D1mi*-Nxd`;1nB|Eq#)%oE&g(hd$5ejf{dD2dtk% zC5Kum&O_^32X#NHk@0AnK@qH>j>2u6@EJZf*i*rGkPdk79Mjq}wBB_>*F>eN_Cp@7 zgA2fC*flMtY7xnHz-t5~B^S5S(yalcpBrFjevY}5G$Q2vvymUfcWG(FFjK*=7(KSi z(cGKR&D1=wc{@8?JLN2P0MknWDu< z_n`isp!%;J`}HFX7Fbu*OjAhvZjC5%Y&y6;+OLfk8dY-XXG1cqL-gC(a9k&PNP|~H zRY4o^ne4g0XRGz&m3Z}PEpm_e?@Qa!>GG{0cP~#5H@H73~`T2IN>Mpn?Hgu)!lGd zZ6Leqb-dq^hNg8p)KYUTwG1;-+#MjDY4`WPAX=8s^Uk>LZd1hrjL+|a=Ps9tSWS}Z z6oeAq$9cXcYAypsJLrIu9|7B37tJO<6ASbT2V%^M7}CDaBLbH#RF5R6{m5-pw-Ods z)Lw^v_I8GIEyKU9h0K8|bWws-?nEEI_q9zs)As4yhP~TOyV^1f$3Pv&rA9h@6PNYP zdZb}H8s&{ka@cfDL5pK)!q>z`*cjI5`MUU!ZjB@8fWy{G9U71fr6yx-0jB5tkRrf3 zIG@_I|H(F|*`%NUD1e0{tm!g$n&kzD=U_WT<$iu|5o)ESM*Z|c}{DoTy?^BXx_|BH1>JmG6%WAt_B`+M{t9WWhm6gD3_{amj_DOlk{ zk$$TQA=+Dt9>eD#?(qN?`FS3NI%a6?Yf;x6kYKIuq?9V|*O6`pr(251xF7moa~b>h z8+vx-_pb%Vd=JVRKjFA)JvgySgW&a6I`f1uQ$VAy%P4%7jrMb2pKXe|cHJTA@`-f7 z1RbzH2RtN42do*+U#7zuMYaSB<=%KHnj#`<0;k^#5ywoIi~ef{JGBan`bs)l$GZ7u zqTZiE#-lMDb04T-O-<)MbGI7o=O%`OZ-{?u0tWkDcs7%?+>~t-j;)Ol?<&6K#rTc$UL= z&e1)z2BAzzYrYJ{H5Iv^ZnURqj=+;HHWtg#%2sAX?yvdm|2|oiO6Y*;fQbThz?bCc zfYV4%1|2~zg|lqxv;c|4ZU;EwY&yDI8)TwG(!ovNDO6hq$0SKi>x+#`U4V;jJ!-8R z%3e&PeaPbxS;J6^4^8L3^B=0UfFwaEXd z3OZTG=y4N4EK=9)&=3{M1{zMR3~BDQ(Au_|R4fv0Pj5IZ{(mB>bQH5luR37m3zyIA zFGhJoIs1!zWE-BrIp)pSJUU>44mh4Z;09Ruf+Swe1CzGY5a>SwwdQ%~$-F`@RN}s@ z1^r~Z?>%3npG(jx@(a_?jzpl>jqBDLLK9((i+kQk;g%xWz9s$N=|T%2NV30;SjK>~ z^AshODnAu%RH-bavPO6YKl0FPtklZnJm#(*+0W`Zd>5a~tC_XJ0l!5DOhoapb@lLa z7PGZFv!_GPORqXlcby#j za_}si*4AaTx)e<6?Xi(=gPL|LZ0I&Z z+$5cc1LK7d$43S5%+mqq!Nb-GyY&^t>VR<_nu3H=7vOPSE}Uu#k?KzwcGlA&c_@!* zf1AbgHAKfUt$9~EdBYcyD9zVPI2JuKIgH<$Z4jkYdE=BUfB$a!pYxLM^>9xYLIYa$ zIbV&st9qv=c#PueA!FJ_9D5^((YVbre1>#)Nj@(81tdlJ7s_94>41~{uvLM1`$zy< zwQ-6p+?JyQ?hpct_zc0gRgQA^EPcAwSU_d^uwVM0(}2wMb9<0|@+DnXk7#{5#473{ z_)Ya2ZyFlPE#c(C_&r5d+E_TTcGx8Sm8D;|zu|yy45=N?DRr*afwVY|)zGua6c9v(E2{W#!FgacM7 z;p-g)JNg+8_!gv?qaGP~w4t2U3q8h8Y3rv!DlCMxFkibAp+VOM7!ua$?y{W#PRO?VZKL)L%qPA7l( zf{sET)9h_=j=fRu3{yv2ZBRBf*v|t)+-T0hW-BH7d=JR-PCbeZpuDe7M6fY2GZPcx zd{Uxs_;eS-CfCaQS^(?ddTDp#CzEJuQ#nks*opj1!CZ`;dA_7IU-YXp8;}EDi3nN^ z#J&pZMr{ouPB~JI-x$9$2cKzV5VnRIPT~?H{oKLkyOO*xY-hvPO`(VR&k&!DR}4j{ zrpTCt<$zV0;ezz4Gke=Wf^w#@ar{(Y7h&BU`oP1=5H4DV)!o5bw{Mb~QpQ^y_AMz82v4EB@aa-deXD$q4=Z(NV0 zJKlMChj)~ME=1uykj)?t@P2~ZT}I?mhr(j`Ru%`m7`4hIi?U0)7fEwxHaCQqqA|~x?Moe%>7iP$^{E3Z7 zCqZOWy}P%2aKi7TSDgdg7u>Rcnh zNL9iLTV$(u2`8}u{{Bvg5=_Mcw^iC^B8=fw)mX(pjB}fUUAd{2G}VJM7=>DEONZm{ z3Wqo|;QR9@`kKw*0LS8d>uhT3&!mm{z4+Wl*bKV)-E%~3J%vr$ZxPkawG%!c5pSkz zUXCc{#gJQ%rl8k+Ta+^n13UaG)S)H&xeZd`|3WIdSurg1>AxpN%}l8Corw+Tmf(rd zITe``ZiQOWml3lx-H?3x<=}uTA)K)k3-m)+topccS|!+t9*jugW2Eb9ja}L-Q@}`G+|FKO))?)S{+a#l_okeY-Yc z#M3!gV>U_W!AxZ$x(Br*QdB=DK)hqJuD9iIK2_N${Gx?rS2%#T5iKtbIps9ah)&#+ zMC#$_+Z)bRr9-bodVLg}`H3>UF4&+!knd(A@|5SWyTibNm8eHo+N7Y@Lsr1)ch58W zusix-x)+N5jLjB%L!E<*2NIIqTM6BOeW>Y7WO`sLQ6qXNWy(nZ3hXwJV ziW=C)Ake%4Wvc}-oZGdAQ|#m+{XD}UK9z!alWETImpEs69fk$4*%)SsjI>3|my?{{ z<%W<+cd&%rIMH_{LlpJQ+gy|g3elE9FoV&ryR6>lF-N^GAY0Ll_lxkld)yp5$f(nN zwTQxf)kqALq?2$CV^NE&k1?%2z)0O50NF~npWi`zwz)&?bde3)`m8Jt+ieQ{V4L)_ z(>Y8x6rxJO;&xjvy)?b*c*pigw-;mj`D>8BxKKH-0V-4TeN1Y8)4+RBC88AX2;t`e zE=A;VZVb-reOd4HaqKBXS-%F5b`471YXif1UE_iMJSDyA?BJ$INoONI?P~nqIXU(P ze++3q`JGoeM6tD~UCX}I)?f>}C9Rmwdw-7`iIpHs-G;W09}~v<_hNVA`f-tRUIY53 z!a_`(!l8Nl?j>owpo2XbK0S-Y`#N87R1ON0>(uYtuhidKtJnC6trF;{}w z9e1s83Kj$DJom-G2rqTig1Yv6oMSCE8^1&QTmEe$5TR;wNIxHLlYVwOhuOw7 zp)xoTxA){c8`ZF%JY-|uK_;ZcupTkj2mRRdb{gD7#`iy}9^jP_{#i#84SA1I19b-jCzUCAM7-UO;NNWotw)8EuzGmVopQfZls@AM`Y$_IQKPr z%8gMbs6?dLP?Fa~&GoGuYvFg7Ux2i@y8jp(%uSpc_->U`9_GdbX) z8S2-{;XaDm^LiLN`9{o{l-FfRfpC298-Rfv_I;Tsc@YE=MNN3$2-48i=y5H{F$yk2 zDm$*8QKj*Glo2`ZgL;wQ?>pIPPN1v?|0mUJpQv_(>zJT?J*S zu5kE+(SlVU&^a_RcmQuZd}akiJ1WKJy15YTkSy+XjP|2jJ>J85Fs2(C8G#`g^t$Vj zR&SkQW77jIOi?~RyX_0Auej%M!pZE2sg%L&5o!`(Ha`pT~ukPyT%w+D+f>b8+% zjYw(_kiv4+lpbV!@3yF!jsf#HDAL&sn{(*z;vD89f-Mv2=l&l0o`>4BJbmr)ye8|^ zxj6O%Y(_*%2mHN`XpGP4p(RlccULw0yVF@_RTCMrUYF%qc5!QG>&+=aMD>7s5mLyN z`FUzU4p>LQQPzULvPnN*5;Gf#F66J zH=-2Jt9~UZGaPqsq_*QoKko(+iCibEoN9dU&KOY_^f{>X^YO-S?e5~Y{si;9Ow23Z z(*yhYN`t)B)?waoCI@Dh{&!+-E5`Abg1KFe&qc`}Sd0u4r40SO=lvogV11?KWeFnc zhkVP39NBO<&7h9EF&MC6w^4m#!yG<80AYZiZStD-46-sA&($stDVqO2K#pP0QyL4k;0bWtC-J^gmfNH*rcDIcag&Vx6Ltr z=|MWOr{OsNh7BxVb{_o;~IH>O11vdBX$B2~FYM`371sC<4vRwa(XxPRz;0EyG z6OljYYB9b)qWPzn1XX7dFX{Fnwv^0(Scd#fH^LQe1R2h+uZC>r4qVW4dbc~csPhM9 z$4gZ@f{5}#S5d;n5k2USXhv;-$6tj(4bwt6-tUd{r*c9U2CystT}MZP{d_`54&r;A z`WJSALkhhfso&MaXBwlXTj#QFet^x)<$ zd~<)Vx4|-hmkGLPw; zk&G}>=uaBI)yYNLz7DNMO^jpw%4G`oZJU|NUPmad1arIGxc+W>z%LNdcM9h`D&+L@ zO6W{)&xJgYcly|f>c7KvDQU@RHBzDa0om~irsPywzVr-&j zZZ8S(xn7R8IJJewykntp!QU?i(r<((m1XZ2f8+X#p};j(|XsWAj&xXXy- z9iwim#W%dBpC7jo-+0=^XU{j%&#Qr?7j*m4G(2b9k-!u9JG^@UGOunkMEkP-OT_)a zx5J#+C>-#QNacooK9?bl>{mL|WDf5TK>B$RcHHuNH}No6*Xuc$OzXZb*w2fRZkF25 z+j-c~TxV=tI{RpIQw!sX#FwfhZ!(%Q9RLu?btIu3+KGe+K4Gjl;m5=oElQ!vRH*=sbG_G%Z zMErF=H;P~5{-_?}wUOFp#2fBAtJ;r7Mz|I&0S6<^`$|Opu&V{?_6X(qB*|y1upfAX zg?kbrTc;>$9wJ5g9b36b-8$J)DDISs?_C$74MAUDJE4#5U9g`QgZ(Vi&wZf@?Ph4j zEr!$Y=z~l%2R+g9*wg68J`QtRKY~Vdjq$l{jr8;39#ZFv(KG!xQlNc725QMEIgzHH zj|;e+kXq10A|Lh!sIx7L_+i(z&|{+_KL{qTVtb#1v~*1dXFT2*Jy?O6z4ZY&#oa`E zObYEWVf0`BW@tZ`p&iMILHsG4j{cl-ro)hebu%>KC>!l(uR7!7^aLg2{-ux(ZwWHl z22dqC5oyFr3{=R{OH&5a_OG?K~g#$p%sxi> z{e_W!en~{J`Wzl`$$mD|&%<42NIo;_=6c@n5|8QUXPw&AjEzMVgjkvi=}G(N09+d^=)6u8+|Hk3iHBc?*Cb7q!7e zqLYz&9Iexz#@NGLwWgONJu4miT9&;R!gZaG6sma+-?&(SWQ1M{=J#Be8;rXQ@r}+t zw0!4E@r?lPaf&S_d9^P6E1({JzxKY&01-r3sn}CO5`OUNlunG3)FK>B8WB`0!--VD zAqU|U_Y@IY3bocS7MqDiD^3b5cc(P+$A}gs^ZRm_C5kEd4Kw{b#$%S?>mX)E6M4BV zsi5G&nAfCQJoC-zgo!X7@)Xj9V`F~MwLPpAmlArt6nX330MgGt8p8paqGlP!0`d;( z`;utqrpNm~#{ArdNYT3)VDPyO`}q=&!zJb+6Twt)aYny81VD5QN}OW{asY_1QqNDt zzAc}5ns({REZ35OLRhD}NL_bEsL|_kj1F(6k6XJZeotw0US1bxg z2C!LCB(H-E2eqY3=jPQW>-H`|L^{&NJy-Sko`yd4B{+6Bl8nb1Z9}_xPz|d`+I$OY zDHrY9)Xn9TtEj9RYb7^?L?91%DWbXY33I-Gp-*(}1UlgM0klNaqYm0x5P}+G(-8*y z`AwTCL~hREMuVZ<2(3D`F3EHo8&S7gDM598W~0td_G$5E=Jr4vTawnLv1oOp@CuRB z1Fi+>UrkWz*&NTePg2r7M*4Y708a56z3SNIGeg?HCuuRJtaQZ*X1nM zTF3@g8md(2bnVH7dqJ$y8kXAi^E!QYPmSY$Bf|sZ9JrBz3NO~Vi3L7cPYLx&~TvJfz0J5 zz5cin-FLwpWv9>1gz69@i{f^`YlkF86Qr#~8L26sRl>`|-3axyWqIIy^_<|16BGqE z!EpCnn3P9~`e+KhveQDib~C`NPKgxyfi5D^PqlocIPc3`3WUtGNk8{E|Z+E;q+G%4l6mA)V;NHBgFMU0L@ndI!8ZbieaqU8)7!zXzXL z!fpEWlRQa1o5SFHBq$1QjTRk0_1~u<_AiGoJP|@IemN%k$W-vUE{H7)Fs{#3BO32A z{rsFEF5+h1@EoR|xjum)xI5I(@?~vWhEbSg>=TZu=R5NxwPTl_JU$5x@WQd|icxl6 z$r~q$p4=3i;=~YCduD?DTq*=a_IH_n-pq#m>}G!M97MbFvpZV$v%}}^2&u7mbUhW;(k1)( zCS&wqbEKc${O%Y-`?&(_?plW##oJKWfYC2DwR7@tZK}aLvId`F!tUYT01m7!j~#F| zl#!AY1#jn36Z$zF>51CvVNToOo<_g2b6^r#YCbM+ASu9 zQ2(6^qL@rS@9p9l_#6TV@*FSokbXAV&pS9c?~zGixD&BQzy0!%9BSI{h~jx;`C$Jq z3OPN`Q_rRX&521NJmC8Fa5>>;rmn3Dr+7pN8>2dm3v6Yik#BOTJl4|q+?y_cSEZ8I zGtTc+ht0(~S|o%h>UFABo=3kj75y4#R}<1Xd{H3ISCfl=lqymQxW*}HR{!d7?5}89 zO`(4E5MJ7#7l8|m*}q*~%vAjoDgF{e`+1DZ^z(B@`nfYQFPT2`t&X<2SQYger8a7N zg8p3-Jx2g5%Fh1LS$hES}@B(|ltKsYtGZkSET6eUk8>l6c3o1| zt^m3-?Ce9lcf8qr(Es1AbgAQ;Pf_p;IEk`^UF)70bRmy-h)%bPh3AJ57M}SA`&sGg z?&(5&WjHjW<#}A-GX1;=y}okZ@a9?4&oiKU#pt)V*QGQ%2$mL@^PPuxhtD_hzlXhd z4%)>@W*okfBo?ix?`{Ag3@zBcvk>1X1!-%32-oBtgXq-=nF6OiR&~MyBOrA77(BaI{mx=%3L!2yoY5=96qOx zWh=u0pXXA1V+kfzl^H4cQCWU#Yo_OlKKk9S07N(JY$Dz{-e}?<4xv?n5+0T!&03W} zCtM0gm=ck3e?*-@Uk5e7ypwQ*4KT6ENoV>gBmLY4t!r*uo_ZPD)<)1v3QjBN7FW}vMnA9nvYdyua5V*k@r7Hv`AIBdA)ASEoL z-0br7`}`Q} z;w0aL6#4^yfb&}(VEVbQNI&bjs!j{tw>IhL!7evx%ONEx=M8U=MLFv=4lXbH^=DU* z_WmkB8?C3c5k#gJpbR~j%M0)YQ0*|+>Ipyzz6Yg9!yW)4$`6PR=c1;%(g!p2UEOCa zB8{6}kX&B0Nk2dB@;B!gid$)nR?I}!x{TzMqF#Gi;W@1>z_n)y)*e3JgkuEO7tGi> zA*Dj&R-oA{AJRagETB@w+k=UGgJn-z_7Qf}VX z*5bdfK&^Zs?)xl=Yz#8wBezFX>?Y%&bcOg2&7HYh3_xj)J3By3?29{;4Q> z6NBirDIBDdl6)M0qljul!CF2Rwcch%`uPl(i{uJCKXUlOy4Wz-{r%IrKba^x=UakJ z@#F$^zADS~M3Pz|iTrvw8loxwV5A8ZD|Q4H(^+}gJFM!Sx9aGuh$D`MZWjfi&h@wl z_Vb3uz=uP#)ThrRIi+aV7}X;9Nr4D%EXSItvQpnca`Itpl2g3nL=Z1b1vHI!6;blr zc;F_Y-jVc|X{u$%u_oi`zPn97*CDcQX7o1Snc?rhW_qq@pQ)yJE|%n(qpbs{WGb?Q zUqa*kMy!c@BRkccj+clcCE6OZTMt7T^GUFde?~NvL}JA^dfqJIDid<5%k~geqBz$W zzOYu7!3__yH?k<7sWNa3lFoXr2B+e82*tb$F2fLz3KeH&ZK?J~5OKd;4Is7HA1~Rh zn20kHgoNb@d9fa|tUX^E$NQ-WR1bLQckCt7&lO;F+X;TmwCp5a|Dt@R`XYKvlCxcb z4Z%+cAaqoFI-cR)m||4HXO_sf-EK$JLJq|s_eZds937+V+&t8^8}o(AdjGaU-QNw# z?QOJ_=+CND^j#SQKfVG%j2F7No~N#8ajT7yjH$Q>0V1LzE*@jqZb%`wPRdMi zf(Y^@Orid>9<-281i>ec+VMoNhglD{Uo1-yr7m?@8#`C1U#V-_Ed%>mExz|@rZiDB z&r>Tf?&DCq`qQ#!EV~YwxdSm~vs;p;rV&IOi~QPPL&mWqR;lrA`2yPBn{4utcH$db z80qKzGYEdDzU8BN&5P<;N<&F)W=!w-Eok2>W4)FjR((0uV!dxnFQ>(ee*Oz%3LA;^b0-j5>TM=e-M}cMsGgIpiG1vnp~rJ9`pEg<7sqqd0|GvAsp#EXHm>3+{*eQAXgJ=B9r(RpI{LbmktbhLW#VK4k=s5u6K#5H6=sZ||8raeJ; zU;Nds_462=u$l<9O)=Ifh4{wJF0a)rkhMxg`uQL%fM);Y`{=La!(Q0WPh$-4m>1(a zd?$r=2*TeBtXc&IST-%jX8ljlt6YrNe`CvryS!E}Th>{OE=(hXvC?Kp!^tFH75uZK zBF@8czvFVgpF)w1&l*AGkir@k<4dgSm9L;tzbIa0oM$O?z1>c_I>$&qceF`A*Fq($ z1sBAEe|C0887IzRD+f@;iXd`(nU$>%9qBkVqg9V^mtt_jg8h7}2h!S>;(nE4@tbe_ z-8A%p^5HJr=OzW&($O(%I$y<327(CA3GWfJaQjM3?OCmmov^sx8(~bKn``q5##2fb z`yEE~ycGY&KFq>)zMJB?iE|w33zn_J)H6W@o7Ng+6IDg+gddQtHU)aEE5IDB^}&AD zZ6~9}BCNjW3rGO+fi7I<+Y^dubuo_^-HFbZAcB9%+hAfz+~dj~w5&rRwkV0Z`{gdL z$8@csEf(Sf1@l!o;R%*)&w5-zJKuG%$%@nY&eYoCe9j0W_!qqdtj5n0;DqlgL?DE+%Zgx~#(b;G*`VbM0s>4r0v@su}2b+k`bVdKPih3VHO|YDcVnM!( zrI=v$am>#5FFcb28T}GOxG%j*Wqz9g2P|vCs}-;lPN5&W0z($sqt@3SLK~gM-be7iJ5eNe4_2;W~sPVie}c6bau?f)P<02czHfZn|( zXz`hv;P-C~xCy*z1QEg_uxrd}SZ=HiN&pv#=2qM^&*dSxRY`)qbmYsuv_q@SG zQr@w3QSY6T=kJ#q0&+4lO#~6#;-JRk-BbY1SB_1)pF_im1iV|7=$(Bx59_^|>>53v znjnbW7YRjRdZBv26Kgdm9A7mBSR{Qg$~nyR-A z!;~T3a3YHLNlmEQ*s^B|AmwalA7D}wZ#Y2&uwd+y2P!L=)z7vlS*Bq{syJtBzQ7mNxFegCV#trn@kvsu3)LW+9V0QX=I_h0kB=6BV`u!mY!v6xIgfWl^=7l%Kr-XQKoB|X-gmIA&A@@@L_1K|Eow%)rM!IcGn$!n|weByZ0r9bn{La zPQM5nxng{8Mp)L1?+QWqUr^L8{)!@YzS64EvUCRKv(gb0(($Sc$=a6Pg$-M6k-slW zlCmE;6a?XaVOb+jCm@BHmR=6Qc{OXe1&Jm_|Bh7QJ-NiPDRjK6Y*>$G*@hteFD{!P zmBa<*pN?3`2EK#(a0}G^_}~lTDCKKr>Uq_c#c=G zdXKSeI3r$H_Ac5O`4AC=#|5WNflWY)(h)yl*BB7Zd+hXW3VlU1-^kAns6EE1A!HInvB~TXrkj&iD-HX6Kc=U6#|o|X-;>}tzCvTR`iI9^q2{SnVnT|%7i zA(o~291}!vk?`daIbjtcPK1+HM5YHYnMk#O>}lCLAjqh;pA`i(f$4Y4p2ucqUq~A= zg2?T;ZE4vSmK|={wP=l*l@Lc2#0G8(xElR%j#r>B{e5g!mqdI_W8i$N_}mgiu!~u{ zfSqtHzUv1pjE`9MZ_7>sdvYtJXPcYECDiU#v+M$pWeO>MSdDtInoE2mHXd6e6TzBs z_GeT@U0p5P)v~i72JsoPCv^q%jHq|#Qw&Q#7nV&8F>^Eq zX=gfLf(S7ZzRaUSmX>ZDhsE$&uuKPokhCKfjP_m#8$vMTAnf=Hv4&TNS+YtJ^ch4r zhaswKUCfMXasRvE89E-l>ndw98qZvz>MTEi4QkeMh1is+Yt#psm9y~vy^7TJ`~Ws5 zjC=_qqD8L_(q1#OM77_ckN9o~)gNWqE|zVMI%kU@oQG&1QQv!e3@Nv$O{Lr?p|wdd zc5gtGqco}4g>Xc{MaiN+w-v}!DnoJ(p0Bqo`yS6-p$lcG+1zJB;?eVc7T2)_N*vo- zb}%>y>RP^sjk1^0&M(*mv*t?>5iN$Tkd7XQ=|GPm)peX@{ju9#2T^UPyJDlAhQ}@Po4tCwaOHXwCr=iHoplG^bk1f6H(h!{j&RDA@1*B2jcIQ4Cf%r&PUJl zHE^y^Al3OLHV8k%$xcI5%Lzk&+gs2TZxWZJqab)yfCJJUnTgG)azzkCrbVLx*u#$$ zP`gr?4!Rn|rA2Ura}a$ghdmjsVSk|Pkj*CI@24R>J`er2s=aJE2O7AtY2aCOXL^?) zh>uta06YO9v__U-6u3SN-GlC!r*#U2gepxR1w4}8f9nG3AK@f%P zfK`g`WiI%%gxQZEUhzdG2%=yeup(pK!3Cd|Fnb$=NBE)=1W~XKSlPKnpczfeFMCk8 z9T5aU6tWXO6ax*o@Y9lDi_w0>`Y%Bc1@C}&gvKxze_9f30wxmi1tkcg;2p3U0Pq19 zep*6oxMjWRfC+*q1_!LBygbE)pOz4N0YeV>f)WH#@D5nn)f~`3OMt1#PuH^kOAtgc zIpLEaen2Nci(CKscsgH#Ad1NW?}35hbON+EZIoqw>3|7>C?*HI5eCB33DDv+MF#9j z2TTw|F*#s0BluN109uSz4*?F29wZ2&s2s4e+byf1BcR1(e_D1Poi9NU#pr~U`p`T& z0$NNq&awmOfC+*qMh83)Dnv<@Cw$McM?ln}cC|N~YuWpld^I$ImTd{C!b>cB81sxLqlc8v_P?8xUo3kAWrZ^=+sm?^mUXx6 zIQ;kegdMlq95kf^CJ3S!9q{UKW=UC)ms|FdWjkBeT+=^eacxR!ug>d~n{Cr_({Cn% zssY<+dsA;_xKx+2zRJkHu=}s+y34%<+kQKpD$LdJ3YEI<*Sp}(p-t^VQ#>P_82NyK zbdzy<RDIpOHX)q-Vj1z!R$n*KITHuI*y>Ac3|bMA{~?o!{c z%vx{!>MOfK_C?|AD|=Hz^X5qWb4wNW+nAHZv?vjHeI-la6}|}XAiciGt`C|n1(xTV zk6MO0yx+xp>4tw)*zEMTmRT`R{HB$6yyRuUF_Ff; zC;P~`m2wlW+jjV^J`~eZ*ca-t_u4bhS3eDxTy(XYI*u%|a8F$w`K^X&=7F-6H@_&ZUOoYIU6* zI2pWa2=)E2m1EJarPJ;vM4Ic`N?c{qkvUt&IiF#v(&6Mo=eTafNS`$bcNTPW5#aUT zl4B3tbfM67mvMSS>TVO4NmtV|bhBh!Q>H()ncXNGJNv?w&#}|Cc3YWhuk+WuadrEH zN)BzQe-}5Ef)eZk;djp=qN)6`G=)N|<1 zcfE7nGbiMV?d!ywwE|~3R+a6$u<5nO0VR$mz8~+8mQ@=B|39D<`QG^MpV_I+j+?x$ zC~42}*8XCz?r7+}^zeGmTVI8)ui{*yQ-5w@euS-e_X6vGDpSt<_yo-D9O`QpUEO*h zK(C~E40>AyI0&h5$33t9Hi`gB{= zwzx?~N-T~7o~1#)2D&dTUAA&>eqp|vwM@Wnvi|dM#*?R74Xy4=uGl(F?eDIKvrJpR zZ3#Bn!!mP9_`z3svTYZ*96|TMKPqzBs+DRjlxy?Vu!$%@UElWi@uIlA5wTMesEm$johcV z$zr{(l8Dm8Z$it&pS~^)F8#u-Bi(#)LRQ)5n+Y?gU-~G-;@I-IBlOBPw|&NuM_25- z>>$*2>SrIT@NSj!=K80~jz^pbE7=9~S4+72CC;~IfhR(=-0S6jCvY|?a9BP3Z}m`C z=eHsg`x-Xgl7*jDIhqt+H2iKD477YT!O7aYn8IKr)Hd4H#X#>xf2 zLZL~aBUHA4^H)(l@4}}q*9cEHEB~U*)Vz|! ZlFa-(2E&$!H}8S644$rjF6*2UngC=bb+P~e literal 0 HcmV?d00001 diff --git a/package.json b/package.json index f62fa6f..4a4e726 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "author": "AJ Keller (www.openbci.com)", "license": "MIT", "dependencies": { - "openbci-cyton": "^1.0.5", + "openbci-cyton": "^1.0.7", "openbci-ganglion": "^1.0.0", - "openbci-utilities": "^0.2.5", - "openbci-wifi": "^0.2.2" + "openbci-utilities": "^0.3.1", + "openbci-wifi": "^0.3.1" }, "directories": { "test": "test" From 52120bf35373b6ffc5767b445fd0d2195444699f Mon Sep 17 00:00:00 2001 From: gerrievanzyl Date: Mon, 20 Nov 2017 12:24:07 -0500 Subject: [PATCH 41/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9e0373..4e23f62 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Using this repo provides a building block for developing with NodeJS. The goal f ## Who are we? -The founder of the OpenBCI NodeJS SDK is [AJ Keller][link_aj_keller]. If we look back in time, we see this library took shape when the [Cyton][link_shop_cyton] board was the only [OpenBCI][link_openbci] board around. Then the [Ganglion][link_shop_ganglion] came around which required it's own [nodejs libary][link_nodejs_ganglion]! When the [wifi shield][link_shop_wifi] was in development, AJ created the [wifi nodejs driver][link_nodejs_wifi] which was had a lot of overlap with Cyton and Ganglion nodejs drivers. Therefore AJ pulled out the common code between all three NodeJS modules and created the [nodejs utilities][link_node_utilities] which as of today is also available to use in the browser. +The founder of the OpenBCI NodeJS SDK is [AJ Keller][link_aj_keller]. If we look back in time, we see this library took shape when the [Cyton][link_shop_cyton] board was the only [OpenBCI][link_openbci] board around. Then the [Ganglion][link_shop_ganglion] came around which required it's own [nodejs libary][link_nodejs_ganglion]! When the [wifi shield][link_shop_wifi_shield] was in development, AJ created the [wifi nodejs driver][link_nodejs_wifi] which was had a lot of overlap with Cyton and Ganglion nodejs drivers. Therefore AJ pulled out the common code between all three NodeJS modules and created the [nodejs utilities][link_nodejs_utilities] which as of today is also available to use in the browser. The contributors to these repos are people using NodeJS mainly for their data acquisition purposes. For example, the entire OpenBCI GUI is dependent on the NodeJS ecosystem to provide cross platform support. From 7601687c20cbd9f29dea94da998629d9b4f06d9f Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Wed, 22 Nov 2017 12:22:38 -0500 Subject: [PATCH 42/42] FIX: python example to add wifi and fix cyton over serial --- examples/python/index.js | 214 ++++++++++++++++++++++++----------- examples/python/package.json | 6 +- 2 files changed, 154 insertions(+), 66 deletions(-) diff --git a/examples/python/index.js b/examples/python/index.js index 2ae7750..a96d7fd 100644 --- a/examples/python/index.js +++ b/examples/python/index.js @@ -11,75 +11,90 @@ * 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 debug = false; // Pretty print any bytes in and out... it's amazing... const verbose = true; // Adds verbosity to functions -const Cyton = require('openbci').Cyton; -let ourBoard = new Cyton({ - simulate: simulate, // Uncomment to see how it works with simulator! - simulatorFirmwareVersion: 'v2', - debug: debug, - verbose: verbose -}); +const kWirelessProtocolBluetooth = 'bluetooth'; // The dongle +const kWirelessProtocolWifi = 'wifi'; + +const curWirelessProtocol = kWirelessProtocolBluetooth; + +let cytonSerial, cytonWifi; +if (curWirelessProtocol === kWirelessProtocolBluetooth) { + const Cyton = require('openbci-cyton'); + const simulate = false; // Sends synthetic data + cytonSerial = new Cyton({ + simulate: simulate, // Uncomment to see how it works with simulator! + simulatorFirmwareVersion: 'v2', + debug: debug, + verbose: verbose + }); -let timeSyncPossible = false; -let resyncPeriodMin = 1; -let secondsInMinute = 60; -let resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; - -ourBoard.autoFindOpenBCIBoard().then(portName => { - if (portName) { - /** - * Connect to the board with portName - * i.e. ourBoard.connect(portName)..... - */ - // Call to connect - ourBoard.connect(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(); - - if (timeSyncPossible) { - ourBoard.streamStart() + let timeSyncPossible = false; + + let resyncPeriodMin = 1; + let secondsInMinute = 60; + let resyncPeriod = cytonSerial.sampleRate() * resyncPeriodMin * secondsInMinute; + + cytonSerial.autoFindOpenBCIBoard().then(portName => { + if (portName) { + /** + * Connect to the board with portName + * i.e. ourBoard.connect(portName)..... + */ + // Call to connect + cytonSerial.connect(portName) + .then(() => { + cytonSerial.once('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 = cytonSerial.usingAtLeastVersionTwoFirmware(); + + cytonSerial.streamStart() .catch(err => { console.log(`stream start: ${err}`); }); - } else { - console.log('not able to time sync'); - } + }); + }) + .catch(err => { + console.log(`connect: ${err}`); + process.exit(0); }); - }) - .catch(err => { - console.log(`connect: ${err}`); - }); - } else { - /** Unable to auto find OpenBCI board */ - console.log('Unable to auto find OpenBCI board'); - } -}); + } else { + /** Unable to auto find OpenBCI board */ + console.log('Unable to auto find OpenBCI board'); + process.exit(0); + } + }); + + const sampleFunc = sample => { + if (timeSyncPossible) { + if (sample._count % resyncPeriod === 0) { + cytonSerial.syncClocksFull() + .then(syncObj => { + // Sync was successful + if (syncObj.valid) { + // Log the object to check it out! + console.log(`timeOffset`, syncObj.timeOffsetMaster); + } else { + // Retry it + console.log(`Was not able to sync... retry!`); + } + }); + } -const sampleFunc = sample => { - if (sample._count % resyncPeriod === 0) { - ourBoard.syncClocksFull() - .then(syncObj => { - // Sync was successful - if (syncObj.valid) { - // Log the object to check it out! - console.log(`timeOffset`, syncObj.timeOffsetMaster); + 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 { - // Retry it - console.log(`Was not able to sync... retry!`); + sendToPython({ + action: 'process', + command: 'sample', + message: 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}`); + } } else { sendToPython({ action: 'process', @@ -87,11 +102,44 @@ const sampleFunc = sample => { message: sample }); } - } -}; + }; + + // Subscribe to your functions + cytonSerial.on('sample', sampleFunc); +} else if (curWirelessProtocol === kWirelessProtocolWifi) { + const protocol = 'tcp'; // or 'udp' + + let Wifi = require('openbci-wifi'); + cytonWifi = new Wifi({ + debug: debug, + verbose: verbose, + sendCounts: false, + latency: 16667, + protocol: protocol, + burst: true + }); -// Subscribe to your functions -ourBoard.on('sample', sampleFunc); + const sampleFunc = (sample) => { + sendToPython({ + action: 'process', + command: 'sample', + message: sample + }); + }; + + cytonWifi.on('sample', sampleFunc); + + cytonWifi.searchToStream({ + streamStart: true, + sampleRate: 1000 + }) + .catch((err) => { + console.log(err); + process.exit(0); + }); +} else { + console.error(Error(`Selected wifi network ${curWirelessProtocol} is not support yet`)); +} // ZMQ fun @@ -105,7 +153,7 @@ socket.bind(portPub, function (err) { * @param {Object} interProcessObject The standard inter-process object. * @return {None} */ -var sendToPython = (interProcessObject, verbose) => { +const sendToPython = (interProcessObject, verbose) => { if (verbose) { console.log(`<- out ${JSON.stringify(interProcessObject)}`); } @@ -114,7 +162,7 @@ var sendToPython = (interProcessObject, verbose) => { } }; -var receiveFromPython = (rawData) => { +const receiveFromPython = (rawData) => { try { let body = JSON.parse(rawData); // five because `resp ` processInterfaceObject(body); @@ -174,11 +222,49 @@ function exitHandler (options, err) { if (options.cleanup) { if (verbose) console.log('clean'); /** Do additional clean up here */ + if (curWirelessProtocol === kWirelessProtocolBluetooth) { + if (cytonSerial) { + cytonSerial.removeAllListeners('sample'); + } + } else if (curWirelessProtocol === kWirelessProtocolWifi) { + if (cytonWifi) { + if (cytonWifi.isConnected()) cytonWifi.disconnect().catch(console.log); + cytonWifi.removeAllListeners('sample'); + cytonWifi.destroy(); + } + } } if (err) console.log(err.stack); if (options.exit) { if (verbose) console.log('exit'); - ourBoard.disconnect().catch(console.log); + if (curWirelessProtocol === kWirelessProtocolBluetooth) { + cytonSerial.disconnect() + .then(() => { + process.exit(0); + }) + .catch((err) => { + console.log(err); + process.exit(0); + }); + } else if (curWirelessProtocol === kWirelessProtocolWifi) { + if (cytonWifi.isStreaming()) { + let timmy = setTimeout(() => { + console.log("timeout"); + process.exit(0); + }, 1000); + cytonWifi.streamStop() + .then(() => { + console.log('stream stopped'); + if (timmy) clearTimeout(timmy); + process.exit(0); + }).catch((err) => { + console.log(err); + process.exit(0); + }); + } else { + process.exit(0); + } + } } } diff --git a/examples/python/package.json b/examples/python/package.json index 4ebb27a..0d5d6fa 100644 --- a/examples/python/package.json +++ b/examples/python/package.json @@ -6,6 +6,7 @@ "scripts": { "start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"", "start-node": "node index.js", + "start-py": "python handoff.py", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ @@ -16,8 +17,9 @@ "author": "AJ Keller", "license": "MIT", "dependencies": { - "openbci": "^2.0.0", - "zmq-prebuilt": "^2.1.0" + "openbci-cyton": "^1.0.7", + "openbci-wifi": "^0.3.1", + "zeromq": "^4.6.0" }, "devEngines": { "node": "<=6.x",