Comparar commits

..

43 Commits

Autor SHA1 Mensagem Data
AJ Keller ee66c49ff0 Update changelog.md
Forgot to update change log on merge
2016-06-21 21:21:14 -04:00
Andy Heusser abfb37a38e Merge pull request #65 from pushtheworldllc/dev-serialport-upgrade
Dev serialport upgrade
2016-06-21 17:14:40 -04:00
AJ Keller 48be1a01db ENH add more travis versions 2016-06-21 07:36:50 -04:00
AJ Keller 8bbc011ead ENH bump serialport to 3.0.0 2016-06-21 07:33:38 -04:00
Teon L Brooks cc839b3b32 Merge pull request #62 from pushtheworldllc/dev-sim-accel
Add accel data to simulator closes #61
2016-06-10 00:04:22 -04:00
AJ Keller e2cd5d1dd7 Add accel data to simulator closes #61 2016-06-09 21:44:22 -04:00
AJ Keller 682074dff7 Update README.md 2016-06-02 22:34:45 -04:00
AJ Keller 0af11eabe5 Update README.md 2016-06-02 22:34:05 -04:00
AJ Keller 31eea736dd Merge pull request #60 from pushtheworldllc/dev
ADD SD card support functions
2016-06-02 22:16:31 -04:00
AJ Keller 8cafa3f7ff ADD SD Card Support Functions 2016-06-02 22:12:17 -04:00
AJ Keller 971787720e Merge pull request #57 from pushtheworldllc/bug-ci-fail
Removed auto test dependent on ntp with travis fail
2016-05-01 12:31:22 -04:00
AJ Keller c267cca924 Removed auto test dependent on ntp with travis fail 2016-05-01 12:28:01 -04:00
AJ Keller 3a90e5ab96 Auto test for hot patch 2016-05-01 11:14:20 -04:00
AJ Keller a18e17c7ca Hot patch for line noise 2016-05-01 11:13:17 -04:00
AJ Keller 5da869f059 Merge pull request #56 from pushtheworldllc/enh-bio-like-sim
ENH simulator looking more brain like
2016-05-01 11:07:00 -04:00
AJ Keller e32ec71c47 TGFAT 2016-04-30 19:57:48 -04:00
AJ Keller 5ee44ae531 TGFAT 2016-04-30 19:44:42 -04:00
AJ Keller b7f8c135b9 ENH simulator looking better 2016-04-30 19:31:11 -04:00
Teon L Brooks 8aa69a18dd Merge pull request #48 from pushtheworldllc/bug-rawDataPacket
FIX rawDataPacket not being emitted
2016-04-26 00:31:48 -04:00
AJ Keller 617e111812 FIX rawDataPacket not being emitted 2016-04-25 10:42:10 -04:00
Teon L Brooks d8c74d5b30 Merge pull request #45 from pushtheworldllc/ntp
ENH/ADD sntp time sync methods
2016-04-24 14:55:22 -04:00
AJ Keller aaf9f08c24 ENH/ADD sntp time sync methods 2016-04-24 11:39:36 -04:00
Teon L Brooks 37c622fdf4 Merge pull request #42 from pushtheworldllc/dev
0.3.1
2016-04-13 10:19:34 -04:00
AJ Keller 1d9927a7d7 0.3.1 2016-04-13 07:16:37 -04:00
Andy Heusser 1eda6df549 Update package.json 2016-04-12 21:13:45 -04:00
Teon L Brooks 88f973d349 Merge pull request #32 from pushtheworldllc/dev
0.3.0 - ENH Continuous Impedance Testing ADD NTP features
2016-04-10 20:39:04 -04:00
AJ Keller afcf1de6e2 ENH README.md and minor changes for PR
ENH fixes for PR

ENH README.md and changelog.md

ENH README.md

ENH README.md updates

updates

Added comments in ._impedanceTestSetChannel()

Fixins
2016-04-10 20:34:20 -04:00
AJ Keller caeeaf7dbd ENH addressing PR #32 comments 2016-04-09 19:43:48 -04:00
AJ Keller 6474391d1e Fixes #33 and #34 2016-04-09 14:34:54 -04:00
AJ Keller 169422e77e Small updates... 2016-04-03 19:10:47 -04:00
AJ Keller 21438f5a37 ENH Readme, ChangeLog, removed outdated funtions. 2016-04-03 17:55:24 -04:00
AJ Keller 2f5a31e481 ENH single channel impedance tests now use Goertzel algorithm. 2016-04-03 17:09:57 -04:00
AJ Keller 04f6fbae14 ENH Buffer 2016-04-02 20:50:49 -04:00
AJ Keller 8a22f3f11b Impedance testing up and running in continuous mode 2016-03-30 17:34:26 -04:00
AJ Keller 3ab00cfaf5 Working on goertzel... 2016-03-30 14:01:05 -04:00
AJ Keller 29b713b7f1 ADD sntp to the dependencies list.s 2016-03-28 16:31:27 -04:00
AJ Keller 74339c126b ENH overhauled raw packet to sample. 2016-03-28 16:27:48 -04:00
AJ Keller 465b3cc3a5 Saving, working on new packet to sample conversion implementation. 2016-03-24 08:12:25 -04:00
AJ Keller 920eab9ed1 Fixing issues and such... 2016-03-23 20:16:24 -04:00
AJ Keller 3a6d87ca90 ADD Sntp module for Sntp support, ADD Sntp functions. 2016-03-23 08:08:16 -04:00
AJ Keller 6a0e1c609f saving... 2016-02-27 17:38:29 -05:00
AJ Keller cd7d388e69 ENH Impedance Testing 2016-02-20 17:34:20 -05:00
AJ Keller 0ef2add033 Starting time sync stuff 2016-02-09 11:37:07 -05:00
14 arquivos alterados com 2078 adições e 1267 exclusões
+6
Ver Arquivo
@@ -1,6 +1,12 @@
language: node_js
node_js:
- "4.0"
- "4.1"
- "4.2"
- "5.11.0"
- "6.0"
- "6.1"
- "6.2"
install:
- npm install --all
script:
+212 -48
Ver Arquivo
@@ -3,11 +3,56 @@
[![Build Status](https://travis-ci.org/OpenBCI/openbci-js-sdk.svg?branch=master)](https://travis-ci.org/OpenBCI/openbci-js-sdk)
[![codecov.io](https://codecov.io/github/OpenBCI/openbci-js-sdk/coverage.svg?branch=master)](https://codecov.io/github/OpenBCI/openbci-js-sdk?branch=master)
# openbci-sdk
# OpenBCI Node.js 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. __United 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,35 @@ Get the current sample rate.
**_Returns_** a number, the current sample rate.
### .simulatorStart()
### .sdStart(recordingDuration)
Start logging to the SD card. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`.
**_recordingDuration_**
The duration you want to log SD information for. Opens a new SD file to write into. Limited to:
* `14sec` - 14 seconds
* `5min` - 5 minutes
* `15min` - 15 minutes
* `30min` - 30 minutes
* `1hour` - 1 hour
* `2hour` - 2 hour
* `4hour` - 4 hour
* `12hour` - 12 hour
* `24hour` - 24 hour
**Note: You must have the proper type of SD card inserted into the board for logging to work.**
**_Returns_** resolves if the command was added to the write queue.
### .sdStop()
Stop logging to the SD card and close any open file. If you are not streaming when you send this command, then you should expect to get a success or failure message followed by and end of transmission `$$$`. The success message contains a lot of useful information about what happened when writing to the SD card.
**_Returns_** resolves if the command was added to the write queue.
### .simulatorEnable()
To enter simulate mode. Must call `.connect()` after.
@@ -535,7 +635,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 +643,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 +699,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 +789,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
-------
@@ -662,4 +826,4 @@ npm test
6. Submit a pull request :D
## License
MIT
MIT
+90
Ver Arquivo
@@ -0,0 +1,90 @@
# 0.3.7
### New Features
* Upgrade dependencies
* Update Travis
# 0.3.6
### New Features
* Simulator now has accelerometer data
# 0.3.5
### New Features
* SD card support! Now logging to an SD card is easier than ever.
### Bug Fixes
* Sample rate does not return correct sample rate for custom rate on simulator. #58
# 0.3.4
### 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
Ver Arquivo
+444 -225
Ver Arquivo
@@ -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,52 @@ 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
});
};
/**
* @description Start logging to the SD card.
* @param recordingDuration {String} - The duration you want to log SD information for. Limited to:
* '14sec', '5min', '15min', '30min', '1hour', '2hour', '4hour', '12hour', '24hour'
* @returns {Promise} - Resolves if the command was added to write queue.
* @author AJ Keller (@pushtheworldllc)
*/
OpenBCIBoard.prototype.sdStart = function(recordingDuration) {
return new Promise((resolve,reject) => {
if (!this.connected) reject('Must be connected to the device');
k.sdSettingForString(recordingDuration)
.then(command => {
// If we are not streaming, then expect a confirmation message back from the board
if (!this.streaming) {
this.isLookingForKeyInBuffer = true;
this.searchingBuf = this.searchBuffers.miscStop;
}
this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone;
return this.write(command);
})
.catch(err => reject(err));
});
};
/**
* @description Sends the stop SD logging command to the board.
* @returns {Promise}
*/
OpenBCIBoard.prototype.sdStop = function() {
return new Promise((resolve,reject) => {
if (!this.connected) reject('Must be connected to the device');
// If we are not streaming, then expect a confirmation message back from the board
if (!this.streaming) {
this.isLookingForKeyInBuffer = true;
this.searchingBuf = this.searchBuffers.miscStop;
}
this.writeOutDelay = k.OBCIWriteIntervalDelayMSNone;
return this.write(k.OBCISDLogStop);
});
};
@@ -824,10 +1036,14 @@ function OpenBCIFactory() {
* @author AJ Keller (@pushtheworldllc)
*/
OpenBCIBoard.prototype.sampleRate = function() {
if(this.options.boardType === k.OBCIBoardDaisy) {
return k.OBCISampleRate125;
if (this.options.simulate) {
return this.options.simulatorSampleRate;
} else {
return k.OBCISampleRate250;
if(this.options.boardType === k.OBCIBoardDaisy) {
return k.OBCISampleRate125;
} else {
return k.OBCISampleRate250;
}
}
};
@@ -846,192 +1062,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 +1372,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
+192 -10
Ver Arquivo
@@ -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 = '=';
@@ -123,6 +126,17 @@ const kOBCISDLogForMin30 = 'F';
const kOBCISDLogForSec14 = 'a';
const kOBCISDLogStop = 'j';
/** SD Card String Commands */
const kOBCIStringSDHour1 = '1hour';
const kOBCIStringSDHour2 = '2hour';
const kOBCIStringSDHour4 = '4hour';
const kOBCIStringSDHour12 = '12hour';
const kOBCIStringSDHour24 = '24hour';
const kOBCIStringSDMin5 = '5min';
const kOBCIStringSDMin15 = '15min';
const kOBCIStringSDMin30 = '30min';
const kOBCIStringSDSec14 = '14sec';
/** Stream Data Commands */
const kOBCIStreamStart = 'b';
const kOBCIStreamStop = 's';
@@ -150,6 +164,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 +179,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 +190,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 +213,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 +241,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 +412,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 +486,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,
@@ -447,6 +530,58 @@ module.exports = {
OBCISDLogForMin30:kOBCISDLogForMin30,
OBCISDLogForSec14:kOBCISDLogForSec14,
OBCISDLogStop:kOBCISDLogStop,
/** SD Card String Commands */
OBCIStringSDHour1:kOBCIStringSDHour1,
OBCIStringSDHour2:kOBCIStringSDHour2,
OBCIStringSDHour4:kOBCIStringSDHour4,
OBCIStringSDHour12:kOBCIStringSDHour12,
OBCIStringSDHour24:kOBCIStringSDHour24,
OBCIStringSDMin5:kOBCIStringSDMin5,
OBCIStringSDMin15:kOBCIStringSDMin15,
OBCIStringSDMin30:kOBCIStringSDMin30,
OBCIStringSDSec14:kOBCIStringSDSec14,
/**
* @description Converts a sd string into the proper setting.
* @param stringCommand {String} - The length of time you want to record to the SD for.
* @returns {Promise} The command to send to the Board, returns an error on improper `stringCommand`
*/
sdSettingForString: (stringCommand) => {
return new Promise((resolve,reject) => {
switch (stringCommand) {
case kOBCIStringSDHour1:
resolve(kOBCISDLogForHour1);
break;
case kOBCIStringSDHour2:
resolve(kOBCISDLogForHour2);
break;
case kOBCIStringSDHour4:
resolve(kOBCISDLogForHour4);
break;
case kOBCIStringSDHour12:
resolve(kOBCISDLogForHour12);
break;
case kOBCIStringSDHour24:
resolve(kOBCISDLogForHour24);
break;
case kOBCIStringSDMin5:
resolve(kOBCISDLogForMin5);
break;
case kOBCIStringSDMin15:
resolve(kOBCISDLogForMin15);
break;
case kOBCIStringSDMin30:
resolve(kOBCISDLogForMin30);
break;
case kOBCIStringSDSec14:
resolve(kOBCISDLogForSec14);
break;
default:
reject(new Error(TypeError));
break;
}
});
},
/** Stream Data Commands */
OBCIStreamStart:kOBCIStreamStart,
OBCIStreamStop:kOBCIStreamStop,
@@ -537,6 +672,7 @@ module.exports = {
OBCIMasterBufferSize:kOBCIMasterBufferSize,
/** Impedance Calculation Variables */
OBCILeadOffDriveInAmps:kOBCILeadOffDriveInAmps,
OBCILeadOffFrequencyHz:kOBCILeadOffFrequencyHz,
/** Channel Setter Maker */
getChannelSetter:channelSetter,
/** Impedance Setter Maker */
@@ -545,6 +681,10 @@ module.exports = {
OBCIWriteIntervalDelayMSLong:kOBCIWriteIntervalDelayMSLong,
OBCIWriteIntervalDelayMSNone:kOBCIWriteIntervalDelayMSNone,
OBCIWriteIntervalDelayMSShort:kOBCIWriteIntervalDelayMSShort,
/** Sync Clocks */
OBCISyncClockServerData:kOBCISyncClockServerData,
OBCISyncClockStart:kOBCISyncClockStart,
OBCISyncClockStop:kOBCISyncClockStop,
/** Impedance */
OBCIImpedanceTextBad:kOBCIImpedanceTextBad,
OBCIImpedanceTextGood:kOBCIImpedanceTextGood,
@@ -565,7 +705,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 +796,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 +819,7 @@ function channelSetter(channelNumber,powerDown,gain,inputType,bias,srb2,srb1) {
kOBCIChannelCmdLatch
];
//console.log(outputArray);
resolve(outputArray);
resolve(outputArray,newChannelSettingsObject);
});
});
}
@@ -671,24 +842,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 +994,15 @@ function commandChannelForCmd(channelNumber) {
break;
}
});
}
function channelSettingsObjectDefault(channelNumber) {
return {
channelNumber:channelNumber,
powerDown: false,
gain: 24,
inputType: kOBCIStringADCNormal,
bias: true,
srb2: true,
srb1: false
};
}
+358 -94
Ver Arquivo
@@ -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,84 +276,291 @@ 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'
sinePhaseRad.fill(0);
var auxData = [0,0,0];
var accelCounter = 0;
// With 250Hz, every 10 samples, with 125Hz, every 5...
var samplesPerAccelRate = Math.floor(sampleRateHz / 25); // best to make this an integer
if (samplesPerAccelRate < 1) samplesPerAccelRate = 1;
// Init arrays to hold coefficients for each channel and init to 0
// This gives the 1/f filter memory on each iteration
var b0 = new Array(numberOfChannels).fill(0);
var b1 = new Array(numberOfChannels).fill(0);
var b2 = new Array(numberOfChannels).fill(0);
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... brainy
case 1:
if (injectAlpha) {
sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz10 / sampleRateHz;
if (sinePhaseRad[i] > 2 * Math.PI) {
sinePhaseRad[i] -= 2 * Math.PI;
}
whiteNoise += (5 * Math.SQRT2 * Math.sin(sinePhaseRad[i]))/uVolts;
}
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;
} else {
newSample.sampleNumber = previousSampleNumber + 1;
}
newSample.auxData = auxData;
/**
* Sample rate of accelerometer is 25Hz... when the accelCounter hits the relative sample rate of the accel
* we will output a new accel value. The approach will be to consider that Z should be about 1 and X and Y
* should be somewhere around 0.
*/
if (accelCounter == samplesPerAccelRate) {
// Initialize a new array
var accelArray = [0,0,0];
// Calculate X
accelArray[0] = (Math.random() * 0.1 * (Math.random() > 0.5 ? -1 : 1));
// Calculate Y
accelArray[1] = (Math.random() * 0.1 * (Math.random() > 0.5 ? -1 : 1));
// Calculate Z, this is around 1
accelArray[2] = 1 - ((Math.random() * 0.4) * (Math.random() > 0.5 ? -1 : 1));
// Store the newly calculated value
newSample.auxData = accelArray;
// Reset the counter
accelCounter = 0;
} else {
// Increment counter
accelCounter++;
// Store the default value
newSample.auxData = auxData;
}
return newSample;
};
},
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;
}
+113 -10
Ver Arquivo
@@ -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,16 +33,35 @@ 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;
// Bools
this.connected = false;
this.sd = {
active:false,
startTime: 0
};
this.streaming = false;
// Buffers
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 +88,62 @@ 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();
this.streaming = true;
break;
case k.OBCIStreamStop:
if (this.stream) clearInterval(this.stream); // Stops the stream
this.streaming = false;
break;
case k.OBCIMiscSoftReset:
if (this.stream) clearInterval(this.stream);
this.streaming = false;
this.emit('data', new Buffer('OpenBCI Board Simulator\nPush The World\nFirmware: v2$$$'));
break;
case k.OBCISDLogForHour1:
case k.OBCISDLogForHour2:
case k.OBCISDLogForHour4:
case k.OBCISDLogForHour12:
case k.OBCISDLogForHour24:
case k.OBCISDLogForMin5:
case k.OBCISDLogForMin15:
case k.OBCISDLogForMin30:
case k.OBCISDLogForSec14:
// If we are not streaming, then do verbose output
if (!this.streaming) {
this.emit('data', new Buffer('Wiring is correct and a card is present.\nCorresponding SD file OBCI_69.TXT\n$$$'));
}
this.sd.active = true;
this.sd.startTime = now();
break;
case k.OBCISDLogStop:
if (!this.streaming) {
if (this.SDLogActive) {
this.emit('data', new Buffer('Total Elapsed Time: ' + (now() - this.sd.startTime) + ' ms\n'));
this.emit('data', new Buffer('Max write time: ' + (Math.random()*500) + ' us\n'));
this.emit('data', new Buffer('Min write time: ' + (Math.random()*200) + ' us\n'));
this.emit('data', new Buffer('Overruns: 0\n$$$'));
} else {
this.emit('data', new Buffer('No open file to close\n$$$'));
}
}
this.SDLogActive = false;
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 +171,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 +183,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();
+5 -4
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "openbci-sdk",
"version": "0.2.0",
"description": "A fully NodeJS based API for the OpenBCI board connecting to the hardware directly over serial",
"version": "0.3.7",
"description": "The official Node.js SDK for the OpenBCI Biosensor Board.",
"main": "openBCIBoard",
"scripts": {
"start": "node index",
@@ -17,9 +17,10 @@
"dependencies": {
"gaussian": "^1.0.0",
"istanbul": "^0.4.2",
"outliers": "0.0.3",
"performance-now": "^0.2.0",
"scientific-statistics": "^0.2.7",
"serialport": "^2.0.2"
"serialport": "3.1.2",
"sntp": "^2.0.0"
},
"directories": {
"test": "test"
-273
Ver Arquivo
@@ -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'));
}
+117
Ver Arquivo
@@ -323,6 +323,86 @@ describe('OpenBCIConstants', function() {
assert.equal('j', k.OBCISDLogStop);
});
});
describe('SD card string Commands',function() {
it('logs for 1 hour', function() {
assert.equal('1hour', k.OBCIStringSDHour1);
});
it('logs for 2 hours', function() {
assert.equal('2hour', k.OBCIStringSDHour2);
});
it('logs for 4 hours', function() {
assert.equal('4hour', k.OBCIStringSDHour4);
});
it('logs for 12 hours', function() {
assert.equal('12hour', k.OBCIStringSDHour12);
});
it('logs for 24 hours', function() {
assert.equal('24hour', k.OBCIStringSDHour24);
});
it('logs for 5 minutes', function() {
assert.equal('5min', k.OBCIStringSDMin5);
});
it('logs for 15 minutes', function() {
assert.equal('15min', k.OBCIStringSDMin15);
});
it('logs for 30 minutes', function() {
assert.equal('30min', k.OBCIStringSDMin30);
});
it('logs for 14 seconds', function() {
assert.equal('14sec', k.OBCIStringSDSec14);
});
});
describe('#sdSettingForString',function() {
it('correct command for 1 hour', function() {
var expectation = k.OBCISDLogForHour1;
var result = k.sdSettingForString('1hour');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 2 hour', function() {
var expectation = k.OBCISDLogForHour2;
var result = k.sdSettingForString('2hour');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 4 hour', function() {
var expectation = k.OBCISDLogForHour4;
var result = k.sdSettingForString('4hour');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 12 hour', function() {
var expectation = k.OBCISDLogForHour12;
var result = k.sdSettingForString('12hour');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 24 hour', function() {
var expectation = k.OBCISDLogForHour24;
var result = k.sdSettingForString('24hour');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 5 min', function() {
var expectation = k.OBCISDLogForMin5;
var result = k.sdSettingForString('5min');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 15 min', function() {
var expectation = k.OBCISDLogForMin15;
var result = k.sdSettingForString('15min');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 30 min', function() {
var expectation = k.OBCISDLogForMin30;
var result = k.sdSettingForString('30min');
return expect(result).to.eventually.equal(expectation);
});
it('correct command for 14 seconds', function() {
var expectation = k.OBCISDLogForSec14;
var result = k.sdSettingForString('14sec');
return expect(result).to.eventually.equal(expectation);
});
it('Invalid command request', function() {
var result = k.sdSettingForString('taco');
return expect(result).to.be.rejected;
});
});
describe('Stream Data Commands',function() {
it('starts',function () {
assert.equal('b', k.OBCIStreamStart);
@@ -896,6 +976,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) {
+136 -121
Ver Arquivo
@@ -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);
}
@@ -224,6 +242,23 @@ describe('openBCISample',function() {
});
});
});
it('should generate a sample with accel data every 25Hz',function() {
var generateSample = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, k.OBCISampleRate250);
var newSample = generateSample(0);
var passed = false;
// Should get one non-zero auxData array (on the 10th sample)
for (var i = 0; i < 10; i++) {
newSample = generateSample(newSample.sampleNumber);
if (newSample.auxData[0] !== 0 || newSample.auxData[1] !== 0 || newSample.auxData[2] !== 0) {
passed = true;
newSample.auxData[0].should.be.approximately(0,0.1);
newSample.auxData[1].should.be.approximately(0,0.1);
newSample.auxData[2].should.be.approximately(1,0.4);
}
}
assert(passed,"a sample with accel data was produced");
});
});
describe('#impedanceCalculationForChannel', function() {
@@ -247,82 +282,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');
}
})
});
});
-26
Ver Arquivo
@@ -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();
// });
//
//});
+405 -456
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff