Comparar commits
31 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 3a90e5ab96 | |||
| a18e17c7ca | |||
| 5da869f059 | |||
| e32ec71c47 | |||
| 5ee44ae531 | |||
| b7f8c135b9 | |||
| 8aa69a18dd | |||
| 617e111812 | |||
| d8c74d5b30 | |||
| aaf9f08c24 | |||
| 37c622fdf4 | |||
| 1d9927a7d7 | |||
| 1eda6df549 | |||
| 88f973d349 | |||
| afcf1de6e2 | |||
| caeeaf7dbd | |||
| 6474391d1e | |||
| 169422e77e | |||
| 21438f5a37 | |||
| 2f5a31e481 | |||
| 04f6fbae14 | |||
| 8a22f3f11b | |||
| 3ab00cfaf5 | |||
| 29b713b7f1 | |||
| 74339c126b | |||
| 465b3cc3a5 | |||
| 920eab9ed1 | |||
| 3a6d87ca90 | |||
| 6a0e1c609f | |||
| cd7d388e69 | |||
| 0ef2add033 |
@@ -1,6 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.0"
|
||||
- "5.11.0"
|
||||
install:
|
||||
- npm install --all
|
||||
script:
|
||||
|
||||
+182
-46
@@ -5,9 +5,54 @@
|
||||
|
||||
# openbci-sdk
|
||||
|
||||
An NPM module for OpenBCI ~ written with love by [Push The World!](www.pushtheworldllc.com)
|
||||
A Node.js module for OpenBCI ~ written with love by [Push The World!](http://www.pushtheworldllc.com)
|
||||
|
||||
## Working with the Module
|
||||
We are proud to support all functionality of the OpenBCI 8 Channel board (16 channel coming soon) and are actively developing and maintaining this module.
|
||||
|
||||
The purpose of this module is to **get connected** and **start streaming** as fast as possible.
|
||||
|
||||
## TL;DR
|
||||
|
||||
#### Install via npm:
|
||||
|
||||
```
|
||||
npm install openbci-sdk
|
||||
```
|
||||
|
||||
#### Get connected and start streaming
|
||||
|
||||
```js
|
||||
var ourBoard = new require('openbci-sdk').OpenBCIBoard();
|
||||
ourBoard.connect(portName)
|
||||
.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."
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
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.
|
||||
* [_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 [400 **_automatic_** tests](https://codecov.io/github/OpenBCI/openbci-js-sdk?branch=master) written for it!
|
||||
|
||||
## General Overview
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
@@ -35,26 +80,22 @@ var ourBoard = require('openbci-sdk').OpenBCIBoard({
|
||||
});
|
||||
```
|
||||
|
||||
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()` 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.
|
||||
|
||||
Another useful way to start the simulator:
|
||||
```js
|
||||
var ourBoard = new require('openbci-sdk').OpenBCIBoard();
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if(portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
} else {
|
||||
/**Unable to auto find OpenBCI board*/
|
||||
}
|
||||
});
|
||||
var openBCIBoard = require('openbci-sdk');
|
||||
var k = openBCIBoard.OpenBCIConstants;
|
||||
var ourBoard = openBCIBoard.OpenBCIBoard();
|
||||
ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true
|
||||
.then(function(boardSerial) {
|
||||
ourBoard.on('ready',function() {
|
||||
/** Start streaming, reading registers, what ever your heart desires */
|
||||
});
|
||||
}).catch(function(err) {
|
||||
/** Handle connection errors */
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
'ready' event
|
||||
------------
|
||||
|
||||
@@ -82,7 +123,7 @@ Sample properties:
|
||||
|
||||
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)` with `portName` equal to `'/dev/tty.openBCISimulator'`.
|
||||
You can also start the simulator by sending `.connect(portName)` with `portName` equal to `'OpenBCISimulator'`.
|
||||
|
||||
To get a 'sample' event, you need to:
|
||||
-------------------------------------
|
||||
@@ -92,7 +133,7 @@ To get a 'sample' event, you need to:
|
||||
4. Install the 'sample' event emitter
|
||||
```js
|
||||
var ourBoard = new require('openbci-sdk').OpenBCIBoard();
|
||||
ourBoard.connect(portName).then(function(boardSerial) {
|
||||
ourBoard.connect(portName).then(function() {
|
||||
ourBoard.on('ready',function() {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample',function(sample) {
|
||||
@@ -109,6 +150,28 @@ var ourBoard = new require('openbci-sdk').OpenBCIBoard();
|
||||
ourBoard.streamStop().then(ourBoard.disconnect());
|
||||
```
|
||||
|
||||
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()` 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 ourBoard = new require('openbci-sdk').OpenBCIBoard();
|
||||
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()` 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.
|
||||
@@ -154,13 +217,11 @@ But wait! What is this `impedanceArray`? An Array of Objects, for each object:
|
||||
[{
|
||||
channel: 1,
|
||||
P: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: 'init'
|
||||
},
|
||||
N: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: 'init'
|
||||
}
|
||||
},
|
||||
@@ -173,8 +234,7 @@ 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)
|
||||
* *data* is an array of raw impedances values that were recorded over 250ms
|
||||
* *average* is an average impedance value taken from `data` array. To get this value we remove outliers from the `data` array and average the cleaned data.
|
||||
* *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
|
||||
@@ -182,7 +242,7 @@ Where:
|
||||
* **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 imputs:
|
||||
To run an impedance test on all inputs, one channel at a time:
|
||||
|
||||
1. Connect to board
|
||||
2. Start streaming
|
||||
@@ -228,6 +288,12 @@ Board optional configurations.
|
||||
* `verbose` To output more messages to the command line.
|
||||
* `simulate` Full functionality, just synthetic data.
|
||||
* `simulatorSampleRate` - The sample rate to use for the simulator (Default is `250`)
|
||||
* `simulatorAlpha` - {Boolean} - Inject and 10Hz alpha wave in Channels 1 and 2 (Default `true`)
|
||||
* `simulatorLineNoise` - Injects line noise on channels.
|
||||
* `60Hz` - 60Hz line noise (Default) (ex. __Unite States__)
|
||||
* `50Hz` - 50Hz line noise (ex. __Europe__)
|
||||
* `None` - Do not inject line noise.
|
||||
* `sntp` - Syncs the module up with an SNTP time server. Syncs the board on startup with the SNTP time. Adds a time stamp to the AUX channels. NOTE: (NOT FULLY IMPLEMENTED) [DO NOT USE]
|
||||
|
||||
**Note, we have added support for either all lowercase OR camelcase of the options, use whichever style you prefer.**
|
||||
|
||||
@@ -237,7 +303,7 @@ Automatically find an OpenBCI board.
|
||||
|
||||
**Note: This will always return an Array of `COM` ports on Windows**
|
||||
|
||||
**_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux.
|
||||
**_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux or `OpenBCISimulator` if `this.options.simulate === true`.
|
||||
|
||||
### .channelOff(channelNumber)
|
||||
|
||||
@@ -307,7 +373,7 @@ The essential precursor method to be called initially to establish a serial conn
|
||||
|
||||
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 The promise will be rejected at any time if the serial port has an 'error' or 'close' event emitted.
|
||||
**_Returns_** a promise, fulfilled by a successful serial connection to the board, the promise will be rejected at any time if the serial port has an 'error' or 'close' event emitted.
|
||||
|
||||
### .debugSession()
|
||||
|
||||
@@ -389,13 +455,11 @@ Where an impedance for this method call would look like:
|
||||
{
|
||||
channel: 1,
|
||||
P: {
|
||||
data: [3456.324,2204.5,...],
|
||||
average: 2394.45,
|
||||
raw: 2394.45,
|
||||
text: 'good'
|
||||
},
|
||||
N: {
|
||||
data: [5436.324,9404.5,...],
|
||||
average: 7694.45,
|
||||
raw: 7694.45,
|
||||
text: 'ok'
|
||||
}
|
||||
}
|
||||
@@ -432,13 +496,11 @@ Where an impedance for this method call would look like:
|
||||
{
|
||||
channel: 1,
|
||||
P: {
|
||||
data: [3456.324,2204.5,...],
|
||||
average: 2394.45,
|
||||
raw: 2394.45,
|
||||
text: 'good'
|
||||
},
|
||||
N: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: 'init'
|
||||
}
|
||||
}
|
||||
@@ -475,18 +537,28 @@ Where an impedance for this method call would look like:
|
||||
{
|
||||
channel: 1,
|
||||
P: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: 'init'
|
||||
},
|
||||
N: {
|
||||
data: [5436.324,9404.5,...],
|
||||
average: 7694.45,
|
||||
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
|
||||
|
||||
### .listPorts()
|
||||
|
||||
List available ports so the user can choose a device when not automatically found.
|
||||
@@ -527,7 +599,7 @@ Get the current sample rate.
|
||||
|
||||
**_Returns_** a number, the current sample rate.
|
||||
|
||||
### .simulatorStart()
|
||||
### .simulatorEnable()
|
||||
|
||||
To enter simulate mode. Must call `.connect()` after.
|
||||
|
||||
@@ -535,7 +607,7 @@ To enter simulate mode. Must call `.connect()` after.
|
||||
|
||||
**_Returns_** a promise, fulfilled if able to enter simulate mode, reject if not.
|
||||
|
||||
### .simulatorStop()
|
||||
### .simulatorDisable()
|
||||
|
||||
To leave simulate mode.
|
||||
|
||||
@@ -543,6 +615,38 @@ To leave simulate mode.
|
||||
|
||||
**_Returns_** a promise, fulfilled if able to stop simulate mode, reject if not.
|
||||
|
||||
### .sntp
|
||||
|
||||
Extends the popular STNP 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
|
||||
|
||||
### .sntpGetServerTime()
|
||||
|
||||
Get time from the SNTP server. Must have internet connection!
|
||||
|
||||
**_Returns_** a promise fulfilled with time object
|
||||
|
||||
### .sntpNow()
|
||||
|
||||
This function gets SNTP time since Jan 1, 1970, if we call this after a successful `.sntpStart()` this time will be sycned, or else we will just get the current computer time, the case if there is no internet.
|
||||
|
||||
**_Returns_** time since UNIX epoch in ms.
|
||||
|
||||
### .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.
|
||||
@@ -567,7 +671,25 @@ Sends a stop streaming command to the board.
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable.
|
||||
|
||||
### .write(data)
|
||||
### .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
|
||||
* `pulse2xFast` - Connect to test signal 2x Amplitude, slow pulse
|
||||
* `none` - Reset to default
|
||||
|
||||
**_Returns_** a promise, if the commands were sent to write buffer.
|
||||
|
||||
### .write(dataToWrite)
|
||||
|
||||
Send commands to the board. Due to the OpenBCI board firmware, 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.
|
||||
|
||||
@@ -639,6 +761,20 @@ A bool, true if connected to an OpenBCI board, false if not.
|
||||
|
||||
A bool, true if streaming data from an OpenBCI board, false if not.
|
||||
|
||||
## Useful Constants
|
||||
|
||||
To use the constants file simply:
|
||||
```js
|
||||
var openBCIBoard = require('openbci-sdk');
|
||||
var k = openBCIBoard.OpenBCIConstants;
|
||||
|
||||
console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console.
|
||||
```
|
||||
|
||||
### .OBCISimulatorPortName
|
||||
|
||||
The name of the simulator port.
|
||||
|
||||
## Dev Notes
|
||||
Running
|
||||
-------
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# 0.3.3
|
||||
|
||||
### New Features
|
||||
|
||||
* Simulator made to look more like brainwave data to the user. Implemented a 1/f filter. Defaults to injecting 60Hz line noise with two channels of alpha (10Hz) boost.
|
||||
|
||||
### Github Issues Addressed
|
||||
|
||||
* [https://github.com/OpenBCI/openbci-js-sdk/issues/44](#44)
|
||||
|
||||
|
||||
# 0.3.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `rawDataPacket` not being emitted
|
||||
|
||||
# 0.3.2
|
||||
|
||||
### Work In Progress
|
||||
|
||||
* SNTP Time Synchronization
|
||||
|
||||
### 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
|
||||
* Add `.sntpNow()` function to get ntp time.
|
||||
|
||||
# 0.3.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Bumped serialport version
|
||||
|
||||
# 0.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Test Signals with ADS1299 using `.testSignal()`
|
||||
* Continuous impedance testing, where each sample gets an `impedances` object that is an array of impedances for each
|
||||
channel.
|
||||
* OpenBCI Radio Test File
|
||||
* Added Sntp npm module with helper functions
|
||||
* Removed stopByte and startByte from sampleObjects
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Changed simulator name to `OpenBCISimulator`
|
||||
* Changed name of function `simulatorOn` to `simulatorEnable`
|
||||
* Changed name of function `simulatorOff` to `simulatorDisable`
|
||||
|
||||
### Work In Progress
|
||||
|
||||
* NTP Time Synchronization
|
||||
* Goertzel algorithm to get voltage for impedance calculation
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Impedance calculations
|
||||
* Readme updates
|
||||
* Serial buffer had the chance to become permanently unaligned, optimized and completely transformed and refactored the way bytes are processed.
|
||||
* Changes to gain of channels not working correctly.
|
||||
* Node 5 compatibility
|
||||
|
||||
### Github Issues Addressed
|
||||
|
||||
* #25, #26, #27, #29, #30, #31, #33, #34
|
||||
+395
-222
@@ -7,6 +7,8 @@ var serialPort = require('serialport');
|
||||
var openBCISample = require('./openBCISample');
|
||||
var k = openBCISample.k;
|
||||
var openBCISimulator = require('./openBCISimulator');
|
||||
var now = require('performance-now');
|
||||
var Sntp = require('sntp');
|
||||
|
||||
/**
|
||||
* @description SDK for OpenBCI Board {@link www.openbci.com}
|
||||
@@ -17,10 +19,13 @@ function OpenBCIFactory() {
|
||||
|
||||
var _options = {
|
||||
boardType: k.OBCIBoardDefault,
|
||||
baudrate: 115200,
|
||||
verbose: false,
|
||||
sntp: false,
|
||||
simulate: false,
|
||||
simulatorSampleRate: 250,
|
||||
baudrate: 115200,
|
||||
verbose: false
|
||||
simulatorAlpha: true,
|
||||
simulatorLineNoise: '60Hz'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,15 +39,27 @@ function OpenBCIFactory() {
|
||||
* `ganglion` - 4 Channel board
|
||||
* (NOTE: THIS IS IN-OP TIL RELEASE OF GANGLION BOARD 07/2016)
|
||||
*
|
||||
* - `baudRate` - Baud Rate, defaults to 115200. Manipulating this is allowed if
|
||||
* firmware on board has been previously configured.
|
||||
*
|
||||
* - `verbose` - Print out useful debugging events
|
||||
*
|
||||
* - `simulate` - Full functionality, just mock data.
|
||||
*
|
||||
* - `simulatorSampleRate` - The sample rate to use for the simulator
|
||||
* (Default is `250`)
|
||||
*
|
||||
* - `baudRate` - Baud Rate, defaults to 115200. Manipulating this is allowed if
|
||||
* firmware on board has been previously configured.
|
||||
* - `simulatorAlpha` - {Boolean} - Inject and 10Hz alpha wave in Channels 1 and 2 (Default `true`)
|
||||
*
|
||||
* - `verbose` - Print out useful debugging events
|
||||
* - `simulatorLineNoise` - Injects line noise on channels.
|
||||
* 3 Possible Boards:
|
||||
* `60Hz` - 60Hz line noise (Default) [America]
|
||||
* `50Hz` - 50Hz line noise [Europe]
|
||||
* `None` - Do not inject line noise.
|
||||
*
|
||||
* - `sntp` - Syncs the module up with an SNTP time server. Syncs the board on startup
|
||||
* with the SNTP time. Adds a time stamp to the AUX channels. (NOT FULLY
|
||||
* IMPLEMENTED) [DO NOT USE]
|
||||
* @constructor
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
@@ -56,38 +73,78 @@ function OpenBCIFactory() {
|
||||
|
||||
/** Configuring Options */
|
||||
opts.boardType = options.boardType || options.boardtype || _options.boardType;
|
||||
opts.simulate = options.simulate || _options.simulate;
|
||||
opts.simulatorSampleRate = options.simulatorSampleRate || options.simulatorsamplerate || _options.simulatorSampleRate;
|
||||
opts.baudRate = options.baudRate || options.baudrate || _options.baudrate;
|
||||
opts.verbose = options.verbose || _options.verbose;
|
||||
opts.sntp = options.SNTP || options.sntp || _options.NTP;
|
||||
opts.simulate = options.simulate || _options.simulate;
|
||||
opts.simulatorSampleRate = options.simulatorSampleRate || options.simulatorsamplerate || _options.simulatorSampleRate;
|
||||
opts.simulatorLineNoise = options.simulatorLineNoise || options.simulatorlinenoise || _options.simulatorLineNoise;
|
||||
// Safety check!
|
||||
if (opts.simulatorLineNoise !== '60Hz' && opts.simulatorLineNoise !== '50Hz' && opts.simulatorLineNoise !== 'None') {
|
||||
opts.simulatorLineNoise = '60Hz';
|
||||
}
|
||||
if (options.simulatorAlpha === false || options.simulatoralpha === false) {
|
||||
opts.simulatorAlpha = false;
|
||||
} else {
|
||||
opts.simulatorAlpha = _options.simulatorAlpha;
|
||||
}
|
||||
// Set to global options object
|
||||
this.options = opts;
|
||||
|
||||
/** Properties (keep alphabetical) */
|
||||
// Arrays
|
||||
this.writeOutArray = new Array(100);
|
||||
this.channelSettingsArray = k.channelSettingsArrayInit(this.numberOfChannels());
|
||||
// Bools
|
||||
this.isLookingForKeyInBuffer = true;
|
||||
// Buffers
|
||||
this.masterBuffer = masterBufferMaker();
|
||||
this.moneyBuf = new Buffer('$$$');
|
||||
this.searchingBuf = this.moneyBuf;
|
||||
this.searchBuffers = {
|
||||
timeSyncStart: new Buffer('$a$'),
|
||||
miscStop: new Buffer('$$$')
|
||||
};
|
||||
this.searchingBuf = this.searchBuffers.miscStop;
|
||||
// Objects
|
||||
this.goertzelObject = openBCISample.goertzelNewObject(this.numberOfChannels());
|
||||
this.writer = null;
|
||||
this.impedanceTest = {
|
||||
active: false,
|
||||
isTestingPInput: false,
|
||||
isTestingNInput: false,
|
||||
onChannel: 0,
|
||||
sampleNumber: 0
|
||||
sampleNumber: 0,
|
||||
continuousMode: false,
|
||||
impedanceForChannel: 0
|
||||
};
|
||||
this.sync = {
|
||||
npt1: 0,
|
||||
ntp2: 0
|
||||
};
|
||||
this.sntpOptions = {
|
||||
host: 'nist1-sj.ustiming.org', // Defaults to pool.ntp.org
|
||||
port: 123, // Defaults to 123 (NTP)
|
||||
resolveReference: true, // Default to false (not resolving)
|
||||
timeout: 1000 // Defaults to zero (no timeout)
|
||||
};
|
||||
// Numbers
|
||||
this.badPackets = 0;
|
||||
this.commandsToWrite = 0;
|
||||
this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType));
|
||||
this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort;
|
||||
this.sampleCount = 0;
|
||||
// Strings
|
||||
|
||||
// NTP
|
||||
if (this.options.sntp) {
|
||||
this.sntpGetServerTime()
|
||||
.then((timeObj) => {
|
||||
if (this.options.verbose) {
|
||||
console.log('NTP synced successfully, time object:');
|
||||
console.log(timeObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: Add connect immediately functionality, suggest this to be the default...
|
||||
}
|
||||
|
||||
@@ -98,9 +155,7 @@ function OpenBCIFactory() {
|
||||
* @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. If at any time the serial port
|
||||
* closes or errors then this promise will be rejected, and this should be
|
||||
* observed and taken care of in the most front facing user methods.
|
||||
* @returns {Promise} if the board was able to connect.
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.connect = function(portName) {
|
||||
@@ -114,7 +169,10 @@ function OpenBCIFactory() {
|
||||
this.options.simulate = true;
|
||||
if (this.options.verbose) console.log('using faux board ' + portName);
|
||||
boardSerial = new openBCISimulator.OpenBCISimulator(portName, {
|
||||
verbose: this.options.verbose
|
||||
verbose: this.options.verbose,
|
||||
sampleRate: this.options.simulatorSampleRate,
|
||||
alpha: this.options.simulatorAlpha,
|
||||
lineNoise: this.options.simulatorLineNoise
|
||||
});
|
||||
} else {
|
||||
/* istanbul ignore if */
|
||||
@@ -150,7 +208,7 @@ function OpenBCIFactory() {
|
||||
resolve();
|
||||
if(this.options.verbose) console.log("Waiting for '$$$'");
|
||||
|
||||
},timeoutLength + 100);
|
||||
},timeoutLength + 250);
|
||||
});
|
||||
boardSerial.on('close',() => {
|
||||
if (this.options.verbose) console.log('Serial Port Closed');
|
||||
@@ -170,12 +228,13 @@ function OpenBCIFactory() {
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.disconnect = function() {
|
||||
// if we are streaming then we need to give extra time for that stop streaming command to propagate through the
|
||||
// system before closing the serial port.
|
||||
var timeout = 0;
|
||||
if (this.streaming) {
|
||||
this.streamStop();
|
||||
if(this.options.verbose) console.log('stop streaming');
|
||||
this.streaming = false;
|
||||
this.write(k.OBCIStreamStop);
|
||||
timeout = 10;
|
||||
timeout = 20;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -206,9 +265,19 @@ function OpenBCIFactory() {
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.streamStart = function() {
|
||||
this.streaming = true;
|
||||
this._reset();
|
||||
return this.write(k.OBCIStreamStart);
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('got here');
|
||||
if(this.streaming) reject('Error [.streamStart()]: Already streaming');
|
||||
this.streaming = true;
|
||||
this._reset();
|
||||
this.write(k.OBCIStreamStart)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 50); // allow time for command to get sent
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -220,8 +289,17 @@ function OpenBCIFactory() {
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.streamStop = function() {
|
||||
this.streaming = false;
|
||||
return this.write(k.OBCIStreamStop);
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.streaming) reject('Error [.streamStop()]: No stream to stop');
|
||||
this.streaming = false;
|
||||
this.write(k.OBCIStreamStop)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 10); // allow time for command to get sent
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -334,7 +412,7 @@ function OpenBCIFactory() {
|
||||
*/
|
||||
OpenBCIBoard.prototype._writeAndDrain = function(data) {
|
||||
return new Promise((resolve,reject) => {
|
||||
//console.log('boardSerial in [writeAndDrain]: ' + JSON.stringify(boardSerial) + ' with command ' + data);
|
||||
//console.log('writing command ' + data);
|
||||
if(!this.serial) reject('Serial port not open');
|
||||
this.serial.write(data,(error,results) => {
|
||||
if(results) {
|
||||
@@ -362,25 +440,31 @@ function OpenBCIFactory() {
|
||||
var macSerialPrefix = 'usbserial-D';
|
||||
return new Promise((resolve, reject) => {
|
||||
/* istanbul ignore else */
|
||||
serialPort.list((err, ports) => {
|
||||
if(err) {
|
||||
if (this.options.verbose) console.log('serial port err');
|
||||
reject(err);
|
||||
}
|
||||
if(ports.some(port => {
|
||||
if(port.comName.includes(macSerialPrefix)) {
|
||||
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');
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
if(ports.some(port => {
|
||||
if(port.comName.includes(macSerialPrefix)) {
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -420,6 +504,8 @@ function OpenBCIFactory() {
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.softReset = function() {
|
||||
this.isLookingForKeyInBuffer = true;
|
||||
this.searchingBuf = this.searchBuffers.miscStop;
|
||||
return this.write(k.OBCIMiscSoftReset);
|
||||
};
|
||||
|
||||
@@ -495,18 +581,95 @@ function OpenBCIFactory() {
|
||||
* 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((arr) => {
|
||||
arrayOfCommands = arr;
|
||||
resolve(this.write(arrayOfCommands));
|
||||
k.getChannelSetter(channelNumber,powerDown,gain,inputType,bias,srb2,srb1)
|
||||
.then((arr,newChannelSettingObject) => {
|
||||
arrayOfCommands = arr;
|
||||
this.channelSettingsArray[channelNumber-1] = newChannelSettingObject;
|
||||
resolve(this.write(arrayOfCommands));
|
||||
}, function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @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) reject('Error: test already active');
|
||||
if (this.impedanceTest.continuousMode) reject('Error: Already in continuous impedance test mode!');
|
||||
|
||||
this.impedanceTest.active = true;
|
||||
this.impedanceTest.continuousMode = true;
|
||||
|
||||
for (var i = 0;i < this.numberOfChannels(); i++) {
|
||||
k.getImpedanceSetter(i + 1,false,true).then((commandsArray) => {
|
||||
this.write(commandsArray);
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @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) reject('Error: no test active');
|
||||
if (!this.impedanceTest.continuousMode) reject('Error: Not in continuous impedance test mode!');
|
||||
|
||||
this.impedanceTest.active = false;
|
||||
this.impedanceTest.continuousMode = false;
|
||||
|
||||
for (var i = 0;i < this.numberOfChannels(); i++) {
|
||||
k.getImpedanceSetter(i + 1,false,false).then((commandsArray) => {
|
||||
this.write(commandsArray);
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -707,20 +870,22 @@ function OpenBCIFactory() {
|
||||
}
|
||||
|
||||
if (!pInput && !nInput) {
|
||||
this.impedanceTest.active = false;
|
||||
this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort;
|
||||
this.impedanceTest.active = false; // Critical to changing the flow of `._processBytes()`
|
||||
//this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort;
|
||||
} else {
|
||||
this.writeOutDelay = k.OBCIWriteIntervalDelayMSLong;
|
||||
//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) => {
|
||||
console.log(commandsArray);
|
||||
this.write(commandsArray);
|
||||
delayInMS += commandsArray.length * k.OBCIWriteIntervalDelayMSLong;
|
||||
//delayInMS += commandsArray.length * k.OBCIWriteIntervalDelayMSLong;
|
||||
delayInMS += this.commandsToWrite * k.OBCIWriteIntervalDelayMSShort; // Account for commands waiting to be sent in the write buffer
|
||||
setTimeout(() => {
|
||||
/**
|
||||
* If either pInput or nInput are true then we should start calculating impedance. Setting
|
||||
* this.isCalculatingImpedance to true here allows us to route every sample for an impedance
|
||||
* 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;
|
||||
@@ -779,8 +944,10 @@ function OpenBCIFactory() {
|
||||
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);
|
||||
}, 250);
|
||||
}, 400);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -813,7 +980,10 @@ function OpenBCIFactory() {
|
||||
if (pInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].P);
|
||||
if (nInput) openBCISample.impedanceSummarize(this.impedanceArray[channelNumber - 1].N);
|
||||
|
||||
resolve(channelNumber);
|
||||
setTimeout(() => {
|
||||
resolve(channelNumber);
|
||||
},50); // Introduce a delay to allow for extra time in case of back to back tests
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
@@ -846,192 +1016,218 @@ function OpenBCIFactory() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Send the command to tell the board to start the syncing protocol.
|
||||
*/
|
||||
OpenBCIBoard.prototype.syncClocksStart = function() {
|
||||
return new Promise((resolve,reject) => {
|
||||
if (!this.connected) reject('Must be connected to the device');
|
||||
if (this.streaming) reject('Cannot be streaming to sync clocks');
|
||||
this.searchingBuf = this.searchBuffers.timeSyncStart;
|
||||
this.isLookingForKeyInBuffer = true;
|
||||
this.write(k.OBCISyncClockStart);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* 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
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._processBytes = function(data) {
|
||||
//console.log(data);
|
||||
var sizeOfData = data.byteLength;
|
||||
this.bytesIn += sizeOfData; // increment to keep track of how many bytes we are receiving
|
||||
if(this.isLookingForKeyInBuffer) { //in a reset state
|
||||
var sizeOfSearchBuf = this.searchingBuf.byteLength; // then size in bytes of the buffer we are searching for
|
||||
for (var i = 0; i < sizeOfData - (sizeOfSearchBuf - 1); i++) {
|
||||
if (this.searchingBuf.equals(data.slice(i, i + sizeOfSearchBuf))) { // slice a chunk of the buffer to analyze
|
||||
if (this.searchingBuf.equals(this.moneyBuf)) {
|
||||
if(this.options.verbose) console.log('Money!');
|
||||
if (this.searchingBuf.equals(this.searchBuffers.miscStop)) {
|
||||
if (this.options.verbose) console.log('Money!');
|
||||
if (this.options.verbose) console.log(data.toString());
|
||||
this.isLookingForKeyInBuffer = false; // critical!!!
|
||||
this.emit('ready'); // tell user they are ready to stream, etc...
|
||||
} else if (this.searchingBuf.equals(this.searchBuffers.timeSyncStart)) {
|
||||
this.sync.ntp1 = now();
|
||||
if(this.options.verbose) console.log('Got time sync request: ' + this.sync.npt1.toFixed(4));
|
||||
this.sync.ntp2 = now();
|
||||
this._writeAndDrain('<' + (this.sync.ntp1 * 1000) + (this.sync.ntp2 * 1000));
|
||||
this.searchingBuf = this.searchBuffers.miscStop;
|
||||
|
||||
} else {
|
||||
getChannelSettingsObj(data.slice(i)).then((channelSettingsObject) => {
|
||||
this.emit('query',channelSettingsObject);
|
||||
}, (err) => {
|
||||
console.log('Error: ' + err);
|
||||
});
|
||||
this.searchingBuf = this.moneyBuf;
|
||||
this.searchingBuf = this.searchBuffers.miscStop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // steaming operation should lead here...
|
||||
// send input data to master buffer
|
||||
this._bufMerger(data);
|
||||
|
||||
// parse the master buffer
|
||||
while(this.masterBuffer.packetsRead < this.masterBuffer.packetsIn) {
|
||||
var rawPacket = this._bufPacketStripper();
|
||||
var newSample = openBCISample.convertPacketToSample(rawPacket);
|
||||
if(newSample) {
|
||||
this.emit('rawDataPacket', rawPacket);
|
||||
newSample._count = this.sampleCount++;
|
||||
if(this.impedanceTest.active) {
|
||||
if (this.impedanceTest.onChannel != 0) {
|
||||
// Get an average of the impedance
|
||||
openBCISample.impedanceCalculationForChannel(newSample,this.impedanceTest.onChannel)
|
||||
.then(rawValue => {
|
||||
impedanceTestApplyRaw.call(this,rawValue);
|
||||
}).catch(err => {
|
||||
console.log('Impedance calculation error: ' + err);
|
||||
var bytesToRead = sizeOfData;
|
||||
|
||||
// is there old data?
|
||||
if (this.buffer) {
|
||||
// Get size of old buffer
|
||||
var oldBufferSize = this.buffer.byteLength;
|
||||
|
||||
// Make a new buffer
|
||||
var newDataBuffer = new Buffer(bytesToRead + oldBufferSize);
|
||||
|
||||
// Put old buffer in the front of the new buffer
|
||||
var oldBytesWritten = this.buffer.copy(newDataBuffer);
|
||||
|
||||
// Move the incoming data into the end of the new buffer
|
||||
data.copy(newDataBuffer,oldBytesWritten);
|
||||
|
||||
// Over write data
|
||||
data = newDataBuffer;
|
||||
|
||||
// Update the number of bytes to read
|
||||
bytesToRead += oldBytesWritten;
|
||||
}
|
||||
|
||||
var readingPosition = 0;
|
||||
|
||||
// 45 < (200 - 33) --> 45 < 167 (good) | 189 < 167 (bad) | 0 < (28 - 33) --> 0 < -5 (bad)
|
||||
while (readingPosition <= bytesToRead - k.OBCIPacketSize) {
|
||||
if (data[readingPosition] === k.OBCIByteStart) {
|
||||
var rawPacket = data.slice(readingPosition, readingPosition + k.OBCIPacketSize);
|
||||
this.emit('rawDataPacket',rawPacket);
|
||||
if (data[readingPosition + k.OBCIPacketSize - 1] === k.OBCIByteStop) {
|
||||
// standard packet!
|
||||
openBCISample.parseRawPacket(rawPacket,this.channelSettingsArray)
|
||||
.then(sampleObject => {
|
||||
|
||||
sampleObject._count = this.sampleCount++;
|
||||
if(this.impedanceTest.active) {
|
||||
var impedanceArray;
|
||||
if (this.impedanceTest.continuousMode) {
|
||||
//console.log('running in contiuous 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];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.emit('sample', sampleObject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.emit('sample', newSample);
|
||||
} else {
|
||||
this.badPackets++;
|
||||
this._bufAlign(); // fix the buffer to start reading at next start byte
|
||||
}
|
||||
|
||||
// increment reading position
|
||||
readingPosition++;
|
||||
}
|
||||
|
||||
// Are there any bytes to move into the buffer?
|
||||
if (readingPosition < bytesToRead) {
|
||||
//we are creating a new Buffer the size of how many bytes are left in the data buffer
|
||||
// so we can move and store that into this.buffer for the next time this function is ran.
|
||||
this.buffer = new Buffer(bytesToRead - readingPosition);
|
||||
|
||||
// copy data from data into this.buffer.
|
||||
data.copy(this.buffer);
|
||||
|
||||
} else {
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Merge an input buffer with the master buffer. Takes into account
|
||||
* wrapping around the master buffer if we run out of space in
|
||||
* the master buffer. Note that if you are not reading bytes from
|
||||
* master buffer, you will lose them if you continue to call this
|
||||
* method due to the overwrite nature of buffers
|
||||
* @param inputBuffer
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._bufMerger = function(inputBuffer) {
|
||||
// we do a try, catch, paradigm to prevent fatal crashes while trying to read from the buffer
|
||||
try {
|
||||
var inputBufferSize = inputBuffer.byteLength;
|
||||
if (inputBufferSize > k.OBCIMasterBufferSize) { /** Critical error condition */
|
||||
console.log("input buffer too large...");
|
||||
} else if (inputBufferSize < (k.OBCIMasterBufferSize - this.masterBuffer.positionWrite)) { /**Normal*/
|
||||
// debug prints
|
||||
// console.log('Storing input buffer of size: ' + inputBufferSize + ' to the master buffer at position: ' + this.masterBufferPositionWrite);
|
||||
//there is room in the buffer, so fill it
|
||||
inputBuffer.copy(this.masterBuffer.buffer,this.masterBuffer.positionWrite,0);
|
||||
// update the write position
|
||||
this.masterBuffer.positionWrite += inputBufferSize;
|
||||
//store the number of packets read in
|
||||
this.masterBuffer.packetsIn += Math.floor((inputBufferSize + this.masterBuffer.looseBytes) / k.OBCIPacketSize);
|
||||
//console.log('Total packets to read: '+ this.masterBuffer.packetsIn);
|
||||
// loose bytes results when there is not an even multiple of packets in the inputBuffer
|
||||
// example: The first time this is ran there are only 68 bytes in the first call to this function
|
||||
// therefore there are only two packets (66 bytes), these extra two bytes need to be saved for the next
|
||||
// call and be considered in the next iteration so we can keep track of how many bytes we need to read.
|
||||
this.masterBuffer.looseBytes = (inputBufferSize + this.masterBuffer.looseBytes) % k.OBCIPacketSize;
|
||||
} else { /** Wrap around condition*/
|
||||
//console.log('We reached the end of the master buffer');
|
||||
//the new buffer cannot fit all the way into the master buffer, going to need to break it up...
|
||||
var bytesSpaceLeftInMasterBuffer = k.OBCIMasterBufferSize - this.masterBuffer.positionWrite;
|
||||
// fill the rest of the buffer
|
||||
inputBuffer.copy(this.masterBuffer.buffer,this.masterBuffer.positionWrite,0,bytesSpaceLeftInMasterBuffer);
|
||||
// overwrite the beginning of master buffer
|
||||
var remainingBytesToWriteToMasterBuffer = inputBufferSize - bytesSpaceLeftInMasterBuffer;
|
||||
inputBuffer.copy(this.masterBuffer.buffer,0,bytesSpaceLeftInMasterBuffer);
|
||||
//this.masterBuffer.write(inputBuffer.slice(bytesSpaceLeftInMasterBuffer,inputBufferSize),0,remainingBytesToWriteToMasterBuffer);
|
||||
//move the masterBufferPositionWrite
|
||||
this.masterBuffer.positionWrite = remainingBytesToWriteToMasterBuffer;
|
||||
// store the number of packets read
|
||||
this.masterBuffer.packetsIn += Math.floor((inputBufferSize + this.masterBuffer.looseBytes) / k.OBCIPacketSize);
|
||||
//console.log('Total packets to read: '+ this.masterBuffer.packetsIn);
|
||||
// see if statement above for explanation of loose bytes
|
||||
this.masterBuffer.looseBytes = (inputBufferSize + this.masterBuffer.looseBytes) % k.OBCIPacketSize;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log('Error: ' + error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Strip packets from the master buffer
|
||||
* @returns {Buffer} A buffer containing a packet of 33 bytes long, ready to be read.
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._bufPacketStripper = function() {
|
||||
try {
|
||||
// not at end of master buffer
|
||||
var rawPacket;
|
||||
if(k.OBCIPacketSize < k.OBCIMasterBufferSize - this.masterBuffer.positionRead) {
|
||||
// extract packet
|
||||
rawPacket = this.masterBuffer.buffer.slice(this.masterBuffer.positionRead, this.masterBuffer.positionRead + k.OBCIPacketSize);
|
||||
// move the read position pointer
|
||||
this.masterBuffer.positionRead += k.OBCIPacketSize;
|
||||
// increment packets read
|
||||
this.masterBuffer.packetsRead++;
|
||||
//console.log(rawPacket);
|
||||
// return this raw packet
|
||||
return rawPacket;
|
||||
} else { //special case because we are at the end of the master buffer (must wrap)
|
||||
// calculate the space left to read from for the partial packet
|
||||
var part1Size = k.OBCIMasterBufferSize - this.masterBuffer.positionRead;
|
||||
// make the first part of the packet
|
||||
var part1 = this.masterBuffer.buffer.slice(this.masterBuffer.positionRead, this.masterBuffer.positionRead + part1Size);
|
||||
// reset the read position to 0
|
||||
this.masterBuffer.positionRead = 0;
|
||||
// get part 2 size
|
||||
var part2Size = k.OBCIPacketSize - part1Size;
|
||||
// get the second part
|
||||
var part2 = this.masterBuffer.buffer.slice(0, part2Size);
|
||||
// merge the two parts
|
||||
rawPacket = Buffer.concat([part1,part2], k.OBCIPacketSize);
|
||||
// move the read position pointer
|
||||
this.masterBuffer.positionRead += part2Size;
|
||||
// increment packets read
|
||||
this.masterBuffer.packetsRead++;
|
||||
// return this raw packet
|
||||
return rawPacket;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log('Error: ' + error);
|
||||
}
|
||||
};
|
||||
|
||||
OpenBCIBoard.prototype._bufAlign = function() {
|
||||
var startingReadPosition = this.masterBuffer.positionRead;
|
||||
console.log('Starting read position: '+ startingReadPosition);
|
||||
var aligned = false;
|
||||
|
||||
while (this.masterBuffer.buffer[this.masterBuffer.positionRead] !== k.OBCIByteStart) {
|
||||
//console.log(this.masterBuffer.buffer[this.masterBuffer.positionRead]);
|
||||
if(this.masterBuffer.positionRead === startingReadPosition) {
|
||||
console.log('Wrapped around and hit the starting point again');
|
||||
aligned = true; // give up and try again at some later point in time when new stuff has been loaded in.
|
||||
} else if (this.masterBuffer.positionRead >= k.OBCIMasterBufferSize) { // Wrap around condition
|
||||
this.masterBuffer.positionRead = 0;
|
||||
console.log('Wrapped around');
|
||||
}
|
||||
this.masterBuffer.positionRead++;
|
||||
}
|
||||
console.log('aligned... new read position: ' + this.masterBuffer.positionRead + ' because start byte is ' + this.masterBuffer.buffer[this.masterBuffer.positionRead])
|
||||
};
|
||||
|
||||
OpenBCIBoard.prototype._reset = function() {
|
||||
this.masterBuffer = masterBufferMaker();
|
||||
this.searchingBuf = this.moneyBuf;
|
||||
this.searchingBuf = this.searchBuffers.miscStop;
|
||||
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
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntpGetOffset = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Sntp.offset(function(err, offset) {
|
||||
if(err) reject(err);
|
||||
resolve(offset);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get time from the SNTP server. Must have internet connection!
|
||||
* @returns {Promise} - Fulfilled with time object
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntpGetServerTime = function() {
|
||||
return new Promise((resolve,reject) => {
|
||||
Sntp.time(this.sntpOptions, (err, time) => {
|
||||
|
||||
if (err) {
|
||||
console.log('Failed: ' + err.message);
|
||||
reject(err);
|
||||
//process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Local clock is off by: ' + time.t + ' milliseconds');
|
||||
//process.exit(0);
|
||||
resolve(time);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Allows users to utilize all features of sntp if they want to...
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntp = Sntp;
|
||||
|
||||
/**
|
||||
* @description This gets the time plus offset
|
||||
*/
|
||||
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() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Sntp.start((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Stops the sntp from updating
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntpStop = function() {
|
||||
Sntp.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description This prints the total number of packets that were not able to be read
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
@@ -1130,29 +1326,6 @@ util.inherits(OpenBCIFactory, EventEmitter);
|
||||
module.exports = new OpenBCIFactory();
|
||||
|
||||
|
||||
/**
|
||||
* @description To apply the calculated raw value to the function
|
||||
* @param rawValue - A raw value of impedance
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
function impedanceTestApplyRaw(rawValue) {
|
||||
var indexOfChannel = this.impedanceTest.onChannel - 1;
|
||||
|
||||
// Subtract the 2.2k Ohm series resistor
|
||||
rawValue -= k.OBCIImpedanceSeriesResistor;
|
||||
|
||||
// Don't allow negative rawValues
|
||||
if (rawValue > 0) {
|
||||
if (this.impedanceTest.isTestingNInput) {
|
||||
this.impedanceArray[indexOfChannel].N.data.push(rawValue);
|
||||
}
|
||||
if (this.impedanceTest.isTestingPInput) {
|
||||
this.impedanceArray[indexOfChannel].P.data.push(rawValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description To parse a given channel given output from a print registers query
|
||||
* @param rawChannelBuffer
|
||||
|
||||
+129
-10
@@ -40,7 +40,10 @@ const kOBCIChannelOn_14 = 'Y';
|
||||
const kOBCIChannelOn_15 = 'U';
|
||||
const kOBCIChannelOn_16 = 'I';
|
||||
|
||||
/** Test Signal Control Commands */
|
||||
/** 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 kOBCITestSignalConnectToDC = 'p';
|
||||
const kOBCITestSignalConnectToGround = '0';
|
||||
const kOBCITestSignalConnectToPulse1xFast = '=';
|
||||
@@ -150,6 +153,11 @@ const kOBCIFilterEnable = 'f';
|
||||
/** Triggers */
|
||||
const kOBCITrigger = '`';
|
||||
|
||||
/** Sync Clocks */
|
||||
const kOBCISyncClockServerData = '<';
|
||||
const kOBCISyncClockStart = '>';
|
||||
const kOBCISyncClockStop = '.';
|
||||
|
||||
/** Possible number of channels */
|
||||
const kOBCINumberOfChannelsDaisy = 16;
|
||||
const kOBCINumberOfChannelsDefault = 8;
|
||||
@@ -160,6 +168,10 @@ const kOBCIBoardDaisy = 'daisy';
|
||||
const kOBCIBoardDefault = 'default';
|
||||
const kOBCIBoardGanglion = 'ganglion';
|
||||
|
||||
/** Possible Simulator Line Noise injections */
|
||||
const kOBCISimulatorLineNoiseHz60 = '60Hz';
|
||||
const kOBCISimulatorLineNoiseHz50 = '50Hz';
|
||||
const kOBCISimulatorLineNoiseNone = 'None';
|
||||
/** Possible Sample Rates*/
|
||||
const kOBCISampleRate125 = 125;
|
||||
const kOBCISampleRate250 = 250;
|
||||
@@ -167,6 +179,18 @@ const kOBCISampleRate250 = 250;
|
||||
/** Packet Size */
|
||||
const kOBCIPacketSize = 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 kOBCIPacketPositionStartByte = 0; // first byte
|
||||
const kOBCIPacketPositionStopByte = 32; // [32]
|
||||
const kOBCIPacketPositionStartAux = 26; // [26,27]:Aux 1 | [28,29]:Aux 2 | [30,31]:Aux 3
|
||||
const kOBCIPacketPositionStopAux = 31; // - - - [30,31]:Aux 3 | 32: Stop byte
|
||||
const kOBCIPacketPositionChannelDataStart = 2; // 0:startByte | 1:sampleNumber | [2:4] | [5:7] | [8:10] | [11:13] | [14:16] | [17:19] | [21:23] | [24:26]
|
||||
const kOBCIPacketPositionChannelDataStop = 25; // 24 bytes for channel data
|
||||
const kOBCIPacketPositionSampleNumber = 1;
|
||||
|
||||
/** Notable Bytes */
|
||||
const kOBCIByteStart = 0xA0;
|
||||
const kOBCIByteStop = 0xC0;
|
||||
@@ -178,10 +202,11 @@ const kErrorInvalidByteStop = "Invalid Stop Byte";
|
||||
const kErrorUndefinedOrNullInput = "Undefined or Null Input";
|
||||
|
||||
/** Max Master Buffer Size */
|
||||
const kOBCIMasterBufferSize = kOBCIPacketSize * 100;
|
||||
const kOBCIMasterBufferSize = 4096;
|
||||
|
||||
/** Impedance Calculation Variables */
|
||||
const kOBCILeadOffDriveInAmps = 0.000000006;
|
||||
const kOBCILeadOffFrequencyHz = 31.5;
|
||||
|
||||
/** Command send delay */
|
||||
const kOBCIWriteIntervalDelayMSLong = 50;
|
||||
@@ -205,7 +230,15 @@ const kOBCIImpedanceThresholdBadMax = 1000000;
|
||||
const kOBCIImpedanceSeriesResistor = 2200; // There is a 2.2 k Ohm series resistor that must be subtracted
|
||||
|
||||
/** Simulator */
|
||||
const kOBCISimulatorPortName = '/dev/tty.openBCISimulator';
|
||||
const kOBCISimulatorPortName = 'OpenBCISimulator';
|
||||
|
||||
/**
|
||||
* Raw data packet types/codes
|
||||
*/
|
||||
const kOBCIPacketTypeRawAux = 3; // 0011
|
||||
const kOBCIPacketTypeStandard = 0; // 0000
|
||||
const kOBCIPacketTypeTimeSynced = 1; // 0001
|
||||
const kOBCIPacketTypeUserDefined = 2; // 0010
|
||||
|
||||
module.exports = {
|
||||
/** Turning channels off */
|
||||
@@ -368,6 +401,36 @@ module.exports = {
|
||||
OBCITestSignalConnectToPulse1xSlow:kOBCITestSignalConnectToPulse1xSlow,
|
||||
OBCITestSignalConnectToPulse2xFast:kOBCITestSignalConnectToPulse2xFast,
|
||||
OBCITestSignalConnectToPulse2xSlow:kOBCITestSignalConnectToPulse2xSlow,
|
||||
getTestSignalCommand: (signal) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
switch (signal) {
|
||||
case 'dc':
|
||||
resolve(kOBCITestSignalConnectToDC);
|
||||
break;
|
||||
case 'ground':
|
||||
resolve(kOBCITestSignalConnectToGround);
|
||||
break;
|
||||
case 'pulse1xFast':
|
||||
resolve(kOBCITestSignalConnectToPulse1xFast);
|
||||
break;
|
||||
case 'pulse1xSlow':
|
||||
resolve(kOBCITestSignalConnectToPulse1xSlow);
|
||||
break;
|
||||
case 'pulse2xFast':
|
||||
resolve(kOBCITestSignalConnectToPulse2xFast);
|
||||
break;
|
||||
case 'pulse2xSlow':
|
||||
resolve(kOBCITestSignalConnectToPulse2xSlow);
|
||||
break;
|
||||
case 'none':
|
||||
resolve(kOBCIChannelDefaultAllSet);
|
||||
break;
|
||||
default:
|
||||
reject('Invalid selection! Check your spelling.');
|
||||
break;
|
||||
}
|
||||
})
|
||||
},
|
||||
/** Channel Setting Commands */
|
||||
OBCIChannelCmdADCNormal:kOBCIChannelCmdADCNormal,
|
||||
OBCIChannelCmdADCShorted:kOBCIChannelCmdADCShorted,
|
||||
@@ -412,6 +475,15 @@ module.exports = {
|
||||
OBCIChannelCmdSRB1Diconnect:kOBCIChannelCmdSRB1Diconnect,
|
||||
OBCIChannelCmdSRB2Connect:kOBCIChannelCmdSRB2Connect,
|
||||
OBCIChannelCmdSRB2Diconnect:kOBCIChannelCmdSRB2Diconnect,
|
||||
/** Channel Settings Object */
|
||||
channelSettingsObjectDefault: channelSettingsObjectDefault,
|
||||
channelSettingsArrayInit: (numberOfChannels) => {
|
||||
var newChannelSettingsArray = [];
|
||||
for (var i = 0; i < numberOfChannels; i++) {
|
||||
newChannelSettingsArray.push(channelSettingsObjectDefault(i));
|
||||
}
|
||||
return newChannelSettingsArray;
|
||||
},
|
||||
/** Channel Setting Helper Strings */
|
||||
OBCIStringADCNormal:kOBCIStringADCNormal,
|
||||
OBCIStringADCShorted:kOBCIStringADCShorted,
|
||||
@@ -537,6 +609,7 @@ module.exports = {
|
||||
OBCIMasterBufferSize:kOBCIMasterBufferSize,
|
||||
/** Impedance Calculation Variables */
|
||||
OBCILeadOffDriveInAmps:kOBCILeadOffDriveInAmps,
|
||||
OBCILeadOffFrequencyHz:kOBCILeadOffFrequencyHz,
|
||||
/** Channel Setter Maker */
|
||||
getChannelSetter:channelSetter,
|
||||
/** Impedance Setter Maker */
|
||||
@@ -545,6 +618,10 @@ module.exports = {
|
||||
OBCIWriteIntervalDelayMSLong:kOBCIWriteIntervalDelayMSLong,
|
||||
OBCIWriteIntervalDelayMSNone:kOBCIWriteIntervalDelayMSNone,
|
||||
OBCIWriteIntervalDelayMSShort:kOBCIWriteIntervalDelayMSShort,
|
||||
/** Sync Clocks */
|
||||
OBCISyncClockServerData:kOBCISyncClockServerData,
|
||||
OBCISyncClockStart:kOBCISyncClockStart,
|
||||
OBCISyncClockStop:kOBCISyncClockStop,
|
||||
/** Impedance */
|
||||
OBCIImpedanceTextBad:kOBCIImpedanceTextBad,
|
||||
OBCIImpedanceTextGood:kOBCIImpedanceTextGood,
|
||||
@@ -565,7 +642,28 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
/** Simulator */
|
||||
OBCISimulatorPortName:kOBCISimulatorPortName
|
||||
OBCISimulatorPortName:kOBCISimulatorPortName,
|
||||
/** Raw data packet types */
|
||||
OBCIPacketTypeRawAux:kOBCIPacketTypeRawAux,
|
||||
OBCIPacketTypeStandard:kOBCIPacketTypeStandard,
|
||||
OBCIPacketTypeTimeSynced:kOBCIPacketTypeTimeSynced,
|
||||
OBCIPacketTypeUserDefined:kOBCIPacketTypeUserDefined,
|
||||
/** fun funcs */
|
||||
isNumber:isNumber,
|
||||
isBoolean:isBoolean,
|
||||
isString:isString,
|
||||
/** OpenBCI V3 Standard Packet Positions */
|
||||
OBCIPacketPositionStartByte:kOBCIPacketPositionStartByte,
|
||||
OBCIPacketPositionStopByte:kOBCIPacketPositionStopByte,
|
||||
OBCIPacketPositionStartAux:kOBCIPacketPositionStartAux,
|
||||
OBCIPacketPositionStopAux:kOBCIPacketPositionStopAux,
|
||||
OBCIPacketPositionChannelDataStart:kOBCIPacketPositionChannelDataStart,
|
||||
OBCIPacketPositionChannelDataStop:kOBCIPacketPositionChannelDataStop,
|
||||
OBCIPacketPositionSampleNumber:kOBCIPacketPositionSampleNumber,
|
||||
/** Possible Simulator Line Noise injections */
|
||||
OBCISimulatorLineNoiseHz60:kOBCISimulatorLineNoiseHz60,
|
||||
OBCISimulatorLineNoiseHz50:kOBCISimulatorLineNoiseHz50,
|
||||
OBCISimulatorLineNoiseNone:kOBCISimulatorLineNoiseNone
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -635,6 +733,16 @@ function channelSetter(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) {
|
||||
// Set SRB1
|
||||
cmdSrb1 = srb1 ? kOBCIChannelCmdSRB1Connect : kOBCIChannelCmdSRB1Diconnect;
|
||||
|
||||
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 = [
|
||||
kOBCIChannelCmdSet,
|
||||
@@ -648,7 +756,7 @@ function channelSetter(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) {
|
||||
kOBCIChannelCmdLatch
|
||||
];
|
||||
//console.log(outputArray);
|
||||
resolve(outputArray);
|
||||
resolve(outputArray,newChannelSettingsObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -671,24 +779,24 @@ function impedanceSetter(channelNumber,pInputApplied,nInputApplied) {
|
||||
if (!isBoolean(pInputApplied)) reject('pInputApplied must be of type \'boolean\' ');
|
||||
if (!isBoolean(nInputApplied)) reject('nInputApplied must be of type \'boolean\' ');
|
||||
|
||||
// Set nInputApplied
|
||||
cmdNInputApplied = nInputApplied ? kOBCIChannelImpedanceTestSignalApplied : kOBCIChannelImpedanceTestSignalAppliedNot;
|
||||
|
||||
// Set pInputApplied
|
||||
cmdPInputApplied = pInputApplied ? kOBCIChannelImpedanceTestSignalApplied : kOBCIChannelImpedanceTestSignalAppliedNot;
|
||||
|
||||
// Set nInputApplied
|
||||
cmdNInputApplied = nInputApplied ? kOBCIChannelImpedanceTestSignalApplied : kOBCIChannelImpedanceTestSignalAppliedNot;
|
||||
|
||||
// Set Channel Number
|
||||
commandChannelForCmd(channelNumber).then(command => {
|
||||
var outputArray = [
|
||||
kOBCIChannelImpedanceSet,
|
||||
command,
|
||||
cmdNInputApplied,
|
||||
cmdPInputApplied,
|
||||
cmdNInputApplied,
|
||||
kOBCIChannelImpedanceLatch
|
||||
];
|
||||
//console.log(outputArray);
|
||||
resolve(outputArray);
|
||||
}).catch(err => reject(err));;
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -823,4 +931,15 @@ function commandChannelForCmd(channelNumber) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
function channelSettingsObjectDefault(channelNumber) {
|
||||
return {
|
||||
channelNumber:channelNumber,
|
||||
powerDown: false,
|
||||
gain: 24,
|
||||
inputType: kOBCIStringADCNormal,
|
||||
bias: true,
|
||||
srb2: true,
|
||||
srb1: false
|
||||
};
|
||||
}
|
||||
+332
-93
@@ -1,65 +1,71 @@
|
||||
'use strict';
|
||||
var gaussian = require('gaussian');
|
||||
var outliers = require('outliers');
|
||||
var stats = require('scientific-statistics');
|
||||
|
||||
var k = require('./openBCIConstants');
|
||||
|
||||
/** Constants for interpreting the EEG data */
|
||||
// Reference voltage for ADC in ADS1299.
|
||||
// Set by its hardware.
|
||||
const ADS1299_VREF = 4.5;
|
||||
// Assumed gain setting for ADS1299.
|
||||
// Set by its Arduino code.
|
||||
const ADS1299_GAIN = 24.0;
|
||||
// Scale factor for aux data
|
||||
const SCALE_FACTOR_ACCEL = 0.002 / Math.pow(2,4);
|
||||
// Scale factor for channelData
|
||||
const SCALE_FACTOR_CHANNEL = ADS1299_VREF / ADS1299_GAIN / (Math.pow(2,23) - 1);
|
||||
// X, Y, Z
|
||||
const ACCEL_NUMBER_AXIS = 3;
|
||||
// Default ADS1299 gains array
|
||||
|
||||
// For computing Goertzel Algorithm
|
||||
// See: http://www.embedded.com/design/configurable-systems/4024443/The-Goertzel-Algorithm
|
||||
// In the tutorial cited above, GOERTZEL_BLOCK_SIZE is referred to as N
|
||||
const GOERTZEL_BLOCK_SIZE = 62;
|
||||
const GOERTZEL_K_250 = Math.floor(0.5 + ((GOERTZEL_BLOCK_SIZE * k.OBCILeadOffFrequencyHz) / k.OBCISampleRate250));
|
||||
const GOERTZEL_W_250 = ((2 * Math.PI) / GOERTZEL_BLOCK_SIZE) * GOERTZEL_K_250;
|
||||
const GOERTZEL_COEFF_250 = 2 * Math.cos(GOERTZEL_W_250);
|
||||
// TODO: Add support for 16 channel Daisy board
|
||||
|
||||
|
||||
var k = require('./openBCIConstants');
|
||||
var sampleModule = {
|
||||
|
||||
module.exports = {
|
||||
convertPacketToSample: function (dataBuf) {
|
||||
var self = this;
|
||||
if(dataBuf === undefined || dataBuf === null) {
|
||||
return;
|
||||
}
|
||||
var numberOfBytes = dataBuf.byteLength;
|
||||
var scaleData = true;
|
||||
/**
|
||||
* @description This takes a 33 byte packet and converts it based on the last four bits.
|
||||
* 0000 - Standard OpenBCI V3 Sample Packet
|
||||
* @param dataBuf
|
||||
* @param channelSettingsArray
|
||||
* @returns {Promise}
|
||||
*/
|
||||
parseRawPacket: (dataBuf,channelSettingsArray) => {
|
||||
const defaultChannelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (dataBuf === undefined || dataBuf === null) reject('Error [parseRawPacket]: dataBuf must be defined.');
|
||||
// Verify proper start byte
|
||||
if (dataBuf[0] != k.OBCIByteStart) reject('Error [parseRawPacket]: Invalid start byte of ' + dataBuf[0].toString(16) + ' expected ' + k.OBCIByteStart.toString(16));
|
||||
// channelSettingsArray is optional, defaults to CHANNEL_SETTINGS_ARRAY_DEFAULT
|
||||
channelSettingsArray = channelSettingsArray || defaultChannelSettingsArray;
|
||||
// Last nibble contains packet type
|
||||
var packetType = getRawPacketType(dataBuf[k.OBCIPacketPositionStopByte]);
|
||||
|
||||
if (numberOfBytes != k.OBCIPacketSize) return;
|
||||
if (dataBuf[0] != k.OBCIByteStart) return;
|
||||
if (dataBuf[32] != k.OBCIByteStop) return;
|
||||
|
||||
var channelData = function () {
|
||||
var out = [];
|
||||
var count = 0;
|
||||
for (var i = 2; i <= numberOfBytes - 10; i += 3) {
|
||||
var number = self.interpret24bitAsInt32(dataBuf.slice(i, i + 3));
|
||||
out.push(number * SCALE_FACTOR_CHANNEL);
|
||||
count++;
|
||||
switch (packetType) {
|
||||
case k.OBCIPacketTypeUserDefined:
|
||||
// Do something with the packet, maybe nothing
|
||||
resolve();
|
||||
break;
|
||||
case k.OBCIPacketTypeTimeSynced:
|
||||
// Parse the time synced packet
|
||||
break;
|
||||
default: //normal packet
|
||||
parsePacketStandard(dataBuf,channelSettingsArray).then(sampleObject => {
|
||||
resolve(sampleObject);
|
||||
}).catch(err => reject(err));
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
var auxData = function () {
|
||||
var out = [];
|
||||
var count = 0;
|
||||
for (var i = numberOfBytes - 7; i < numberOfBytes - 1; i += 2) {
|
||||
out.push(scaleData ? self.interpret16bitAsInt32(dataBuf.slice(i, i + 2)) * SCALE_FACTOR_ACCEL : self.interpret16bitAsInt32(dataBuf.slice(i, i + 2)));
|
||||
count++;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
return {
|
||||
startByte: dataBuf[0], // byte
|
||||
sampleNumber: dataBuf[1], // byte
|
||||
channelData: channelData(), // multiple of 3 bytes
|
||||
auxData: auxData(), // multiple of 2 bytes
|
||||
stopByte: dataBuf[numberOfBytes - 1] // byte
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
convertSampleToPacket: function(sample) {
|
||||
var packetBuffer = new Buffer(k.OBCIPacketSize);
|
||||
packetBuffer.fill(0);
|
||||
@@ -106,6 +112,31 @@ module.exports = {
|
||||
console.log('---- Stop Byte: ' + sample.stopByte);
|
||||
}
|
||||
},
|
||||
samplePrintHeader: function() {
|
||||
return (
|
||||
'All voltages in Volts!' +
|
||||
'sampleNumber, channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8, aux1, aux2, aux3\n');
|
||||
},
|
||||
samplePrintLine: function(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'
|
||||
);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description Convert float number into three byte buffer. This is the opposite of .interpret24bitAsInt32()
|
||||
* @param float - The number you want to convert
|
||||
@@ -115,7 +146,7 @@ module.exports = {
|
||||
var intBuf = new Buffer(3); // 3 bytes for 24 bits
|
||||
intBuf.fill(0); // Fill the buffer with 0s
|
||||
|
||||
var temp = float / SCALE_FACTOR_CHANNEL; // Convert to counts
|
||||
var temp = float / ( ADS1299_VREF / 24 / (Math.pow(2,23) - 1)); // Convert to counts
|
||||
|
||||
temp = Math.floor(temp); // Truncate counts number
|
||||
|
||||
@@ -148,11 +179,11 @@ module.exports = {
|
||||
return intBuf;
|
||||
},
|
||||
/**
|
||||
* Purpose: Calculate the impedance for one channel only.
|
||||
* @description Calculate the impedance for one channel only.
|
||||
* @param sampleObject - Standard OpenBCI sample object
|
||||
* @param channelNumber - Number, the channel you want to calculate impedance for.
|
||||
* @returns {Promise} - Fullfilled with impedance vaule for the specified channel.
|
||||
* Author: AJ Keller
|
||||
* @returns {Promise} - Fulfilled with impedance value for the specified channel.
|
||||
* @author AJ Keller
|
||||
*/
|
||||
impedanceCalculationForChannel: function(sampleObject,channelNumber) {
|
||||
const sqrt2 = Math.sqrt(2);
|
||||
@@ -167,9 +198,39 @@ module.exports = {
|
||||
sampleObject.channelData[index] *= -1;
|
||||
}
|
||||
var impedance = (sqrt2 * sampleObject.channelData[index]) / k.OBCILeadOffDriveInAmps;
|
||||
//if (index === 0) console.log("Voltage: " + (sqrt2*sampleObject.channelData[index]) + " leadoff amps: " + k.OBCILeadOffDriveInAmps + " impedance: " + impedance);
|
||||
resolve(impedance);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description Calculate the impedance for all channels.
|
||||
* @param sampleObject - Standard OpenBCI sample object
|
||||
* @returns {Promise} - Fulfilled with impedances for the sample
|
||||
* @author AJ Keller
|
||||
*/
|
||||
impedanceCalculationForAllChannels: function(sampleObject) {
|
||||
const sqrt2 = Math.sqrt(2);
|
||||
return new Promise((resolve,reject) => {
|
||||
if(sampleObject === undefined || sampleObject === null) reject('Sample Object cannot be null or undefined');
|
||||
if(sampleObject.channelData === undefined || sampleObject.channelData === null) reject('Channel cannot be null or undefined');
|
||||
|
||||
var sampleImpedances = [];
|
||||
var numChannels = sampleObject.channelData.length;
|
||||
for (var index = 0;index < numChannels; index++) {
|
||||
if (sampleObject.channelData[index] < 0) {
|
||||
sampleObject.channelData[index] *= -1;
|
||||
}
|
||||
var impedance = (sqrt2 * sampleObject.channelData[index]) / k.OBCILeadOffDriveInAmps;
|
||||
sampleImpedances.push(impedance);
|
||||
|
||||
//if (index === 0) console.log("Voltage: " + (sqrt2*sampleObject.channelData[index]) + " leadoff amps: " + k.OBCILeadOffDriveInAmps + " impedance: " + impedance);
|
||||
}
|
||||
|
||||
sampleObject.impedances = sampleImpedances;
|
||||
|
||||
resolve(sampleObject);
|
||||
});
|
||||
},
|
||||
interpret16bitAsInt32: function(twoByteBuffer) {
|
||||
var prefix = 0;
|
||||
|
||||
@@ -200,14 +261,10 @@ module.exports = {
|
||||
},
|
||||
impedanceObject: newImpedanceObject,
|
||||
impedanceSummarize: (singleInputObject) => {
|
||||
var median = stats.median(singleInputObject.data);
|
||||
if (median > k.OBCIImpedanceThresholdBadMax) { // The case for no load (super high impedance)
|
||||
singleInputObject.average = median;
|
||||
if (singleInputObject.raw > k.OBCIImpedanceThresholdBadMax) { // The case for no load (super high impedance)
|
||||
singleInputObject.text = k.OBCIImpedanceTextNone;
|
||||
} else {
|
||||
var cleanedData = singleInputObject.data.filter(outliers()); // Remove outliers
|
||||
singleInputObject.average = stats.mean(cleanedData); // Get average numerical impedance
|
||||
singleInputObject.text = k.getTextForRawImpedance(singleInputObject.average); // Get textual impedance
|
||||
singleInputObject.text = k.getTextForRawImpedance(singleInputObject.raw); // Get textual impedance
|
||||
}
|
||||
},
|
||||
newSample: function() {
|
||||
@@ -219,14 +276,20 @@ module.exports = {
|
||||
stopByte: k.OBCIByteStop
|
||||
}
|
||||
},
|
||||
randomSample: function(numberOfChannels,sampleRateHz) {
|
||||
/**
|
||||
* @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
|
||||
* @param sampleRateHz
|
||||
* @param injectAlpha
|
||||
* @param lineNoise
|
||||
* @returns {Function}
|
||||
*/
|
||||
randomSample: function(numberOfChannels,sampleRateHz, injectAlpha,lineNoise) {
|
||||
var self = this;
|
||||
const distribution = gaussian(0,2);
|
||||
const distribution = gaussian(0,1);
|
||||
const sineWaveFreqHz10 = 10;
|
||||
const sineWaveFreqHz50 = 50;
|
||||
const sineWaveFreqHz60 = 60;
|
||||
const pi = Math.PI;
|
||||
const sqrt2 = Math.sqrt(2);
|
||||
const uVolts = 1000000;
|
||||
|
||||
var sinePhaseRad = new Array(numberOfChannels+1); //prevent index error with '+1'
|
||||
@@ -234,41 +297,58 @@ module.exports = {
|
||||
|
||||
var auxData = [0,0,0];
|
||||
|
||||
// Init arrays to hold coefficients for each channel
|
||||
var b0 = new Array(numberOfChannels);
|
||||
var b1 = new Array(numberOfChannels);
|
||||
var b2 = new Array(numberOfChannels);
|
||||
|
||||
// Init coefficients to 0
|
||||
b0.fill(0);
|
||||
b1.fill(0);
|
||||
b2.fill(0);
|
||||
|
||||
return function(previousSampleNumber) {
|
||||
var newSample = self.newSample();
|
||||
|
||||
//console.log('New Sample: ' + JSON.stringify(newSample));
|
||||
|
||||
var whiteNoise;
|
||||
for(var i = 0; i < numberOfChannels; i++) { //channels are 0 indexed
|
||||
newSample.channelData[i] = distribution.ppf(Math.random())*Math.sqrt(sampleRateHz/2)/uVolts;
|
||||
// This produces white noise
|
||||
whiteNoise = distribution.ppf(Math.random()) * Math.sqrt(sampleRateHz/2)/uVolts;
|
||||
|
||||
switch (i) {
|
||||
case 1: // scale first channel higher
|
||||
newSample.channelData[i] *= 10;
|
||||
break;
|
||||
case 2:
|
||||
sinePhaseRad[i] += 2 * pi * sineWaveFreqHz10 / sampleRateHz;
|
||||
if (sinePhaseRad[i] > 2 * pi) {
|
||||
sinePhaseRad[i] -= 2 * pi;
|
||||
case 0: // Add 10Hz signal to channel 1... briany
|
||||
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;
|
||||
}
|
||||
newSample.channelData[i] += (10 * sqrt2 * Math.sin(sinePhaseRad[i]))/uVolts;
|
||||
break;
|
||||
case 3:
|
||||
sinePhaseRad[i] += 2 * pi * sineWaveFreqHz50 / sampleRateHz;
|
||||
if (sinePhaseRad[i] > 2 * pi) {
|
||||
sinePhaseRad[i] -= 2 * pi;
|
||||
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;
|
||||
}
|
||||
newSample.channelData[i] += (50 * sqrt2 * Math.sin(sinePhaseRad[i]))/uVolts;
|
||||
break;
|
||||
case 4:
|
||||
sinePhaseRad[i] += 2 * pi * sineWaveFreqHz60 / sampleRateHz;
|
||||
if (sinePhaseRad[i] > 2 * pi) {
|
||||
sinePhaseRad[i] -= 2 * pi;
|
||||
}
|
||||
newSample.channelData[i] += (50 * sqrt2 * Math.sin(sinePhaseRad[i]))/uVolts;
|
||||
break;
|
||||
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
newSample.channelData[i] = b0[i] + b1[i] + b2[i] + whiteNoise * 0.1848;
|
||||
}
|
||||
if (previousSampleNumber == 255) {
|
||||
newSample.sampleNumber = 0;
|
||||
@@ -281,22 +361,181 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
scaleFactorAux: SCALE_FACTOR_ACCEL,
|
||||
scaleFactorChannel: SCALE_FACTOR_CHANNEL,
|
||||
k:k
|
||||
k:k,
|
||||
/**
|
||||
* @description Use the Goertzel algorithm to calculate impedances
|
||||
* @param sample - a sample with channelData Array
|
||||
* @param goertzelObj - An object that was created by a call to this.goertzelNewObject()
|
||||
* @returns {Array} - Returns an array if finished computing
|
||||
*/
|
||||
goertzelProcessSample: (sample,goertzelObj) => {
|
||||
// calculate the goertzel values for all channels
|
||||
for (var i = 0; i < goertzelObj.numberOfChannels; i++) {
|
||||
var q0 = GOERTZEL_COEFF_250 * goertzelObj.q1[i] - goertzelObj.q2[i] + sample.channelData[i];
|
||||
goertzelObj.q2[i] = goertzelObj.q1[i];
|
||||
goertzelObj.q1[i] = q0;
|
||||
|
||||
//console.log('Q1: ' + goertzelObj.q1[i] + ' Q2: ' + goertzelObj.q2[i]);
|
||||
}
|
||||
|
||||
|
||||
// Increment the index counter
|
||||
goertzelObj.index++;
|
||||
|
||||
|
||||
// Have we iterated more times then block size?
|
||||
if (goertzelObj.index > GOERTZEL_BLOCK_SIZE) {
|
||||
var impedanceArray = [];
|
||||
for (var j = 0; j < goertzelObj.numberOfChannels; j++) {
|
||||
// Calculate the magnitude of the voltage
|
||||
var q1SQRD = goertzelObj.q1[j] * goertzelObj.q1[j];
|
||||
var q2SQRD = goertzelObj.q2[j] * goertzelObj.q2[j];
|
||||
var lastPart = goertzelObj.q1[j] * goertzelObj.q2[j] * GOERTZEL_COEFF_250;
|
||||
|
||||
//console.log('Chan ' + j + ', Q1^2: ' + q1SQRD + ', Q2^2: ' + q2SQRD + ', Last Part: ' + lastPart);
|
||||
|
||||
var voltage = Math.sqrt((goertzelObj.q1[j] * goertzelObj.q1[j]) + (goertzelObj.q2[j] * goertzelObj.q2[j]) - goertzelObj.q1[j] * goertzelObj.q2[j] * GOERTZEL_COEFF_250);
|
||||
|
||||
// Calculate the impedance r = v/i
|
||||
var impedance = voltage / k.OBCILeadOffDriveInAmps;
|
||||
// Push the impedance into the final array
|
||||
impedanceArray.push(impedance);
|
||||
|
||||
// Reset the goertzel variables to get ready for the next iteration
|
||||
goertzelObj.q1[j] = 0;
|
||||
goertzelObj.q2[j] = 0;
|
||||
}
|
||||
|
||||
// Reset the goertzel index counter
|
||||
goertzelObj.index = 0;
|
||||
|
||||
// Pass out the impedance array
|
||||
return impedanceArray;
|
||||
} else {
|
||||
// This reject is really just for debugging
|
||||
return;
|
||||
}
|
||||
},
|
||||
goertzelNewObject: (numberOfChannels) => {
|
||||
// Object to help calculate the goertzel
|
||||
var q1 = [];
|
||||
var q2 = [];
|
||||
for (var i = 0; i < numberOfChannels; i++) {
|
||||
q1.push(0);
|
||||
q2.push(0);
|
||||
}
|
||||
return {
|
||||
q1: q1,
|
||||
q2: q2,
|
||||
index: 0,
|
||||
numberOfChannels: numberOfChannels
|
||||
}
|
||||
},
|
||||
GOERTZEL_BLOCK_SIZE:GOERTZEL_BLOCK_SIZE
|
||||
};
|
||||
|
||||
module.exports = sampleModule;
|
||||
|
||||
function newImpedanceObject(channelNumber) {
|
||||
return {
|
||||
channel: channelNumber,
|
||||
P: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: k.OBCIImpedanceTextInit
|
||||
},
|
||||
N: {
|
||||
data: [],
|
||||
average: -1,
|
||||
raw: -1,
|
||||
text: k.OBCIImpedanceTextInit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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: Array of floats of accel data
|
||||
* sampleNumber: a Number that is the sample
|
||||
* }
|
||||
*/
|
||||
function parsePacketStandard(dataBuf, channelSettingsArray) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (dataBuf.byteLength != k.OBCIPacketSize) reject("Error [parsePacketStandard]: input buffer must be " + k.OBCIPacketSize + " bytes!");
|
||||
|
||||
var sampleObject = {};
|
||||
// Need build the standard sample object
|
||||
getAccelDataArray(dataBuf.slice(k.OBCIPacketPositionStartAux,k.OBCIPacketPositionStopAux+1))
|
||||
.then(accelData => {
|
||||
sampleObject.auxData = accelData;
|
||||
return getChannelDataArray(dataBuf.slice(k.OBCIPacketPositionChannelDataStart,k.OBCIPacketPositionChannelDataStop+1), channelSettingsArray);
|
||||
})
|
||||
.then(channelSettingArray => {
|
||||
sampleObject.channelData = channelSettingArray;
|
||||
// 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];
|
||||
resolve(sampleObject);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
//reject(err);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @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 {Promise} - Fulfilled with Array of floats 3 elements long
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
function getAccelDataArray(dataBuf) {
|
||||
return new Promise(resolve => {
|
||||
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);
|
||||
}
|
||||
resolve(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 with 24 bit signed integers, number of elements is same as channelSettingsArray.length * 3
|
||||
* @param channelSettingsArray - The channel settings array, see OpenBCIConstants.channelSettingsArrayInit() for specs
|
||||
* @returns {Promise} - Fulfilled with Array filled with floats for each channel's voltage in VOLTS
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
function getChannelDataArray(dataBuf, channelSettingsArray) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!Array.isArray(channelSettingsArray)) reject('Error [getChannelDataArray]: Channel Settings must be an array!');
|
||||
var channelData = [];
|
||||
// Iterate through each object in the array
|
||||
channelSettingsArray.forEach((channelSettingsObject, index) => {
|
||||
if (!channelSettingsObject.hasOwnProperty('gain')) reject('Error [getChannelDataArray]: Invalid channel settings object at index ' + index);
|
||||
if (!k.isNumber(channelSettingsObject.gain)) reject('Error [getChannelDataArray]: Property gain of channelSettingsObject not or type Number');
|
||||
// Get scale factor
|
||||
var scaleFactor = ADS1299_VREF / channelSettingsObject.gain / (Math.pow(2,23) - 1);
|
||||
// Each number is 3 bytes, need to traverse index * 3 in the buffer
|
||||
index *= 3;
|
||||
// Convert the three byte signed integer and convert it
|
||||
channelData.push(scaleFactor * sampleModule.interpret24bitAsInt32(dataBuf.slice(index, index + 3)));
|
||||
});
|
||||
resolve(channelData);
|
||||
});
|
||||
}
|
||||
function getRawPacketType(stopByte) {
|
||||
return stopByte & 0xF;
|
||||
}
|
||||
|
||||
+76
-10
@@ -6,6 +6,7 @@ var stream = require('stream');
|
||||
|
||||
var openBCISample = require('./openBCISample');
|
||||
var k = openBCISample.k;
|
||||
var now = require('performance-now');
|
||||
|
||||
|
||||
function OpenBCISimulatorFactory() {
|
||||
@@ -16,7 +17,9 @@ function OpenBCISimulatorFactory() {
|
||||
var _options = {
|
||||
samplerate: 250,
|
||||
daisy: false,
|
||||
verbose: false
|
||||
verbose: false,
|
||||
alpha: true,
|
||||
lineNoise: '60Hz'
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +33,8 @@ function OpenBCISimulatorFactory() {
|
||||
/** Configuring Options */
|
||||
opts.sampleRate = options.sampleRate || options.samplerate || _options.samplerate;
|
||||
opts.daisy = options.daisy || _options.daisy;
|
||||
opts.lineNoise = options.lineNoise || options.linenoise || _options.lineNoise;
|
||||
opts.alpha = options.alpha || _options.alpha;
|
||||
opts.verbose = options.verbose || _options.verbose;
|
||||
|
||||
this.options = opts;
|
||||
@@ -40,6 +45,18 @@ function OpenBCISimulatorFactory() {
|
||||
this.buffer = new Buffer(500);
|
||||
// Numbers
|
||||
this.sampleNumber = -1; // So the first sample is 0
|
||||
// Objects
|
||||
this.time = {
|
||||
current: 0,
|
||||
start: now(),
|
||||
loop: null,
|
||||
ntp0: 0,
|
||||
ntp1: 0,
|
||||
ntp2: 0,
|
||||
ntp3: 0
|
||||
};
|
||||
console.log('Simulator started at time: ' + this.time.start);
|
||||
console.log('Time board has been running: ' + (now() - this.time.start));
|
||||
// Strings
|
||||
this.portName = portName || k.OBCISimulatorPortName;
|
||||
|
||||
@@ -66,13 +83,30 @@ function OpenBCISimulatorFactory() {
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype.write = function(data,callback) {
|
||||
if (data[0] === k.OBCIStreamStart) {
|
||||
if (!this.stream) this._startStream();
|
||||
} else if (data[0] === k.OBCIStreamStop) {
|
||||
if (this.stream) clearInterval(this.stream); // Stops the stream
|
||||
} else if (data[0] === k.OBCIMiscSoftReset) {
|
||||
if (this.stream) clearInterval(this.stream);
|
||||
this.emit('data', new Buffer('OpenBCI Board Simulator\nPush The World V-0.2\n$$$'));
|
||||
|
||||
switch (data[0]) {
|
||||
case k.OBCIStreamStart:
|
||||
if (!this.stream) this._startStream();
|
||||
break;
|
||||
case k.OBCIStreamStop:
|
||||
if (this.stream) clearInterval(this.stream); // Stops the stream
|
||||
break;
|
||||
case k.OBCIMiscSoftReset:
|
||||
if (this.stream) clearInterval(this.stream);
|
||||
this.emit('data', new Buffer('OpenBCI Board Simulator\nPush The World V-0.2\n$$$'));
|
||||
break;
|
||||
case k.OBCISyncClockStart:
|
||||
setTimeout(() => {
|
||||
if (this.options.verbose) console.log('Recieved sync command');
|
||||
this._syncStart();
|
||||
}, 10);
|
||||
break;
|
||||
case k.OBCISyncClockServerData:
|
||||
this.time.ntp3 = this.time.current;
|
||||
this._syncUp(data.slice(1));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** Handle Callback */
|
||||
@@ -100,7 +134,7 @@ function OpenBCISimulatorFactory() {
|
||||
|
||||
if (intervalInMS < 2) intervalInMS = 2;
|
||||
|
||||
var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250);
|
||||
var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250, this.options.alpha, this.options.lineNoise);
|
||||
|
||||
var getNewPacket = sampNumber => {
|
||||
return openBCISample.convertSampleToPacket(generateSample(sampNumber));
|
||||
@@ -112,10 +146,42 @@ function OpenBCISimulatorFactory() {
|
||||
}, intervalInMS);
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._syncStart = function() {
|
||||
|
||||
this.time.ntp0 = now();
|
||||
var buffer = new Buffer('$a$' + this.time.ntp0);
|
||||
this.emit('data',buffer);
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._syncUp = function(data) {
|
||||
// get the first number
|
||||
console.log(data.length);
|
||||
var halfwayPoint = (data.length / 2);
|
||||
this.time.ntp1 = parseFloat(data.slice(0,halfwayPoint-1));
|
||||
this.time.ntp2 = parseFloat(data.slice(halfwayPoint));
|
||||
console.log('ntp1: ' + this.time.ntp1 + ' ntp2: ' + this.time.ntp2);
|
||||
|
||||
var timeSpentOnNetwork = this.time.ntp3 - this.time.ntp0 - (this.time.ntp2 - this.time.ntp1);
|
||||
|
||||
var transferTime = timeSpentOnNetwork / 2;
|
||||
|
||||
var trueTime = this.time.ntp2 + transferTime;
|
||||
|
||||
var delta = trueTime - this.time.ntp3;
|
||||
console.log('Delta: ' + delta);
|
||||
|
||||
this.time.start += delta;
|
||||
|
||||
|
||||
|
||||
this.emit('data','Synced!' + '$$$');
|
||||
|
||||
};
|
||||
|
||||
factory.OpenBCISimulator = OpenBCISimulator;
|
||||
|
||||
}
|
||||
|
||||
util.inherits(OpenBCISimulatorFactory, EventEmitter);
|
||||
|
||||
module.exports = new OpenBCISimulatorFactory();
|
||||
module.exports = new OpenBCISimulatorFactory();
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openbci-sdk",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.4",
|
||||
"description": "A fully NodeJS based API for the OpenBCI board connecting to the hardware directly over serial",
|
||||
"main": "openBCIBoard",
|
||||
"scripts": {
|
||||
@@ -17,9 +17,9 @@
|
||||
"dependencies": {
|
||||
"gaussian": "^1.0.0",
|
||||
"istanbul": "^0.4.2",
|
||||
"outliers": "0.0.3",
|
||||
"scientific-statistics": "^0.2.7",
|
||||
"serialport": "^2.0.2"
|
||||
"performance-now": "^0.2.0",
|
||||
"serialport": "~2.1.0",
|
||||
"sntp": "^2.0.0"
|
||||
},
|
||||
"directories": {
|
||||
"test": "test"
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
/**
|
||||
* Created by AJ Keller
|
||||
* Date: 12/23/15
|
||||
* Purpose: To unit test the OpenBCIBoard file
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var OpenBCIBoard = require('../openBCIBoard');
|
||||
var OpenBCISample = OpenBCIBoard.OpenBCISample;
|
||||
var k = OpenBCIBoard.OpenBCIConstants;
|
||||
var chai = require('chai')
|
||||
, expect = chai.expect
|
||||
, should = chai.should();
|
||||
var chaiAsPromised = require("chai-as-promised");
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
var sampleSelf = function() {
|
||||
return {
|
||||
masterBuffer: {
|
||||
buffer: new Buffer(3300),
|
||||
positionRead:0,
|
||||
positionWrite:0,
|
||||
packetsIn:0,
|
||||
packetsRead:0,
|
||||
looseBytes:0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var sampleData;
|
||||
var bciBoard;
|
||||
|
||||
var samplePacket = function () {
|
||||
var byteSample = 0x45;
|
||||
var buffy = new Buffer([0xA0,byteSample,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,0xC0]);
|
||||
return buffy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
describe('OpenBCIBoard',function() {
|
||||
describe('#_bufMerger',function() {
|
||||
beforeEach(function() {
|
||||
bciBoard = new OpenBCIBoard.OpenBCIBoard();
|
||||
sampleData = [];
|
||||
(function(){
|
||||
for(var i = 0;i < 100;i++) {
|
||||
var sample = samplePacket();
|
||||
sample.sampleNumber = i;
|
||||
sampleData[i] = sample;
|
||||
}
|
||||
}());
|
||||
});
|
||||
it('should write the buffer to the empty master buffer', function() {
|
||||
bciBoard._bufMerger(sampleData[0]);
|
||||
|
||||
// test to see if buffers match
|
||||
assert.equal(0,sampleData[0].compare(bciBoard.masterBuffer.buffer.slice(0, k.OBCIPacketSize)));
|
||||
// test to make sure the write position was moved
|
||||
assert.equal(k.OBCIPacketSize, bciBoard.masterBuffer.positionWrite);
|
||||
// test to make sure we read in the right number of packets
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,1);
|
||||
// test to make sure there are no loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,0);
|
||||
});
|
||||
it('should write the tiny buffer to the empty master buffer, but not record a packet in', function() {
|
||||
var tinyBufferSize = 10; //tiny because its smaller than a packet
|
||||
var tinyBuffer = sampleData[0].slice(0,tinyBufferSize);
|
||||
|
||||
bciBoard._bufMerger(tinyBuffer);
|
||||
|
||||
// test to see if buffers match
|
||||
assert.equal(0,tinyBuffer.compare(bciBoard.masterBuffer.buffer.slice(0, tinyBufferSize)));
|
||||
// test to make sure the write position was moved
|
||||
assert.equal(tinyBufferSize, bciBoard.masterBuffer.positionWrite);
|
||||
// test to make sure we read in the right number of packets
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,0);
|
||||
// test to make sure there are 10 loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,tinyBufferSize);
|
||||
});
|
||||
it('should write the multi packet plus loose byte buffer to the empty master buffer', function() {
|
||||
var tinyBufferSize = 10; //tiny because its smaller than a packet
|
||||
var tinyBuffer = sampleData[0].slice(0,tinyBufferSize);
|
||||
|
||||
var buffers = [sampleData[0],sampleData[1],sampleData[2],tinyBuffer];
|
||||
var totalLength = k.OBCIPacketSize * 3 + tinyBufferSize;
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer);
|
||||
|
||||
// test to see if buffers match
|
||||
assert.equal(0,multiPacketBuffer.compare(bciBoard.masterBuffer.buffer.slice(0, totalLength)));
|
||||
// test to make sure the write position was moved
|
||||
assert.equal(totalLength, bciBoard.masterBuffer.positionWrite);
|
||||
// test to make sure we read in the right number of packets (shall be three)
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,3);
|
||||
// test to make sure there are 10 loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,tinyBufferSize);
|
||||
});
|
||||
it('should use looseBytes in to write the multi packet to the master buffer and record an extra packet in', function() {
|
||||
var tinyBufferSize = 10; //tiny because its smaller than a packet
|
||||
var tinyBuffer = sampleData[0].slice(0,tinyBufferSize);
|
||||
|
||||
var hackedBufferSize = k.OBCIPacketSize - tinyBufferSize;
|
||||
var hackedBuffer = sampleData[0].slice(tinyBufferSize);
|
||||
|
||||
bciBoard.masterBuffer.looseBytes = tinyBufferSize;
|
||||
|
||||
var buffers = [hackedBuffer,sampleData[0],sampleData[1],sampleData[2]];
|
||||
var totalLength = k.OBCIPacketSize * 3 + hackedBufferSize;
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer);
|
||||
|
||||
// test to see if buffers match
|
||||
assert.equal(0,multiPacketBuffer.compare(bciBoard.masterBuffer.buffer.slice(0, totalLength)));
|
||||
// test to make sure the write position was moved
|
||||
assert.equal(totalLength, bciBoard.masterBuffer.positionWrite);
|
||||
// test to make sure we read in the right number of packets (shall be three)
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,4);
|
||||
// test to make sure there are 10 loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,0);
|
||||
});
|
||||
it('should write in the position of the masterBufferPositionWrite', function() {
|
||||
var originalWritePosition = 69;
|
||||
bciBoard.masterBuffer.positionWrite = originalWritePosition;
|
||||
|
||||
bciBoard._bufMerger(sampleData[0]);
|
||||
|
||||
// test to see if the master buffer contains the correct data
|
||||
assert.equal(0,sampleData[0].compare(bciBoard.masterBuffer.buffer.slice(originalWritePosition, originalWritePosition + k.OBCIPacketSize)));
|
||||
// test to see if the write position was moved properly
|
||||
assert.equal(bciBoard.masterBuffer.positionWrite,originalWritePosition + k.OBCIPacketSize);
|
||||
// test to make sure we read in the right number of packets
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,1);
|
||||
// test to make sure there are no loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,0);
|
||||
});
|
||||
it('should wrap the input buffer around the master buffer',function() {
|
||||
var spaceRemaingInMasterBuffer = 15;
|
||||
var originalWritePosition = k.OBCIMasterBufferSize - spaceRemaingInMasterBuffer;
|
||||
|
||||
bciBoard.masterBuffer.positionWrite = originalWritePosition;
|
||||
|
||||
bciBoard._bufMerger(sampleData[0]);
|
||||
|
||||
// test to see that the end of master buffer contains half of sample
|
||||
assert.equal(0,sampleData[0].slice(0,spaceRemaingInMasterBuffer).compare(bciBoard.masterBuffer.buffer.slice(originalWritePosition)));
|
||||
// test to see that the beginning of master buffer contains the second half of sampleData[0]
|
||||
assert.equal(0,sampleData[0].slice(spaceRemaingInMasterBuffer).compare(bciBoard.masterBuffer.buffer.slice(0,sampleData[0].byteLength - spaceRemaingInMasterBuffer)));
|
||||
// test to make sure the write position was moved correctly
|
||||
assert.equal(bciBoard.masterBuffer.positionWrite,sampleData[0].byteLength - spaceRemaingInMasterBuffer);
|
||||
// test to make sure we read in the right number of packets
|
||||
assert.equal(bciBoard.masterBuffer.packetsIn,1);
|
||||
// test to make sure there are no loose bytes
|
||||
assert.equal(bciBoard.masterBuffer.looseBytes,0);
|
||||
});
|
||||
});
|
||||
describe('#_bufPacketStripper', function() {
|
||||
beforeEach(function() {
|
||||
bciBoard = new OpenBCIBoard.OpenBCIBoard();
|
||||
sampleData = [];
|
||||
(function(){
|
||||
for(var i = 0;i < 100;i++) {
|
||||
var sample = samplePacket();
|
||||
sample.sampleNumber = i;
|
||||
sampleData[i] = sample;
|
||||
}
|
||||
}());
|
||||
});
|
||||
it('should remove a packet from the master buffer', function() {
|
||||
var buffers = [sampleData[0],sampleData[1],sampleData[2]];
|
||||
var totalLength = k.OBCIPacketSize * 3;
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
//console.log(multiPacketBuffer);
|
||||
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer);
|
||||
|
||||
// run through three iterations of stripping packets
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var rawPacket = bciBoard._bufPacketStripper();
|
||||
var sample = OpenBCISample.convertPacketToSample(rawPacket);
|
||||
//console.log(OpenBCISample.debugPrettyPrint(sample));
|
||||
//console.log('Sample ' + i + ' has sample number ' + sample.sampleNumber);
|
||||
assert.equal(sample.sampleNumber,69);
|
||||
}
|
||||
});
|
||||
it('should remove a packet from the master buffer, even at wrap..', function() {
|
||||
|
||||
var buffers = [sampleData[0],sampleData[1],sampleData[2]];
|
||||
var totalLength = k.OBCIPacketSize * 3;
|
||||
|
||||
bciBoard.masterBuffer.positionWrite = k.OBCIMasterBufferSize - 10;
|
||||
bciBoard.masterBuffer.positionRead = bciBoard.masterBuffer.positionWrite;
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer);
|
||||
|
||||
// run through three iterations of stripping packets
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var rawPacket = bciBoard._bufPacketStripper();
|
||||
var sample = OpenBCISample.convertPacketToSample(rawPacket);
|
||||
assert(sample.sampleNumber,i);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('#_bufAlign',function() {
|
||||
beforeEach(function() {
|
||||
bciBoard = new OpenBCIBoard.OpenBCIBoard();
|
||||
sampleData = [];
|
||||
(function(){
|
||||
for(var i = 0;i < 100;i++) {
|
||||
var sample = samplePacket();
|
||||
sample.sampleNumber = i;
|
||||
sampleData[i] = sample;
|
||||
}
|
||||
}());
|
||||
});
|
||||
it('should fix a buffer', function() {
|
||||
var buffers = [sampleData[0],sampleData[1],sampleData[2]];
|
||||
var totalLength = k.OBCIPacketSize * 3;
|
||||
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
//console.log(multiPacketBuffer);
|
||||
|
||||
var bytesToHackOffSample = 10;
|
||||
var expectedNewPositionRead = k.OBCIPacketSize - bytesToHackOffSample;
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer.slice(10,totalLength));
|
||||
|
||||
bciBoard._bufAlign();
|
||||
|
||||
assert(expectedNewPositionRead,bciBoard.masterBuffer.positionRead);
|
||||
|
||||
});
|
||||
it('should wrap & then fix a buffer', function() {
|
||||
var buffers = [sampleData[0],sampleData[1],sampleData[2]];
|
||||
var totalLength = k.OBCIPacketSize * 3;
|
||||
|
||||
|
||||
var multiPacketBuffer = Buffer.concat(buffers,totalLength);
|
||||
|
||||
var bytesFromEndOfBuffer = 5;
|
||||
|
||||
bciBoard.masterBuffer.positionRead = k.OBCIMasterBufferSize - bytesFromEndOfBuffer;
|
||||
|
||||
var bytesToHackOffSample = 10;
|
||||
var expectedNewPositionRead = bytesToHackOffSample - bytesFromEndOfBuffer;
|
||||
|
||||
bciBoard._bufMerger(multiPacketBuffer.slice(10,totalLength));
|
||||
|
||||
bciBoard._bufAlign();
|
||||
|
||||
assert(expectedNewPositionRead,bciBoard.masterBuffer.positionRead);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function debugPrintBufferCompare(buf1,buf2) {
|
||||
console.log('Comparing:\n\tBuffer 1\n\t\t' + buf1.toString('hex') + '\n\tBuffer 2\n\t\t' + buf2.toString('hex'));
|
||||
|
||||
}
|
||||
@@ -896,6 +896,43 @@ describe('OpenBCIConstants', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
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('#getImpedanceSetter', function() {
|
||||
describe('channel input selection works', function () {
|
||||
it('channel 2', function (done) {
|
||||
|
||||
+119
-121
@@ -30,76 +30,82 @@ var sampleBuf = samplePacket();
|
||||
|
||||
|
||||
describe('openBCISample',function() {
|
||||
describe('#convertPacketToSample', function() {
|
||||
it('should have the correct start byte', function() {
|
||||
var sample = openBCISample.convertPacketToSample(sampleBuf);
|
||||
assert.equal(k.OBCIByteStart,sample.startByte);
|
||||
});
|
||||
it('should have the correct stop byte', function() {
|
||||
var sample = openBCISample.convertPacketToSample(sampleBuf);
|
||||
assert.equal(k.OBCIByteStop,sample.stopByte);
|
||||
var channelScaleFactor = 4.5 / 24 / (Math.pow(2,23) - 1);
|
||||
describe('#parseRawPacket', function() {
|
||||
it('should fulfill promise', function() {
|
||||
return openBCISample.parseRawPacket(sampleBuf).should.be.fulfilled;
|
||||
});
|
||||
it('should have the correct sample number', function() {
|
||||
var sample = openBCISample.convertPacketToSample(sampleBuf);
|
||||
assert.equal(0x45,sample.sampleNumber);
|
||||
return openBCISample.parseRawPacket(sampleBuf).should.eventually.have.property('sampleNumber').equal(0x45);
|
||||
});
|
||||
it('all the channels should have the same number value as their (index + 1) * scaleFactor', function() {
|
||||
var sample = openBCISample.convertPacketToSample(sampleBuf);
|
||||
for(var i = 0;i < k.OBCINumberOfChannelsDefault;i++) {
|
||||
//console.log(openBCISample.scaleFactorChannel * i);
|
||||
assert.equal(sample.channelData[i],openBCISample.scaleFactorChannel * (i+1));
|
||||
}
|
||||
it('all the channels should have the same number value as their (index + 1) * scaleFactor', function(done) {
|
||||
openBCISample.parseRawPacket(sampleBuf) // sampleBuf has its channel number for each 3 byte integer. See line 20...
|
||||
.then(sampleObject => {
|
||||
// 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)
|
||||
sampleObject.channelData.forEach((channelValue, index) => {
|
||||
assert.equal(channelValue,channelScaleFactor * (index + 1),'Channel ' + index + ' does not compute correctly');
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
it('all the auxs should have the same number value as their index * scaleFactor', function() {
|
||||
var sample = openBCISample.convertPacketToSample(sampleBuf);
|
||||
for(var i = 0;i < 3;i++) {
|
||||
assert.equal(sample.auxData[i],openBCISample.scaleFactorAux * i);
|
||||
}
|
||||
it('all the auxs should have the same number value as their index * scaleFactor', function(done) {
|
||||
openBCISample.parseRawPacket(sampleBuf)
|
||||
.then(sampleObject => {
|
||||
sampleObject.auxData.forEach((auxValue, index) => {
|
||||
assert.equal(auxValue,openBCISample.scaleFactorAux * index,'Aux ' + index + ' does not compute correctly');
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
it('check to see if negative numbers work on channel data',function() {
|
||||
it('check to see if negative numbers work on channel data',function(done) {
|
||||
var temp = samplePacket();
|
||||
//console.log(temp);
|
||||
var taco = new Buffer([0x81]);
|
||||
taco.copy(temp,2);
|
||||
var sample = openBCISample.convertPacketToSample(temp);
|
||||
sample.channelData[0].should.be.approximately(-8323071 * openBCISample.scaleFactorChannel, 0.001);
|
||||
|
||||
openBCISample.parseRawPacket(temp)
|
||||
.then(sampleObject => {
|
||||
assert.equal(sampleObject.channelData[0],channelScaleFactor * -8323071,'Negative numbers not working correctly');
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
it('check to see if negative numbers work on aux data',function() {
|
||||
var temp = samplePacket();
|
||||
//console.log(temp);
|
||||
var taco = new Buffer([0x81]);
|
||||
taco.copy(temp,26);
|
||||
//console.log(temp);
|
||||
var sample = openBCISample.convertPacketToSample(temp);
|
||||
//openBCISample.debugPrettyPrint(sample);
|
||||
sample.auxData[0].should.be.approximately(-32512 * openBCISample.scaleFactorAux,1);
|
||||
|
||||
openBCISample.parseRawPacket(temp)
|
||||
.then(sampleObject => {
|
||||
sampleObject.auxData[0].should.be.approximately(-32512 * openBCISample.scaleFactorAux,1);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
describe('#errorConditions', function() {
|
||||
it('send non data buffer', function() {
|
||||
var sample = openBCISample.convertPacketToSample(1);
|
||||
assert.equal(undefined,sample);
|
||||
it('send non data buffer', function(done) {
|
||||
openBCISample.parseRawPacket(1).should.be.rejected.and.notify(done)
|
||||
//var sample = openBCISample.convertPacketToSample(1);
|
||||
//assert.equal(undefined,sample);
|
||||
});
|
||||
it('bad start byte', function() {
|
||||
it('bad start byte', function(done) {
|
||||
var temp = samplePacket();
|
||||
temp[0] = 69;
|
||||
var sample = openBCISample.convertPacketToSample(temp);
|
||||
assert.equal(undefined,sample);
|
||||
openBCISample.parseRawPacket(temp).should.be.rejected.and.notify(done);
|
||||
//var sample = openBCISample.convertPacketToSample(temp);
|
||||
//assert.equal(undefined,sample);
|
||||
});
|
||||
it('bad stop byte', function() {
|
||||
var temp = samplePacket();
|
||||
temp[32] = 69;
|
||||
var sample = openBCISample.convertPacketToSample(temp);
|
||||
assert.equal(undefined,sample);
|
||||
it('wrong number of bytes', function(done) {
|
||||
openBCISample.parseRawPacket(new Buffer(5)).should.be.rejected.and.notify(done);
|
||||
//var sample = openBCISample.convertPacketToSample(new Buffer(5));
|
||||
//assert.equal(undefined,sample);
|
||||
});
|
||||
it('wrong number of bytes', function() {
|
||||
var sample = openBCISample.convertPacketToSample(new Buffer(5));
|
||||
assert.equal(undefined,sample);
|
||||
});
|
||||
it('undefined', function() {
|
||||
var sample = openBCISample.convertPacketToSample();
|
||||
assert.equal(undefined,sample);
|
||||
it('undefined', function(done) {
|
||||
openBCISample.parseRawPacket().should.be.rejected.and.notify(done);
|
||||
//var sample = openBCISample.convertPacketToSample();
|
||||
//assert.equal(undefined,sample);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -121,17 +127,29 @@ describe('openBCISample',function() {
|
||||
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() {
|
||||
var sample = openBCISample.convertPacketToSample(packetBuffer);
|
||||
for(var i = 0; i < k.OBCINumberOfChannelsDefault; i++) {
|
||||
sample.channelData[i].should.be.approximately(newSample.channelData[i],0.001);
|
||||
}
|
||||
it('should convert channel data to binary', function(done) {
|
||||
openBCISample.parseRawPacket(packetBuffer)
|
||||
.then(sample => {
|
||||
for(var i = 0; i < k.OBCINumberOfChannelsDefault; i++) {
|
||||
sample.channelData[i].should.be.approximately(newSample.channelData[i],0.001);
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
//var sample = openBCISample.convertPacketToSample(packetBuffer);
|
||||
|
||||
});
|
||||
it('should convert aux data to binary', function() {
|
||||
var sample = openBCISample.convertPacketToSample(packetBuffer);
|
||||
for(var i = 0; i < 3; i++) {
|
||||
sample.auxData[i].should.be.approximately(newSample.auxData[i],0.001);
|
||||
}
|
||||
it('should convert aux data to binary', function(done) {
|
||||
openBCISample.parseRawPacket(packetBuffer)
|
||||
.then(sample => {
|
||||
for(var i = 0; i < 3; i++) {
|
||||
sample.auxData[i].should.be.approximately(newSample.auxData[i],0.001);
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
//var sample = openBCISample.convertPacketToSample(packetBuffer);
|
||||
|
||||
});
|
||||
});
|
||||
describe('#interpret24bitAsInt32', function() {
|
||||
@@ -188,7 +206,7 @@ describe('openBCISample',function() {
|
||||
|
||||
var num = openBCISample.interpret24bitAsInt32(buff);
|
||||
|
||||
num = num * openBCISample.scaleFactorChannel;
|
||||
num = num * channelScaleFactor;
|
||||
|
||||
num.should.be.approximately(newSample.channelData[i],0.00002);
|
||||
}
|
||||
@@ -247,82 +265,62 @@ describe('openBCISample',function() {
|
||||
impedanceArray = openBCISample.impedanceArray(numberOfChannels);
|
||||
});
|
||||
it('should find impedance good', function() {
|
||||
impedanceArray[0].data = [5201.84, 7583.14, 2067.17, 0, 4132.37, 3189.33, 0, 3010.21, 7720.12, 4095.69, 0, 2730.19];
|
||||
impedanceArray[0].N.raw = 2201.84;
|
||||
|
||||
openBCISample.impedanceSummarize(impedanceArray[0]);
|
||||
openBCISample.impedanceSummarize(impedanceArray[0].N);
|
||||
|
||||
var sum = 0;
|
||||
var arrLen = impedanceArray[0].data.length;
|
||||
for (var i = 0; i < arrLen; i++) {
|
||||
sum += impedanceArray[0].data[i];
|
||||
}
|
||||
var avg = sum / arrLen;
|
||||
|
||||
impedanceArray[0].average.should.be.approximately(avg,10); // Check the average
|
||||
impedanceArray[0].text.should.equal(k.OBCIImpedanceTextGood); // Check the text
|
||||
impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextGood); // Check the text
|
||||
});
|
||||
it('should find impedance ok', function() {
|
||||
impedanceArray[0].data = [5201.84, 7583.14, 6067.17, 4305.43, 4132.37, 9189.33, 6925.34, 5010.21, 7720.12, 6095.69, 8730.19];
|
||||
impedanceArray[0].N.raw = 5201.84;
|
||||
|
||||
openBCISample.impedanceSummarize(impedanceArray[0]);
|
||||
openBCISample.impedanceSummarize(impedanceArray[0].N);
|
||||
|
||||
var sum = 0;
|
||||
var arrLen = impedanceArray[0].data.length;
|
||||
for (var i = 0; i < arrLen; i++) {
|
||||
sum += impedanceArray[0].data[i];
|
||||
}
|
||||
var avg = sum / arrLen;
|
||||
|
||||
impedanceArray[0].average.should.be.approximately(avg,10); // Check the average
|
||||
impedanceArray[0].text.should.equal(k.OBCIImpedanceTextOk); // Check the text
|
||||
impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextOk); // Check the text
|
||||
});
|
||||
it('should find impedance bad', function() {
|
||||
impedanceArray[0].data = [10201.84, 12583.14, 16067.17, 14305.43, 14132.37, 13189.33, 16925.34, 15010.21, 17720.12, 16095.69, 18730.19];
|
||||
impedanceArray[0].N.raw = 10201.84;
|
||||
|
||||
openBCISample.impedanceSummarize(impedanceArray[0]);
|
||||
openBCISample.impedanceSummarize(impedanceArray[0].N);
|
||||
|
||||
var sum = 0;
|
||||
var arrLen = impedanceArray[0].data.length;
|
||||
for (var i = 0; i < arrLen; i++) {
|
||||
sum += impedanceArray[0].data[i];
|
||||
}
|
||||
var avg = sum / arrLen;
|
||||
|
||||
impedanceArray[0].average.should.be.approximately(avg,10); // Check the average
|
||||
impedanceArray[0].text.should.equal(k.OBCIImpedanceTextBad); // Check the text
|
||||
impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextBad); // Check the text
|
||||
});
|
||||
it('should find impedance none', function() {
|
||||
impedanceArray[0].data = [44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09, 44194179.09];
|
||||
impedanceArray[0].N.data = 44194179.09; // A huge number that would be seen if there was no electrode connected
|
||||
|
||||
openBCISample.impedanceSummarize(impedanceArray[0]);
|
||||
openBCISample.impedanceSummarize(impedanceArray[0].N);
|
||||
|
||||
var sum = 0;
|
||||
var arrLen = impedanceArray[0].data.length;
|
||||
for (var i = 0; i < arrLen; i++) {
|
||||
sum += impedanceArray[0].data[i];
|
||||
}
|
||||
var avg = sum / arrLen;
|
||||
|
||||
impedanceArray[0].average.should.be.approximately(avg,10); // Check the average
|
||||
impedanceArray[0].text.should.equal(k.OBCIImpedanceTextNone); // Check the text
|
||||
});
|
||||
it('should remove outliers from data and find good impedance', function() {
|
||||
impedanceArray[0].data = [5201.84, 7583.14, 1112067.17, 0, 4132.37, 3189.33, 0, 1113010.21, 7720.12, 4095.69, 0, 2730.19];
|
||||
|
||||
openBCISample.impedanceSummarize(impedanceArray[0]);
|
||||
|
||||
var cleanedData = [5201.84, 7583.14, 0, 4132.37, 3189.33, 0, 7720.12, 4095.69, 0, 2730.19];
|
||||
|
||||
var sum = 0;
|
||||
var arrLen = cleanedData.length;
|
||||
for (var i = 0; i < arrLen; i++) {
|
||||
sum += cleanedData[i];
|
||||
}
|
||||
var avg = sum / arrLen;
|
||||
|
||||
impedanceArray[0].average.should.be.approximately(avg,10); // Check the average
|
||||
impedanceArray[0].text.should.equal(k.OBCIImpedanceTextGood); // Check the text
|
||||
impedanceArray[0].N.text.should.equal(k.OBCIImpedanceTextNone); // Check the text
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#goertzelProcessSample', function() {
|
||||
var numberOfChannels = k.OBCINumberOfChannelsDefault;
|
||||
var goertzelObj = openBCISample.goertzelNewObject(numberOfChannels);
|
||||
var newRandomSample = openBCISample.randomSample(numberOfChannels,k.OBCISampleRate250);
|
||||
|
||||
it('produces an array of impedances', function(done) {
|
||||
|
||||
var passed = false;
|
||||
for (var i = 0; i < openBCISample.GOERTZEL_BLOCK_SIZE + 1; i++) {
|
||||
console.log('Iteration ' + i);
|
||||
var impedanceArray = openBCISample.goertzelProcessSample(newRandomSample(i),goertzelObj);
|
||||
if (impedanceArray) {
|
||||
console.log('Impedance Array: ');
|
||||
for(var j = 0; j < numberOfChannels; j++) {
|
||||
console.log('Channel ' + (j+1) + ': ' + impedanceArray[j].toFixed(8))
|
||||
}
|
||||
passed = true;
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (passed) {
|
||||
done();
|
||||
} else {
|
||||
done('Failed to produce impedance array within block size + 1');
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//'use strict';
|
||||
//
|
||||
//var sinon = require('sinon');
|
||||
//var chai = require('chai');
|
||||
//var expect = chai.expect;
|
||||
//
|
||||
//var MockedOpenBCIBoard = require('../test_mocks/openbci-hardware');
|
||||
//var openBCIBoard = MockedOpenBCIBoard.openBCIBoard;
|
||||
//var hardware = MockedOpenBCIBoard.hardware;
|
||||
//
|
||||
//describe('openBCIBoard', function () {
|
||||
// var sandbox;
|
||||
//
|
||||
// beforeEach(function (){
|
||||
// sandbox = sinon.sandbox.create();
|
||||
//
|
||||
// // Create a board for fun and profit
|
||||
// hardware.reset();
|
||||
// hardware.createBoard();
|
||||
// });
|
||||
//
|
||||
// afterEach(function () {
|
||||
// sandbox.restore();
|
||||
// });
|
||||
//
|
||||
//});
|
||||
+314
-456
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
Referência em uma Nova Issue
Bloquear um usuário