Comparar commits
16 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 3c46100e1d | |||
| 2c0b2e37d2 | |||
| 094e328296 | |||
| 4c9bbb1f6d | |||
| b51cb54f92 | |||
| 19f05e362f | |||
| c33d08b486 | |||
| 97cff2dac9 | |||
| 6cf5e993ed | |||
| 6cbf99e635 | |||
| 9bd1121ab8 | |||
| 7d878f1a16 | |||
| ba4370793c | |||
| e88922c106 | |||
| a43c52fdec | |||
| 5cf97383b9 |
+322
-129
@@ -2,6 +2,8 @@
|
||||
[](https://gitter.im/OpenBCI/OpenBCI_NodeJS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://travis-ci.org/OpenBCI/OpenBCI_NodeJS)
|
||||
[](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS)
|
||||
[](https://david-dm.org/OpenBCI/OpenBCI_NodeJS)
|
||||
[](http://npmjs.com/package/openbci)
|
||||
|
||||
# OpenBCI Node.js SDK
|
||||
|
||||
@@ -11,20 +13,37 @@ We are proud to support all functionality of the OpenBCI 8 and 16 Channel boards
|
||||
|
||||
The purpose of this module is to **get connected** and **start streaming** as fast as possible.
|
||||
|
||||
## TL;DR
|
||||
### Table of Contents:
|
||||
---
|
||||
|
||||
#### Install via npm:
|
||||
1. [Installation](#install)
|
||||
2. [TL;DR](#tldr)
|
||||
3. [About](#About)
|
||||
4. [General Overview](#general-overview)
|
||||
5. [SDK Reference Guide](#sdk-reference-guide)
|
||||
1. [Constructor](#constructor)
|
||||
2. [Methods](#method)
|
||||
3. [Events](#event)
|
||||
4. [Properties](#property)
|
||||
5. [Constants](#constants)
|
||||
6. [Interfacing With Other Tools](#interfacing-with-other-tools)
|
||||
7. [Developing](#developing)
|
||||
8. [Testing](#testing)
|
||||
9. [Contribute](#contribute)
|
||||
10. [License](#license)
|
||||
11. [Roadmap](#roadmap)
|
||||
|
||||
### <a name="install"></a> Installation:
|
||||
```
|
||||
npm install openbci
|
||||
```
|
||||
|
||||
#### Get connected and start streaming
|
||||
### <a name="tldr"></a> TL;DR:
|
||||
Get connected and start streaming right now
|
||||
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
ourBoard.connect(portName)
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(function() {
|
||||
ourBoard.on('ready',function() {
|
||||
ourBoard.streamStart();
|
||||
@@ -43,6 +62,7 @@ ourBoard.connect(portName)
|
||||
})
|
||||
```
|
||||
|
||||
### <a name="about"></a> About:
|
||||
Want to know if the module really works? Check out some projects and organizations using it:
|
||||
|
||||
* [_OpenEXP_](https://github.com/openexp/OpenEXP): an open-source desktop app for running experiments and collecting behavioral and physiological data.
|
||||
@@ -51,9 +71,9 @@ Want to know if the module really works? Check out some projects and organizatio
|
||||
|
||||
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!
|
||||
How are you still doubting and not using this already? Fine, go look at some of the [700 **_automatic_** tests](https://codecov.io/github/OpenBCI/openbci-js-sdk?branch=master) written for it!
|
||||
|
||||
## General Overview
|
||||
### <a name="general-overview"></a> General Overview:
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
@@ -130,14 +150,14 @@ 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 `'OpenBCISimulator'`.
|
||||
You can also start the simulator by sending [`.connect(portName)`](#method-connect) with `portName` equal to [`'OpenBCISimulator'`](#constants-obcisimulatorportname).
|
||||
|
||||
To get a 'sample' event, you need to:
|
||||
To get a ['sample'](#event-sample) event, you need to:
|
||||
-------------------------------------
|
||||
1. Call `.connect(serialPortName)`
|
||||
2. Install the 'ready' event emitter on resolved promise
|
||||
3. In callback for 'ready' emitter, call `streamStart()`
|
||||
4. Install the 'sample' event emitter
|
||||
1. Call [`.connect(serialPortName)`](#method-connect)
|
||||
2. Install the ['ready'](#event-ready) event emitter on resolved promise
|
||||
3. In callback for ['ready'](#event-ready) emitter, call [`streamStart()`](#method-stream-start)
|
||||
4. Install the ['sample'](#event-sample) event emitter
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
@@ -152,7 +172,7 @@ ourBoard.connect(portName).then(function() {
|
||||
/** Handle connection errors */
|
||||
});
|
||||
```
|
||||
Close the connection with `.streamStop()` and disconnect with `.disconnect()`
|
||||
Close the connection with [`.streamStop()`](#method-stream-stop) and disconnect with [`.disconnect()`](#method-disconnect)
|
||||
```js
|
||||
var ourBoard = new require('openbci').OpenBCIBoard();
|
||||
ourBoard.streamStop().then(ourBoard.disconnect());
|
||||
@@ -160,33 +180,58 @@ ourBoard.streamStop().then(ourBoard.disconnect());
|
||||
|
||||
Time Syncing
|
||||
------------
|
||||
You must be using OpenBCI firmware version 2 in order to do time syncing. After you `.connect()` and send a `.softReset()`, you can call `.usingVersionTwoFirmware()` to get a boolean response as to if you are using `v1` or `v2`.
|
||||
You must be using OpenBCI firmware version 2 in order to do time syncing. After you [`.connect()`](#method-connect) and send a [`.softReset()`](#method-soft-reset), you can call [`.usingVersionTwoFirmware()`](#method-using-version-two-firmware) to get a boolean response as to if you are using `v1` or `v2`.
|
||||
|
||||
Now using firmware `v2`, the fun begins! What we set out to do was synchronize not only the board to this modules clock, but also with a global NTP server, so that you could use several different devices and all sync to the same global server. That way you can really do some serious cloud computing!
|
||||
Now using firmware `v2`, the fun begins! We synchronize the Board's clock with the module's time. In firmware `v2` we leverage samples with time stamps and _ACKs_ from the Dongle to form a time synchronization strategy. Time syncing has been verified to +/- 4ms and a test report is on the way. We are still working on the synchronize of this module and an NTP server, this is an open call for any NTP experts out there! With a global NTP server you could use several different devices and all sync to the same time server. That way you can really do some serious cloud computing!
|
||||
|
||||
Keep your resync interval above 50ms. While it's important to resync every couple minutes due to drifting of clocks, please do not try to sync without getting the last sync event! We can only support one sync operation at a time!
|
||||
|
||||
Using local computer time:
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard,
|
||||
ourBoard = new OpenBCIBoard({
|
||||
verbose:true,
|
||||
timeSync: true // Sync up with NTP servers in constructor
|
||||
verbose:true
|
||||
});
|
||||
|
||||
const resyncPeriodMin = 5; // re sync every five minutes
|
||||
const secondsInMinute = 60;
|
||||
var sampleRate = k.OBCISampleRate250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
|
||||
var timeSyncPossible = false;
|
||||
|
||||
// Call to connect
|
||||
ourBoard.connect(portName).then(() => {
|
||||
ourBoard.on('ready',() => {
|
||||
// Get the sample rate after 'ready'
|
||||
sampleRate = ourBoard.sampleRate();
|
||||
// Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties.
|
||||
timeSyncPossible = ourBoard.usingVersionTwoFirmware();
|
||||
|
||||
ourBoard.streamStart()
|
||||
.then(() => {
|
||||
/** Start streaming command sent to board. */
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`stream start: ${err}`);
|
||||
})
|
||||
});
|
||||
// 'synced' event contains the guts of the whole sync operation.
|
||||
ourBoard.on('synced',obj => {
|
||||
console.log('sync obj',obj);
|
||||
});
|
||||
|
||||
// PTW recommends sample driven
|
||||
ourBoard.on('sample',sample => {
|
||||
// Resynchronize every 100 samples
|
||||
if (sample.sampleNumber % 100 === 0) {
|
||||
ourBoard.syncClocks();
|
||||
// Resynchronize every every 5 minutes
|
||||
if (sample._count % (sampleRate * resyncPeriodMin * secondsInMinute) === 0) {
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
// Sync was successful
|
||||
if (syncObj.valid) {
|
||||
// Log the object to check it out!
|
||||
console.log(`syncObj`,syncObj);
|
||||
|
||||
// Sync was not successful
|
||||
} else {
|
||||
// Retry it
|
||||
console.log(`Was not able to sync, please retry?`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sample.timeStamp) { // true after the first sync
|
||||
@@ -204,7 +249,7 @@ 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.
|
||||
If a port is not automatically found, then call [`.listPorts()`](#method-list-ports) to get a list of all serial ports this would be a good place to present a drop down picker list to the user, so they may manually select the serial port name.
|
||||
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
@@ -221,7 +266,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
});
|
||||
```
|
||||
|
||||
Note: `.autoFindOpenBCIBoard()` will return the name of the Simulator if you instantiate with option `simulate: true`.
|
||||
Note: [`.autoFindOpenBCIBoard()`](#method-auto-find-open-bci-board) will return the name of the Simulator if you instantiate with option `simulate: true`.
|
||||
|
||||
Auto Test - (Using impedance to determine signal quality)
|
||||
---------------------------------------------------------
|
||||
@@ -235,8 +280,8 @@ To test specific inputs of channels:
|
||||
|
||||
1. Connect to board.
|
||||
2. Start streaming.
|
||||
3. Install the 'impedanceArray' emitter
|
||||
4. Call `.impedanceTestChannels()` with your configuration array
|
||||
3. Install the ['impedanceArray'](#event-impedance-array) event
|
||||
4. Call [`.impedanceTestChannels()`](#method-impedance-test-channels) with your configuration array
|
||||
|
||||
A configuration array looks like, for an 8 channel board, `['-','N','n','p','P','-','b','b']`
|
||||
|
||||
@@ -298,8 +343,8 @@ To run an impedance test on all inputs, one channel at a time:
|
||||
|
||||
1. Connect to board
|
||||
2. Start streaming
|
||||
3. Install the 'impedanceObject'
|
||||
4. Call `.impedanceTestAllChannels()`
|
||||
3. Install the ['impedanceArray'](#event-impedance-array)
|
||||
4. Call [`.impedanceTestAllChannels()`](#method-impedance-test-all-channels)
|
||||
|
||||
**Note: Takes up to 5 seconds to start measuring impedances. There is an unknown number of samples taken. Not always 60!**
|
||||
|
||||
@@ -319,11 +364,11 @@ ourBoard.connect(portName).then(function(boardSerial) {
|
||||
|
||||
See Reference Guide for a complete list of impedance tests.
|
||||
|
||||
Reference Guide
|
||||
# <a name="sdk-reference-guide"></a> SDK Reference Guide:
|
||||
---------------
|
||||
## Methods
|
||||
## <a name="constructor"></a> Constructor:
|
||||
|
||||
### OpenBCIBoard (options)
|
||||
### <a name="constructor-openbciboard"></a> OpenBCIBoard (options)
|
||||
|
||||
Create new instance of an OpenBCI board.
|
||||
|
||||
@@ -351,12 +396,16 @@ Board optional configurations.
|
||||
* `None` - Do not inject line noise.
|
||||
* `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that setting and this sample rate will be used. (Default is `250`)
|
||||
* `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely due to a OpenBCI dongle not being plugged in.
|
||||
* `timeSync` - {Boolean} 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.
|
||||
* `verbose` {Boolean} - Print out useful debugging events
|
||||
* `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source of truth instead of local computer time. If you are running experiments on your local computer, keep this `false`. (Default `false`)
|
||||
* `sntpTimeSyncHost` - {String} The sntp server to use, can be either sntp or ntp (Defaults `pool.ntp.org`).
|
||||
* `sntpTimeSyncPort` - {Number} The port to access the sntp server (Defaults `123`)
|
||||
* `verbose` {Boolean} - Print out useful debugging events (Default `false`)
|
||||
|
||||
**Note, we have added support for either all lowercase OR camel case for the options, use whichever style you prefer.**
|
||||
|
||||
### .autoFindOpenBCIBoard()
|
||||
## <a name="method"></a> Methods:
|
||||
|
||||
### <a name="method-auto-find-open-bci-board"></a> .autoFindOpenBCIBoard()
|
||||
|
||||
Automatically find an OpenBCI board.
|
||||
|
||||
@@ -364,7 +413,7 @@ Automatically find an OpenBCI board.
|
||||
|
||||
**_Returns_** a promise, fulfilled with a `portName` such as `/dev/tty.*` on Mac/Linux or `OpenBCISimulator` if `this.options.simulate === true`.
|
||||
|
||||
### .channelOff(channelNumber)
|
||||
### <a name="method-channel-off"></a> .channelOff(channelNumber)
|
||||
|
||||
Turn off a specified channel
|
||||
|
||||
@@ -374,7 +423,7 @@ A number (1-16) specifying which channel you want to turn off.
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue.
|
||||
|
||||
### .channelOn(channelNumber)
|
||||
### <a name="method-channel-on"></a> .channelOn(channelNumber)
|
||||
|
||||
Turn on a specified channel
|
||||
|
||||
@@ -384,7 +433,7 @@ A number (1-16) specifying which channel you want to turn on.
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue.
|
||||
|
||||
### .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1)
|
||||
### <a name="method-"></a> .channelSet(channelNumber,powerDown,gain,inputType,bias,srb2,srb1)
|
||||
|
||||
Send a channel setting command to the board.
|
||||
|
||||
@@ -424,7 +473,7 @@ ourBoard.channelSet(2,false,24,'normal',true,true,false);
|
||||
// sends ['x','2','0','6','0','1','1','0','X'] to the command queue
|
||||
```
|
||||
|
||||
### .connect (portName)
|
||||
### <a name="method-connect"></a> .connect(portName)
|
||||
|
||||
The essential precursor method to be called initially to establish a serial connection to the OpenBCI board.
|
||||
|
||||
@@ -434,17 +483,17 @@ The system path of the OpenBCI board serial port to open. For example, `/dev/tty
|
||||
|
||||
**_Returns_** a promise, fulfilled by a successful serial connection to the board.
|
||||
|
||||
### .debugSession()
|
||||
### <a name="method-debug-session"></a> .debugSession()
|
||||
|
||||
Calls all `.printPacketsBad()`, `.printPacketsRead()`, `.printBytesIn()`
|
||||
Calls all [`.printPacketsBad()`](#method-print-packets-bad), [`.printPacketsRead()`](#method-print-packets-read), [`.printBytesIn()`](#method-print-bytes-in)
|
||||
|
||||
### .disconnect()
|
||||
### <a name="method-disconnect"></a> .disconnect()
|
||||
|
||||
Closes the serial port opened by `.connect()`. Waits for stop streaming command to be sent if currently streaming.
|
||||
Closes the serial port opened by [`.connect()`](#method-connect). Waits for stop streaming command to be sent if currently streaming.
|
||||
|
||||
**_Returns_** a promise, fulfilled by a successful close of the serial port object, rejected otherwise.
|
||||
|
||||
### .getSettingsForChannel(channelNumber)
|
||||
### <a name="method-get-settings-for-channel"></a> .getSettingsForChannel(channelNumber)
|
||||
|
||||
Gets the specified channelSettings register data from printRegisterSettings call.
|
||||
|
||||
@@ -456,17 +505,17 @@ A number specifying which channel you want to get data on. Only 1-8 at this time
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the board and the `.processBytes()` function is ready to reach for the specified channel.
|
||||
|
||||
### .impedanceTestAllChannels()
|
||||
### <a name="method-impedance-test-all-channels"></a> .impedanceTestAllChannels()
|
||||
|
||||
To apply test signals to the channels on the OpenBCI board used to test for impedance. This can take a little while to actually run (<8 seconds)!
|
||||
|
||||
Don't forget to install the `impedanceArray` emitter to receive the impendances!
|
||||
Don't forget to install the ['impedanceArray'](#event-impedance-array) emitter to receive the impendances!
|
||||
|
||||
**Note, you must be connected in order to set the test commands. Also this method can take up to 5 seconds to send all commands!**
|
||||
|
||||
**_Returns_** a promise upon completion of test.
|
||||
|
||||
### .impedanceTestChannels(arrayOfCommands)
|
||||
### <a name="method-impedance-test-channels"></a> .impedanceTestChannels(arrayOfCommands)
|
||||
|
||||
**_arrayOfCommands_**
|
||||
|
||||
@@ -483,7 +532,7 @@ Don't forget to install the `impedanceArray` emitter to receive the impendances!
|
||||
|
||||
**_Returns_** a promise upon completion of test.
|
||||
|
||||
### .impedanceTestChannel(channelNumber)
|
||||
### <a name="method-impedance-test-channel"></a> .impedanceTestChannel(channelNumber)
|
||||
|
||||
Run a complete impedance test on a single channel, applying the test signal individually to P & N inputs.
|
||||
|
||||
@@ -493,7 +542,7 @@ A Number, specifies which channel you want to test.
|
||||
|
||||
**_Returns_** a promise that resolves a single channel impedance object.
|
||||
|
||||
Example:
|
||||
**Example**
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
@@ -525,7 +574,7 @@ Where an impedance for this method call would look like:
|
||||
}
|
||||
```
|
||||
|
||||
### .impedanceTestChannelInputP(channelNumber)
|
||||
### <a name="method-impedance-test-channel-input-p"></a> .impedanceTestChannelInputP(channelNumber)
|
||||
|
||||
Run impedance test on a single channel, applying the test signal only to P input.
|
||||
|
||||
@@ -535,7 +584,7 @@ A Number, specifies which channel you want to test.
|
||||
|
||||
**_Returns_** a promise that resolves a single channel impedance object.
|
||||
|
||||
Example:
|
||||
**Example**
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
@@ -567,7 +616,7 @@ Where an impedance for this method call would look like:
|
||||
}
|
||||
```
|
||||
|
||||
### .impedanceTestChannelInputN(channelNumber)
|
||||
### <a name="method-impedance-test-channel-input-n"></a> .impedanceTestChannelInputN(channelNumber)
|
||||
|
||||
Run impedance test on a single channel, applying the test signal only to N input.
|
||||
|
||||
@@ -577,7 +626,7 @@ A Number, specifies which channel you want to test.
|
||||
|
||||
**_Returns_** a promise that resolves a single channel impedance object.
|
||||
|
||||
Example:
|
||||
**Example**
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
@@ -609,25 +658,25 @@ Where an impedance for this method call would look like:
|
||||
}
|
||||
```
|
||||
|
||||
### .impedanceTestContinuousStart()
|
||||
### <a name="method-impedance-test-continuous-start"></a> .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()
|
||||
### <a name="method-impedance-test-continuous-stop"></a> .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()
|
||||
### <a name="method-list-ports"></a> .listPorts()
|
||||
|
||||
List available ports so the user can choose a device when not automatically found.
|
||||
|
||||
**_Returns_** a promise, fulfilled with a list of available serial ports.
|
||||
|
||||
### .numberOfChannels()
|
||||
### <a name="method-number-of-channels"></a> .numberOfChannels()
|
||||
|
||||
Get the current number of channels available to use. (i.e. 8 or 16).
|
||||
|
||||
@@ -635,25 +684,25 @@ Get the current number of channels available to use. (i.e. 8 or 16).
|
||||
|
||||
**_Returns_** a number, the total number of available channels.
|
||||
|
||||
### .printBytesIn()
|
||||
### <a name="method-print-bytes-in"></a> .printBytesIn()
|
||||
|
||||
Prints the total number of bytes that were read in this session to the console.
|
||||
|
||||
### .printPacketsBad()
|
||||
### <a name="method-print-packets-bad"></a> .printPacketsBad()
|
||||
|
||||
Prints the total number of packets that were not able to be read in this session to the console.
|
||||
|
||||
### .printPacketsRead()
|
||||
### <a name="method-print-packets-read"></a> .printPacketsRead()
|
||||
|
||||
Prints the total number of packets that were read in this session to the console.
|
||||
|
||||
### .printRegisterSettings()
|
||||
### <a name="method-print-register-settings"></a> .printRegisterSettings()
|
||||
|
||||
Prints all register settings for the ADS1299 and the LIS3DH on the OpenBCI board.
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue.
|
||||
|
||||
### .radioBaudRateSet(speed)
|
||||
### <a name="method-radio-baud-rate-set"></a> .radioBaudRateSet(speed)
|
||||
|
||||
Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. There exists a detrimental error where if the Host is interrupted by the radio during a Serial write, then all hell breaks loose. So this is an effort to eliminate that problem by increasing the rate at which serial data is sent from the Host to the Serial driver. The rate can either be set to default or fast. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the new baud rate after closing the current serial port and reopening one.
|
||||
|
||||
@@ -665,7 +714,7 @@ Used to set the OpenBCI Host (Dongle) baud rate. With the RFduino configuration,
|
||||
|
||||
**_Returns_** {Promise} - Resolves a {Number} that is the new baud rate, rejects on error.
|
||||
|
||||
### .radioChannelGet()
|
||||
### <a name="method-radio-channel-get"></a> .radioChannelGet()
|
||||
|
||||
Used to query the OpenBCI system for it's radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve an Object. See `returns` below.
|
||||
|
||||
@@ -673,7 +722,7 @@ Used to query the OpenBCI system for it's radio channel number. The function wil
|
||||
|
||||
**_Returns_** {Promise} - Resolve an object with keys `channelNumber` which is a Number and `err` which contains an error in the condition that there system is experiencing board communications failure.
|
||||
|
||||
### .radioChannelSet(channelNumber)
|
||||
### <a name="method-radio-channel-set"></a> .radioChannelSet(channelNumber)
|
||||
|
||||
Used to set the system radio channel number. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve.
|
||||
|
||||
@@ -685,7 +734,7 @@ Used to set the system radio channel number. The function will reject if not con
|
||||
|
||||
**_Returns_** {Promise} - Resolves with the new channel number, rejects with err.
|
||||
|
||||
### .radioChannelSetHostOverride(channelNumber)
|
||||
### <a name="method-radio-channel-set-host-override"></a> .radioChannelSetHostOverride(channelNumber)
|
||||
|
||||
Used to set the ONLY the radio dongle Host channel number. This will fix your radio system if your dongle and board are not on the right channel and bring down your radio system if you take your dongle and board are not on the same channel. Use with caution! The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve.
|
||||
|
||||
@@ -697,7 +746,7 @@ Used to set the ONLY the radio dongle Host channel number. This will fix your ra
|
||||
|
||||
**_Returns_** {Promise} - Resolves with the new channel number, rejects with err.
|
||||
|
||||
### .radioPollTimeGet()
|
||||
### <a name="method-radio-poll-time-get"></a> .radioPollTimeGet()
|
||||
|
||||
Used to query the OpenBCI system for it's device's poll time. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve the poll time when fulfilled. It's important to note that if the board is not on, this function will always be rejected with a failure message.
|
||||
|
||||
@@ -705,7 +754,7 @@ Used to query the OpenBCI system for it's device's poll time. The function will
|
||||
|
||||
**_Returns_** {Promise} - Resolves with the new poll time, rejects with err.
|
||||
|
||||
### .radioPollTimeSet(pollTime)
|
||||
### <a name="method-radio-poll-time-set"></a> .radioPollTimeSet(pollTime)
|
||||
|
||||
Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is the Host and the Board is the Device. Only the Device can initiate a communication between the two entities. Therefore this sets the interval at which the Device polls the Host for new information. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware 2+ then this function should resolve.
|
||||
|
||||
@@ -717,7 +766,7 @@ Used to set the OpenBCI poll time. With the RFduino configuration, the Dongle is
|
||||
|
||||
**_Returns_** {Promise} - Resolves with the new channel number, rejects with err.
|
||||
|
||||
### .radioSystemStatusGet()
|
||||
### <a name="method-radio-system-status-get"></a> .radioSystemStatusGet()
|
||||
|
||||
Used to ask the Host if it's radio system is up. This is useful to quickly determine if you are in fact ready to start trying to connect and such. The function will reject if not connected to the serial port of the dongle. Further the function should reject if currently streaming. Lastly and more important, if the board is not running the new firmware then this functionality does not exist and thus this method will reject. If the board is using firmware +v2.0.0 and the radios are both on the same channel and powered, then this will resolve true.
|
||||
|
||||
@@ -725,7 +774,7 @@ Used to ask the Host if it's radio system is up. This is useful to quickly deter
|
||||
|
||||
**_Returns_** {Promise} - Resolves true if both radios are powered and on the same channel; false otherwise.
|
||||
|
||||
### .sampleRate()
|
||||
### <a name="method-sample-rate"></a> .sampleRate()
|
||||
|
||||
Get the current sample rate.
|
||||
|
||||
@@ -733,7 +782,7 @@ Get the current sample rate.
|
||||
|
||||
**_Returns_** a number, the current sample rate.
|
||||
|
||||
### .sdStart(recordingDuration)
|
||||
### <a name="method-sd-start"></a> .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 `$$$`.
|
||||
|
||||
@@ -755,21 +804,21 @@ The duration you want to log SD information for. Opens a new SD file to write in
|
||||
|
||||
**_Returns_** resolves if the command was added to the write queue.
|
||||
|
||||
### .sdStop()
|
||||
### <a name="method-sd-stop"></a> .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()
|
||||
### <a name="method-simulator-enable"></a> .simulatorEnable()
|
||||
|
||||
To enter simulate mode. Must call `.connect()` after.
|
||||
To enter simulate mode. Must call [`.connect()`](#method-connect) after.
|
||||
|
||||
**Note, must be called after the constructor.**
|
||||
|
||||
**_Returns_** a promise, fulfilled if able to enter simulate mode, reject if not.
|
||||
|
||||
### .simulatorDisable()
|
||||
### <a name="method-simulator-disable"></a> .simulatorDisable()
|
||||
|
||||
To leave simulate mode.
|
||||
|
||||
@@ -777,39 +826,27 @@ To leave simulate mode.
|
||||
|
||||
**_Returns_** a promise, fulfilled if able to stop simulate mode, reject if not.
|
||||
|
||||
### .sntp
|
||||
### <a name="method-sntp"></a> .sntp
|
||||
|
||||
Extends the popular STNP package on [npmjs](https://www.npmjs.com/package/sntp)
|
||||
|
||||
### .sntpGetOffset()
|
||||
### <a name="method-sntp-get-offset"></a> .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()
|
||||
### <a name="method-sntp-start"></a> .sntpStart()
|
||||
|
||||
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 synced, 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;
|
||||
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()
|
||||
### <a name="method-sntp-stop"></a> .sntpStop()
|
||||
|
||||
Stops the SNTP from updating
|
||||
|
||||
### .softReset()
|
||||
### <a name="method-soft-reset"></a> .softReset()
|
||||
|
||||
Sends a soft reset command to the board.
|
||||
|
||||
@@ -817,23 +854,23 @@ Sends a soft reset command to the board.
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue.
|
||||
|
||||
### .streamStart()
|
||||
### <a name="method-stream-start"></a> .streamStart()
|
||||
|
||||
Sends a start streaming command to the board.
|
||||
|
||||
**Note, You must have called and fulfilled `.connect()` AND observed a `'ready'` emitter before calling this method.**
|
||||
**Note, You must have called and fulfilled [`.connect()`](#method-connect) AND observed a `'ready'` emitter before calling this method.**
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable.
|
||||
|
||||
### .streamStop()
|
||||
### <a name="method-stream-stop"></a> .streamStop()
|
||||
|
||||
Sends a stop streaming command to the board.
|
||||
|
||||
**Note, You must have called and fulfilled `.connect()` AND observed a `'ready'` emitter before calling this method.**
|
||||
**Note, You must have called and fulfilled [`.connect()`](#method-connect) AND observed a `'ready'` emitter before calling this method.**
|
||||
|
||||
**_Returns_** a promise, fulfilled if the command was sent to the write queue, rejected if unable.
|
||||
|
||||
### .syncClocks()
|
||||
### <a name="method-sync-clocks"></a> .syncClocks()
|
||||
|
||||
Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using version +2 firmware.
|
||||
|
||||
@@ -841,7 +878,99 @@ Send the command to tell the board to start the syncing protocol. Must be connec
|
||||
|
||||
**_Returns_** {Promise} resolves if the command was sent to the write queue, rejects if unable.
|
||||
|
||||
### .testSignal(signal)
|
||||
### <a name="method-sync-clocks-full"></a> .syncClocksFull()
|
||||
|
||||
Send the command to tell the board to start the syncing protocol. Must be connected, streaming and using v2 firmware. Uses the `synced` event to ensure multiple syncs don't overlap.
|
||||
|
||||
**Note, this functionality requires OpenBCI Firmware Version 2.0**
|
||||
|
||||
**_Returns_** {Promise} resolves if `synced` event is emitted, rejects if not connected or using firmware v2. Resolves with a synced object:
|
||||
```javascript
|
||||
{
|
||||
boardTime: 0, // The time contained in the time sync set packet.
|
||||
correctedTransmissionTime: false, // If the confirmation and the set packet arrive in the same serial flush we have big problem! This will be true in this case. See source code for full explanation.
|
||||
timeSyncSent: 0, // The time the `<` was sent to the Dongle.
|
||||
timeSyncSentConfirmation: 0, // The time the `<` was sent to the Board; It's really the time `,` was received from the Dongle.
|
||||
timeSyncSetPacket: 0, // The time the set packet was received from the Board.
|
||||
timeRoundTrip: 0, // Simply timeSyncSetPacket - timeSyncSent.
|
||||
timeTransmission: 0, // Estimated time it took for time sync set packet to be sent from Board to Driver.
|
||||
timeOffset: 0, // The map (or translation) from boardTime to module time.
|
||||
valid: false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below.
|
||||
}
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
Syncing multiple times to base the offset of the average of the four syncs.
|
||||
|
||||
```javascript
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard,
|
||||
ourBoard = new OpenBCIBoard({
|
||||
verbose:true
|
||||
});
|
||||
|
||||
var portName = /* INSERT PORT NAME HERE */;
|
||||
var samples = []; // Array to store time synced samples into
|
||||
var timeSyncActivated = false;
|
||||
|
||||
ourBoard.connect(portName)
|
||||
.then(() => {
|
||||
ourBoard.on('ready',() => {
|
||||
ourBoard.streamStart()
|
||||
.then(() => {
|
||||
/** Could also call `.syncClocksFull()` here */
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`Error starting stream ${err}`);
|
||||
})
|
||||
});
|
||||
ourBoard.on('sample',sample => {
|
||||
/** If we are not synced, then do that! */
|
||||
if (timeSyncActivated === false) {
|
||||
timeSyncActivated = true;
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
if (syncObj.valid) {
|
||||
console.log('1st sync done');
|
||||
}
|
||||
return ourBoard.syncClocksFull();
|
||||
})
|
||||
.then(syncObj => {
|
||||
if (syncObj.valid) {
|
||||
console.log('2nd sync done');
|
||||
}
|
||||
return ourBoard.syncClocksFull();
|
||||
})
|
||||
.then(syncObj => {
|
||||
if (syncObj.valid) {
|
||||
console.log('3rd sync done');
|
||||
}
|
||||
return ourBoard.syncClocksFull();
|
||||
})
|
||||
.then(syncObj => {
|
||||
if (syncObj.valid) {
|
||||
console.log('4th sync done');
|
||||
|
||||
}
|
||||
/* Do awesome time syncing stuff */
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`sync err ${err}`);
|
||||
});
|
||||
}
|
||||
if (startLoggingSamples && sample.hasOwnProperty("timeStamp") && sample.hasOwnProperty("boardTime")) {
|
||||
/** If you only want to log samples with time stamps */
|
||||
samples.push(sample);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect ${err}`);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### <a name="method-test-signal"></a> .testSignal(signal)
|
||||
|
||||
Apply the internal test signal to all channels.
|
||||
|
||||
@@ -859,15 +988,21 @@ A String indicating which test signal to apply
|
||||
|
||||
**_Returns_** a promise, if the commands were sent to write buffer.
|
||||
|
||||
### .usingVersionTwoFirmware()
|
||||
### <a name="method-time"></a> .time()
|
||||
|
||||
Uses `._sntpNow()` time when sntpTimeSync specified `true` in options, or else Date.now() for time.
|
||||
|
||||
**_Returns_** time since UNIX epoch in ms.
|
||||
|
||||
### <a name="method-using-version-two-firmware"></a> .usingVersionTwoFirmware()
|
||||
|
||||
Convenience method to determine if you can use firmware v2.x.x capabilities.
|
||||
|
||||
**Note, should be called after a `.softReset()` because we can parse the output of that to determine if we are using firmware version 2.**
|
||||
**Note, should be called after a [`.softReset()`](#method-soft-reset) because we can parse the output of that to determine if we are using firmware version 2.**
|
||||
|
||||
**_Returns_** a boolean, true if using firmware version 2 or greater.
|
||||
|
||||
### .write(dataToWrite)
|
||||
### <a name="method-write"></a> .write(dataToWrite)
|
||||
|
||||
Send commands to the board. Due to the OpenBCI board firmware 1.0, a 10ms spacing **must** be observed between every command sent to the board. This method handles the timing and spacing between characters by adding characters to a global write queue and pulling from it every 10ms. If you are using firmware version +2.0 then you no spacing will be used.
|
||||
|
||||
@@ -899,47 +1034,51 @@ ourBoard.write('c');
|
||||
ourBoard.write('o');
|
||||
```
|
||||
|
||||
## Events
|
||||
## <a name="event"></a> Events:
|
||||
|
||||
### .on('close', callback)
|
||||
### <a name="event-close"></a> .on('close', callback)
|
||||
|
||||
Emitted when the serial connection to the board is closed.
|
||||
|
||||
### .on('error', callback)
|
||||
### <a name="event-close"></a> .on('droppedPacket', callback)
|
||||
|
||||
Emitted when a packet (or packets) are dropped. Returns an array.
|
||||
|
||||
### <a name="event-error"></a> .on('error', callback)
|
||||
|
||||
Emitted when there is an on the serial port.
|
||||
|
||||
### .on('impedanceArray', callback)
|
||||
### <a name="event-impedance-array"></a> .on('impedanceArray', callback)
|
||||
|
||||
Emitted when there is a new impedanceArray available.
|
||||
Emitted when there is a new impedanceArray available. Returns an array.
|
||||
|
||||
### .on('query', callback)
|
||||
### <a name="event-query"></a> .on('query', callback)
|
||||
|
||||
Emitted resulting in a call to `.getChannelSettings()` with the channelSettingsObject
|
||||
Emitted resulting in a call to [`.getChannelSettings()`](#method-get-settings-for-channel) with the channelSettingsObject
|
||||
|
||||
### .on('rawDataPacket', callback)
|
||||
### <a name="event-raw-data-packet"></a> .on('rawDataPacket', callback)
|
||||
|
||||
Emitted when there is a new raw data packet available.
|
||||
|
||||
### .on('ready', callback)
|
||||
### <a name="event-ready"></a> .on('ready', callback)
|
||||
|
||||
Emitted when the board is in a ready to start streaming state.
|
||||
|
||||
### .on('sample', callback)
|
||||
### <a name="event-sample"></a> .on('sample', callback)
|
||||
|
||||
Emitted when there is a new sample available.
|
||||
|
||||
## Properties
|
||||
## <a name="property"></a> Properties:
|
||||
|
||||
### connected
|
||||
### <a name="property-connected"></a> connected
|
||||
|
||||
A bool, true if connected to an OpenBCI board, false if not.
|
||||
|
||||
### streaming
|
||||
### <a name="property-streaming"></a> streaming
|
||||
|
||||
A bool, true if streaming data from an OpenBCI board, false if not.
|
||||
|
||||
## Useful Constants
|
||||
## <a name="constants"></a> Constants:
|
||||
|
||||
To use the constants file simply:
|
||||
```js
|
||||
@@ -949,25 +1088,70 @@ var k = openBCIBoard.OpenBCIConstants;
|
||||
console.log(k.OBCISimulatorPortName); // prints OpenBCISimulator to the console.
|
||||
```
|
||||
|
||||
### .OBCISimulatorPortName
|
||||
### <a name="constants-obcisimulatorportname"></a> .OBCISimulatorPortName
|
||||
|
||||
The name of the simulator port.
|
||||
|
||||
## Dev Notes
|
||||
Running
|
||||
-------
|
||||
1. `npm install -D`
|
||||
2. Plug usb module into serial port
|
||||
3. Turn OpenBCI device on
|
||||
4. Type `npm start` into the terminal in the project directory
|
||||
## <a name="interfacing-with-other-tools"></a> Interfacing With Other Tools:
|
||||
|
||||
### <a name="interfacing-with-other-tools-labstreaminglayer"></a> LabStreamingLayer
|
||||
|
||||
[LabStreamingLayer](https://github.com/sccn/labstreaminglayer) by SCCN is a stream management tool designed to time-synchronize multiple data streams, potentially from different sources, over a LAN network with millisecond accuracy (given configuration).
|
||||
|
||||
For example, a VR display device running a Unity simulation may, using the [LSL4Unity](https://github.com/xfleckx/LSL4Unity) library, emit string markers into LSL corresponding to events of interest (For the P300 ERP, this event would be the onset of an attended, unusual noise in a pattern of commonplace ones). The computer doing data collection via the OpenBCI_NodeJS library (potentially with 4ms accuracy) would then output into an LSL stream the EEG and AUX data. LSL can then synchronize the two clocks relative to each other before inputting into a different program or toolkit, like [BCILAB](https://github.com/sccn/BCILAB) for analysis to trigger responses in the Unity display.
|
||||
|
||||
This requires OpenBCI_NodeJS exporting data into LSL. Currently, there does not exist a pre-built NodeJS module for LSL, though LSL comes with tools that could possibly allow creation of one. In the meantime, the simpler route is to use a concurrent python script (driven by NodeJS module [python-shell](https://www.npmjs.com/package/python-shell)) to handoff the data to LSL for you, like so:
|
||||
|
||||
In your NodeJS code, before initializing/connecting to the OpenBCIBoard:
|
||||
```js
|
||||
// Construct LSL Handoff Python Shell
|
||||
var PythonShell = require('python-shell');
|
||||
var lsloutlet = new PythonShell('LslHandoff.py');
|
||||
|
||||
lsloutlet.on('message', function(message){
|
||||
console.log('LslOutlet: ' + message);
|
||||
});
|
||||
console.log('Python Shell Created for LSLHandoff');
|
||||
```
|
||||
|
||||
In your NodeJS code, when reading samples:
|
||||
```js
|
||||
st = sample.channelData.join(' ')
|
||||
//getTime returns milliseconds since midnight 1970/01/01
|
||||
var s = ''+ sample.timeStamp + ': '+ st
|
||||
lsloutlet.send(s)
|
||||
```
|
||||
|
||||
in LSLHandoff.py:
|
||||
```py
|
||||
from pylsl import StreamInfo, StreamOutlet
|
||||
info = StreamInfo('OpenBCI_EEG', 'EEG', 8, 250, 'float32', '[RANDOM NUMBER HERE]')
|
||||
outlet = StreamOutlet(info)
|
||||
while True:
|
||||
strSample = raw_input().split(': ',1)
|
||||
sample = map(float, strSample[1].split(' '))
|
||||
stamp = float(strSample[0])
|
||||
|
||||
outlet.push_sample(sample, stamp)
|
||||
print('Pushed Sample At: ' + strSample[0])
|
||||
```
|
||||
AUX data would be done the same way in a separate LSL stream.
|
||||
|
||||
## <a name="developing"></a> Developing:
|
||||
### <a name="developing-running"></a> Running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### <a name="developing-testing"></a> Testing:
|
||||
|
||||
Testing
|
||||
-------
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Contribute to the library
|
||||
## <a name="contribute"></a> Contribute:
|
||||
|
||||
1. Fork it!
|
||||
2. Create your feature branch: `git checkout -b my-new-feature`
|
||||
3. Make changes and ensure tests all pass. (`npm test`)
|
||||
@@ -975,5 +1159,14 @@ npm test
|
||||
5. Push to the branch: `git push origin my-new-feature`
|
||||
6. Submit a pull request :D
|
||||
|
||||
## License
|
||||
## <a name="license"></a> License:
|
||||
|
||||
MIT
|
||||
|
||||
## <a name="roadmap"></a> Roadmap:
|
||||
|
||||
1. Ganglion integration (2.x)
|
||||
2. Compatible with node streams (3.x)
|
||||
3. Remove factory paradigm from main file (3.x)
|
||||
5. ES6/ES7 total adoption (3.x)
|
||||
4. Browser support (with browser serialport) (x.x)
|
||||
|
||||
@@ -1,3 +1,57 @@
|
||||
# 1.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Add dropped packet detection, new event `droppedPacket` can be added to get an array of dropped packet numbers in the case of the dropped packet event.
|
||||
|
||||
# 1.2.3
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Add table of contents to read me
|
||||
* Reduce size of repo by removing impedance test report
|
||||
|
||||
# 1.2.2
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Upgrade serialport to 4.x
|
||||
|
||||
# 1.2.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed bug where set channel function allowed for channel 0 to be set. Cannot set system to channel 0; lower limit is 1.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Add tutorial/sample code for interfacing the module with lab streaming layer.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Fixed time synced accel to work OpenBCI_32bit_Library release candidate 5 and newer.
|
||||
|
||||
# 1.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Add function `.time()` which should be used in time syncing
|
||||
* Add function `.syncClocksFull()` which should be used for immediate consecutive time syncs
|
||||
* Synced object can be emitted on `synced` event. Check `valid` property for if the sync was done
|
||||
* Add detailed description of object returned on `synced` event to README.md
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Changed option named `timeSync` to `sntpTimeSync`
|
||||
* Removed function called `.sntpNow()` because it was replaced by `.time()`
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Time sync working
|
||||
* Module could not work with local time
|
||||
|
||||
# 1.0.1
|
||||
|
||||
### New Features
|
||||
|
||||
Arquivo binário não exibido.
+211
-93
@@ -3,7 +3,7 @@
|
||||
var EventEmitter = require('events').EventEmitter,
|
||||
util = require('util'),
|
||||
stream = require('stream'),
|
||||
serialPort = require('serialport'),
|
||||
SerialPort = require('serialport'),
|
||||
openBCISample = require('./openBCISample'),
|
||||
k = openBCISample.k,
|
||||
openBCISimulator = require('./openBCISimulator'),
|
||||
@@ -34,7 +34,9 @@ function OpenBCIFactory() {
|
||||
simulatorInjectLineNoise: '60Hz',
|
||||
simulatorSampleRate: 250,
|
||||
simulatorSerialPortFailure:false,
|
||||
timeSync: false,
|
||||
sntpTimeSync: false,
|
||||
sntpTimeSyncHost: 'pool.ntp.org',
|
||||
sntpTimeSyncPort: 123,
|
||||
verbose: false
|
||||
};
|
||||
|
||||
@@ -84,8 +86,13 @@ function OpenBCIFactory() {
|
||||
* - `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely
|
||||
* due to a OpenBCI dongle not being plugged in.
|
||||
*
|
||||
* - `timeSync` - {Boolean} 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.
|
||||
* - `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source
|
||||
* of truth instead of local computer time. If you are running experiements on your local
|
||||
* computer, keep this `false`. (Default `false`)
|
||||
*
|
||||
* - `sntpTimeSyncHost` - {String} The ntp server to use, can be either sntp or ntp. (Defaults `pool.ntp.org`).
|
||||
*
|
||||
* - `sntpTimeSyncPort` - {Number} The port to access the ntp server. (Defaults `123`)
|
||||
*
|
||||
* - `verbose` {Boolean} - Print out useful debugging events
|
||||
*
|
||||
@@ -125,7 +132,9 @@ function OpenBCIFactory() {
|
||||
}
|
||||
opts.simulatorSampleRate = options.simulatorSampleRate || options.simulatorsamplerate || _options.simulatorSampleRate;
|
||||
opts.simulatorSerialPortFailure = options.simulatorSerialPortFailure || options.simulatorserialportfailure || _options.simulatorSerialPortFailure;
|
||||
opts.timeSync = options.timeSync || options.timesync || _options.timeSync;
|
||||
opts.sntpTimeSync = options.sntpTimeSync || options.sntptimesync || _options.sntpTimeSync;
|
||||
opts.sntpTimeSyncHost = options.sntpTimeSyncHost || options.sntptimesynchost || _options.sntpTimeSyncHost;
|
||||
opts.sntpTimeSyncPort = options.sntpTimeSyncPort || options.sntptimesyncport || _options.sntpTimeSyncPort;
|
||||
opts.verbose = options.verbose || _options.verbose;
|
||||
|
||||
// Set to global options object
|
||||
@@ -163,34 +172,27 @@ function OpenBCIFactory() {
|
||||
|
||||
this._lowerChannelsSampleObject = null;
|
||||
this.sync = {
|
||||
active: false,
|
||||
timeGotPacketSent: 0,
|
||||
timeLastBoardTime: 0,
|
||||
timeGotSetPacket: 0,
|
||||
timeRoundTrip: 0,
|
||||
timeTransmission: 0,
|
||||
timeOffset: 0,
|
||||
curSyncObj: null,
|
||||
objArray: [],
|
||||
sntpActive: false,
|
||||
timeOffsetMaster: 0,
|
||||
timeOffsetAvg: 0,
|
||||
timeOffsetArray: []
|
||||
};
|
||||
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)
|
||||
timeOffsetArray: [],
|
||||
};
|
||||
this.writer = null;
|
||||
// Numbers
|
||||
this.badPackets = 0;
|
||||
this.curParsingMode = k.OBCIParsingReset;
|
||||
this.commandsToWrite = 0;
|
||||
this.impedanceArray = openBCISample.impedanceArray(k.numberOfChannelsForBoardType(this.options.boardType));
|
||||
this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort;
|
||||
this.previousSampleNumber = -1;
|
||||
this.sampleCount = 0;
|
||||
this.curParsingMode = k.OBCIParsingReset;
|
||||
this.timeOfPacketArrival = 0;
|
||||
this.writeOutDelay = k.OBCIWriteIntervalDelayMSShort;
|
||||
// Strings
|
||||
|
||||
// NTP
|
||||
if (this.options.timeSync) {
|
||||
if (this.options.sntpTimeSync) {
|
||||
// establishing ntp connection
|
||||
this.sntpStart()
|
||||
.then(() => {
|
||||
@@ -239,7 +241,7 @@ function OpenBCIFactory() {
|
||||
} else {
|
||||
/* istanbul ignore if */
|
||||
if (this.options.verbose) console.log('using real board ' + portName);
|
||||
boardSerial = new serialPort.SerialPort(portName, {
|
||||
boardSerial = new SerialPort(portName, {
|
||||
baudRate: this.options.baudRate
|
||||
},(err) => {
|
||||
if (err) reject(err);
|
||||
@@ -472,14 +474,14 @@ function OpenBCIFactory() {
|
||||
OpenBCIBoard.prototype._writeAndDrain = function(data) {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.serial) reject('Serial port not open');
|
||||
this.serial.write(data,(error,results) => {
|
||||
if(results) {
|
||||
this.serial.write(data,(error) => {
|
||||
if(error) {
|
||||
console.log('Error [writeAndDrain]: ' + error);
|
||||
reject(error);
|
||||
} else {
|
||||
this.serial.drain(function() {
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
console.log('Error [writeAndDrain]: ' + error);
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -503,7 +505,7 @@ function OpenBCIFactory() {
|
||||
if (this.options.verbose) console.log('auto found sim board');
|
||||
resolve(k.OBCISimulatorPortName);
|
||||
} else {
|
||||
serialPort.list((err, ports) => {
|
||||
SerialPort.list((err, ports) => {
|
||||
if(err) {
|
||||
if (this.options.verbose) console.log('serial port err');
|
||||
reject(err);
|
||||
@@ -920,7 +922,7 @@ function OpenBCIFactory() {
|
||||
*/
|
||||
OpenBCIBoard.prototype.listPorts = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
serialPort.list((err, ports) => {
|
||||
SerialPort.list((err, ports) => {
|
||||
if(err) reject(err);
|
||||
else {
|
||||
ports.push( {
|
||||
@@ -1524,7 +1526,7 @@ function OpenBCIFactory() {
|
||||
|
||||
/**
|
||||
* @description Send the command to tell the board to start the syncing protocol. Must be connected,
|
||||
* streaming and using version +2 firmware.
|
||||
* streaming and using at least version 2.0.0 firmware.
|
||||
* **Note**: This functionality requires OpenBCI Firmware Version 2.0
|
||||
* @since 1.0.0
|
||||
* @returns {Promise} - Resolves if sent, rejects if not connected or using firmware verison +2.
|
||||
@@ -1534,13 +1536,45 @@ function OpenBCIFactory() {
|
||||
return new Promise((resolve,reject) => {
|
||||
if (!this.connected) reject('Must be connected to the device');
|
||||
if (!this.streaming) reject('Must be streaming to sync clocks');
|
||||
if (!this.usingVersionTwoFirmware()) reject('Time sync not implemented on V1 firmware, please update');
|
||||
if (!this.usingVersionTwoFirmware()) reject('Time sync not implemented on v1 firmware, please update to v2');
|
||||
this.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
this.sync.curSyncObj.timeSyncSent = this.time();
|
||||
this.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
this._writeAndDrain(k.OBCISyncTimeSet);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Send the command to tell the board to start the syncing protocol. Must be connected,
|
||||
* streaming and using at least version 2.0.0 firmware. Uses the `synced` event to ensure multiple syncs
|
||||
* don't overlap.
|
||||
* **Note**: This functionality requires OpenBCI Firmware Version 2.0
|
||||
* @since 1.1.0
|
||||
* @returns {Promise} - Resolves if `synced` event is emitted, rejects if not connected or using firmware v2.
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.syncClocksFull = function() {
|
||||
return new Promise((resolve,reject) => {
|
||||
if (!this.connected) reject('Must be connected to the device');
|
||||
if (!this.streaming) reject('Must be streaming to sync clocks');
|
||||
if (!this.usingVersionTwoFirmware()) reject('Time sync not implemented on v1 firmware, please update to v2');
|
||||
setTimeout(() => {
|
||||
return reject('syncClocksFull timeout after 500ms with no sync');
|
||||
}, 1000); // Should not take more than 1s to sync up
|
||||
this.once('synced',syncObj => {
|
||||
return resolve(syncObj);
|
||||
});
|
||||
this.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
this.sync.curSyncObj.timeSyncSent = this.time();
|
||||
this.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
this._writeAndDrain(k.OBCISyncTimeSet)
|
||||
.catch(err => {
|
||||
return reject(err);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Consider the '_processBytes' method to be the work horse of this
|
||||
* entire framework. This method gets called any time there is new
|
||||
@@ -1584,7 +1618,7 @@ function OpenBCIFactory() {
|
||||
// If there is only one match
|
||||
if (openBCISample.isTimeSyncSetConfirmationInBuffer(data)) {
|
||||
if (this.options.verbose) console.log(`Found Time Sync Sent`);
|
||||
this.sync.timeGotPacketSent = this.sntpNow();
|
||||
this.sync.curSyncObj.timeSyncSentConfirmation = this.time();
|
||||
this.curParsingMode = k.OBCIParsingNormal;
|
||||
}
|
||||
this.buffer = this._processDataBuffer(data);
|
||||
@@ -1625,6 +1659,8 @@ function OpenBCIFactory() {
|
||||
// tail byte 0xCx where x is the set of numbers from 0-F (hex)
|
||||
if (openBCISample.isStopByte(dataBuffer[parsePosition + k.OBCIPacketSize - 1])) {
|
||||
/** We just qualified a raw packet */
|
||||
// This could be a time set packet!
|
||||
this.timeOfPacketArrival = this.time();
|
||||
// Grab the raw packet, make a copy of it.
|
||||
var rawPacket;
|
||||
if (k.getVersionNumber(process.version) >= 6) {
|
||||
@@ -1700,6 +1736,11 @@ function OpenBCIFactory() {
|
||||
OpenBCIBoard.prototype._processQualifiedPacket = function(rawDataPacketBuffer) {
|
||||
if (!rawDataPacketBuffer) return;
|
||||
if (rawDataPacketBuffer.byteLength !== k.OBCIPacketSize) return;
|
||||
var missedPacketArray = openBCISample.droppedPacketCheck(this.previousSampleNumber, rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber]);
|
||||
if (missedPacketArray) {
|
||||
this.emit('droppedPacket', missedPacketArray);
|
||||
}
|
||||
this.previousSampleNumber = rawDataPacketBuffer[k.OBCIPacketPositionSampleNumber];
|
||||
var packetType = openBCISample.getRawPacketType(rawDataPacketBuffer[k.OBCIPacketPositionStopByte]);
|
||||
switch (packetType) {
|
||||
case k.OBCIStreamPacketStandardAccel:
|
||||
@@ -1712,14 +1753,15 @@ function OpenBCIFactory() {
|
||||
// Do nothing for User Defined Packets
|
||||
break;
|
||||
case k.OBCIStreamPacketAccelTimeSyncSet:
|
||||
this._processPacketTimeSyncSet(rawDataPacketBuffer);
|
||||
// Don't waste any time!
|
||||
this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival);
|
||||
this._processPacketTimeSyncedAccel(rawDataPacketBuffer);
|
||||
break;
|
||||
case k.OBCIStreamPacketAccelTimeSynced:
|
||||
this._processPacketTimeSyncedAccel(rawDataPacketBuffer);
|
||||
break;
|
||||
case k.OBCIStreamPacketRawAuxTimeSyncSet:
|
||||
this._processPacketTimeSyncSet(rawDataPacketBuffer);
|
||||
this._processPacketTimeSyncSet(rawDataPacketBuffer, this.timeOfPacketArrival);
|
||||
this._processPacketTimeSyncedRawAux(rawDataPacketBuffer);
|
||||
break;
|
||||
case k.OBCIStreamPacketRawAuxTimeSynced:
|
||||
@@ -1764,7 +1806,7 @@ function OpenBCIFactory() {
|
||||
OpenBCIBoard.prototype._processPacketStandardAccel = function(rawPacket) {
|
||||
openBCISample.parseRawPacketStandard(rawPacket,this.channelSettingsArray)
|
||||
.then(sampleObject => {
|
||||
//openBCISample.debugPrettyPrint(sampleObject);
|
||||
// openBCISample.debugPrettyPrint(sampleObject);
|
||||
sampleObject.rawPacket = rawPacket;
|
||||
this._finalizeNewSample.call(this,sampleObject);
|
||||
})
|
||||
@@ -1788,49 +1830,124 @@ function OpenBCIFactory() {
|
||||
|
||||
/**
|
||||
* @description A method to parse a stream packet that does not have channel data or aux/accel data, just a timestamp
|
||||
* @param rawPacket - A 33byte data buffer from _processQualifiedPacket
|
||||
* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket
|
||||
* @param timeOfPacketArrival {Number} - The time the packet arrived.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._processPacketTimeSyncSet = function(rawPacket) {
|
||||
OpenBCIBoard.prototype._processPacketTimeSyncSet = function(rawPacket, timeOfPacketArrival) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var currentTime = this.sntpNow();
|
||||
if (this.sync.curSyncObj === null) reject('no sync in progress');
|
||||
// console.log('hey');
|
||||
this.sync.curSyncObj.timeSyncSetPacket = timeOfPacketArrival;
|
||||
|
||||
if (this.options.verbose) console.log('Got time set packet from the board');
|
||||
|
||||
// this.curParsingMode will equal k.OBCIParsingNormal if comma found
|
||||
if (this.curParsingMode === k.OBCIParsingTimeSyncSent) {
|
||||
if (this.options.verbose) console.log(`Missed the time sync sent confirmation, sycned object will not be valid, please resync again`);
|
||||
// Fix the curParsingMode back to normal
|
||||
this.curParsingMode = k.OBCIParsingNormal;
|
||||
// Emit the bad sync object for fun
|
||||
this.emit('synced',openBCISample.newSyncObject());
|
||||
// Set back to null
|
||||
this.sync.curSyncObj = null;
|
||||
// Return will exit this method with the err
|
||||
return reject(`Missed the time sync sent confirmation`);
|
||||
}
|
||||
|
||||
// We got the timeSyncSentConfirmation... continue on
|
||||
openBCISample.getFromTimePacketTime(rawPacket)
|
||||
.then(boardTime => {
|
||||
this.sync.timeRoundTrip = currentTime - this.sync.timeGotPacketSent;
|
||||
this.sync.timeTransmission = math.floor(this.sync.timeRoundTrip / 2); // Must be whole number
|
||||
this.sync.timeOffset = currentTime - this.sync.timeTransmission - boardTime;
|
||||
this.sync.active = true;
|
||||
this.sync.curSyncObj.boardTime = boardTime;
|
||||
// if (this.options.verbose) {
|
||||
// console.log(`Sent sync command at ${this.sync.curSyncObj.timeSyncSent} ms`);
|
||||
// console.log(`Sent confirmation at ${this.sync.curSyncObj.timeSyncSentConfirmation} ms`)
|
||||
// console.log(`Set packet arrived at ${this.sync.curSyncObj.timeSyncSetPacket} ms`);
|
||||
// }
|
||||
|
||||
// Calculate the time between sending the `<` to getting the set packet, call this the round trip length
|
||||
this.sync.curSyncObj.timeRoundTrip = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent;
|
||||
if (this.options.verbose) console.log(`Round trip time: ${this.sync.curSyncObj.timeRoundTrip} ms`);
|
||||
|
||||
|
||||
// If the sync sent conf and set packet arrive in different serial flushes
|
||||
// ------------------------------------------
|
||||
// | | timeTransmission | < GOOD :)
|
||||
// ------------------------------------------
|
||||
// ^ ^ ^
|
||||
// s s s
|
||||
// e e e
|
||||
// n n t packet
|
||||
// t t confirmation
|
||||
//
|
||||
// Assume it's good...
|
||||
this.sync.curSyncObj.timeTransmission = this.sync.curSyncObj.timeRoundTrip - (this.sync.curSyncObj.timeSyncSentConfirmation - this.sync.curSyncObj.timeSyncSent);
|
||||
|
||||
// If the conf and the set packet arrive in the same serial flush we have big problem!
|
||||
// ------------------------------------------
|
||||
// | | | < BAD :(
|
||||
// ------------------------------------------
|
||||
// ^ ^ ^
|
||||
// s s s
|
||||
// e e e
|
||||
// n n t packet
|
||||
// t t confirmation
|
||||
if ((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSentConfirmation) < k.OBCITimeSyncThresholdTransFailureMS) {
|
||||
// Estimate that 75% of the time between sent and set packet was spent on the packet making its way from board to this point
|
||||
this.sync.curSyncObj.timeTransmission = math.floor((this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeSyncSent) * k.OBCITimeSyncMultiplierWithSyncConf);
|
||||
if (this.options.verbose) console.log(`Had to correct transmission time`);
|
||||
this.sync.curSyncObj.correctedTransmissionTime = true;
|
||||
}
|
||||
|
||||
// Calculate the offset #finally
|
||||
this.sync.curSyncObj.timeOffset = this.sync.curSyncObj.timeSyncSetPacket - this.sync.curSyncObj.timeTransmission - boardTime;
|
||||
if (this.options.verbose) {
|
||||
console.log(`Board offset time: ${this.sync.curSyncObj.timeOffset} ms`);
|
||||
console.log(`Board time: ${boardTime}`);
|
||||
}
|
||||
|
||||
|
||||
// Add to array
|
||||
if (this.sync.timeOffsetArray.length >= k.OBCITimeSyncArraySize) {
|
||||
// Shift the oldest one out of the array
|
||||
this.sync.timeOffsetArray.shift();
|
||||
// Push the new value into the array
|
||||
this.sync.timeOffsetArray.push(this.sync.timeOffset);
|
||||
this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset);
|
||||
} else {
|
||||
// Push the new value into the array
|
||||
this.sync.timeOffsetArray.push(this.sync.timeOffset);
|
||||
this.sync.timeOffsetArray.push(this.sync.curSyncObj.timeOffset);
|
||||
}
|
||||
this.sync.timeOffsetAvg = math.floor(math.mean(this.sync.timeOffsetArray));
|
||||
|
||||
|
||||
// Calculate the master time offset that we use averaging to compute
|
||||
if (this.sync.timeOffsetArray.length > 1) {
|
||||
var sum = this.sync.timeOffsetArray.reduce(function(a, b) { return a + b; });
|
||||
this.sync.timeOffsetMaster = math.floor(sum / this.sync.timeOffsetArray.length);
|
||||
|
||||
} else {
|
||||
this.sync.timeOffsetMaster = this.sync.curSyncObj.timeOffset;
|
||||
}
|
||||
|
||||
if (this.options.verbose) {
|
||||
console.log(`Time Sent Confirmation Found was ${this.sync.timeGotPacketSent}`)
|
||||
console.log(`Current time is ${currentTime}`);
|
||||
console.log(`Their difference is ${currentTime - this.sync.timeGotPacketSent}`);
|
||||
console.log(`Board time: ${boardTime}`);
|
||||
console.log(`Round trip time: ${this.sync.timeRoundTrip} ms`);
|
||||
console.log(`Transmission time: ${this.sync.timeTransmission} ms`);
|
||||
console.log(`Board offset time: ${this.sync.timeOffset}`);
|
||||
console.log(`Corrected board time: ${(this.sync.timeOffset + boardTime)} ms`);
|
||||
console.log(`Average across sync offset: ${this.sync.timeOffsetAvg}`);
|
||||
console.log(`Corrected board time with average: ${(this.sync.timeOffset + boardTime)} ms`);
|
||||
console.log(`Master offset ${this.sync.timeOffsetMaster} ms`);
|
||||
}
|
||||
this.emit('synced',this.sync);
|
||||
resolve(rawPacket);
|
||||
|
||||
// Set the valid object to true
|
||||
this.sync.curSyncObj.valid = true;
|
||||
|
||||
// Emit it!
|
||||
this.emit('synced',this.sync.curSyncObj);
|
||||
// Save obj to the global array
|
||||
this.sync.objArray.push(this.sync.curSyncObj);
|
||||
// Set to null
|
||||
this.sync.curSyncObj = null;
|
||||
return resolve(rawPacket);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Error in _processPacketTimeSyncSet', err)
|
||||
reject(err);
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1844,7 +1961,7 @@ function OpenBCIFactory() {
|
||||
*/
|
||||
OpenBCIBoard.prototype._processPacketTimeSyncedAccel = function(rawPacket) {
|
||||
// if (this.sync.active === false) console.log('Need to sync with board...');
|
||||
openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffset, this.accelArray)
|
||||
openBCISample.parsePacketTimeSyncedAccel(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster, this.accelArray)
|
||||
.then((sampleObject) => {
|
||||
sampleObject.rawPacket = rawPacket;
|
||||
this._finalizeNewSample.call(this,sampleObject);
|
||||
@@ -1855,13 +1972,13 @@ function OpenBCIFactory() {
|
||||
/**
|
||||
* @description A method to parse a stream packet that contains channel data, a time stamp and two extra bytes that
|
||||
* shall be emitted as a raw buffer and not scaled.
|
||||
* @param rawPacket - A 33byte data buffer from _processQualifiedPacket
|
||||
* @param rawPacket {Buffer} - A 33byte data buffer from _processQualifiedPacket
|
||||
* @private
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._processPacketTimeSyncedRawAux = function(rawPacket) {
|
||||
// if (this.sync.active === false) console.log('Need to sync with board...');
|
||||
openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffset)
|
||||
openBCISample.parsePacketTimeSyncedRawAux(rawPacket, this.channelSettingsArray, this.sync.timeOffsetMaster)
|
||||
.then(sampleObject => {
|
||||
this._finalizeNewSample.call(this,sampleObject);
|
||||
})
|
||||
@@ -1871,7 +1988,7 @@ function OpenBCIFactory() {
|
||||
/**
|
||||
* @description A method to emit samples through the EventEmitter channel `sample` or compute impedances if are
|
||||
* being tested.
|
||||
* @param sampleObject - A sample object that follows the normal standards.
|
||||
* @param sampleObject {Object} - A sample object that follows the normal standards.
|
||||
* @private
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
@@ -1897,8 +2014,9 @@ function OpenBCIFactory() {
|
||||
* sample object only when the upper channels arrive in an even sampleNumber sample object. No sample will be
|
||||
* emitted on an even sampleNumber if _lowerChannelsSampleObject is null and one will be added to the
|
||||
* missedPacket counter. Further missedPacket will increase if two odd sampleNumber packets arrive in a row.
|
||||
* @param sampleObject
|
||||
* @param sampleObject {Object} - The sample object to finalize
|
||||
* @private
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype._finalizeNewSampleForDaisy = function(sampleObject) {
|
||||
if(openBCISample.isOdd(sampleObject.sampleNumber)) {
|
||||
@@ -1948,28 +2066,6 @@ function OpenBCIFactory() {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @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...
|
||||
*/
|
||||
@@ -1977,34 +2073,56 @@ function OpenBCIFactory() {
|
||||
|
||||
/**
|
||||
* @description This gets the time plus offset
|
||||
* @private
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntpNow = Sntp.now;
|
||||
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() {
|
||||
OpenBCIBoard.prototype.sntpStart = function(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Sntp.start((err) => {
|
||||
this.options.sntpTimeSync = true;
|
||||
Sntp.start({
|
||||
host: this.options.sntpTimeSyncHost, // Defaults to pool.ntp.org
|
||||
port: this.options.sntpTimeSyncPort, // Defaults to 123 (NTP)
|
||||
clockSyncRefresh: 30 * 60 * 1000 // Resync every 30 minutes
|
||||
}, err => {
|
||||
if (err) {
|
||||
this.sync.active = true;
|
||||
this.sync.sntpActive = false;
|
||||
reject(err);
|
||||
} else {
|
||||
this.sync.active = false;
|
||||
this.sync.sntpActive = true;
|
||||
resolve();
|
||||
this.emit('sntpTimeLock');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Stops the sntp from updating
|
||||
* @description Stops the sntp from updating.
|
||||
*/
|
||||
OpenBCIBoard.prototype.sntpStop = function() {
|
||||
Sntp.stop();
|
||||
this.sync.active = false;
|
||||
this.options.sntpTimeSync = false;
|
||||
this.sync.sntpActive = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @description Should use sntp time when sntpTimeSync specified in options, or else use Date.now() for time
|
||||
* @returns {Number} - The time
|
||||
* @author AJ Keller (@pushtheworldllc)
|
||||
*/
|
||||
OpenBCIBoard.prototype.time = function() {
|
||||
if (this.options.sntpTimeSync) {
|
||||
return this._sntpNow();
|
||||
} else {
|
||||
return Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+18
-7
@@ -199,6 +199,9 @@ const OBCISimulatorLineNoiseNone = 'None';
|
||||
const OBCISampleRate125 = 125;
|
||||
const OBCISampleRate250 = 250;
|
||||
|
||||
/** Max sample number */
|
||||
const OBCISampleNumberMax = 255;
|
||||
|
||||
/** Packet Size */
|
||||
const OBCIPacketSize = 33;
|
||||
|
||||
@@ -275,9 +278,9 @@ const OBCIStreamPacketRawAuxTimeSynced = 6; // 0110
|
||||
const OBCIStreamPacketTimeByteSize = 4;
|
||||
|
||||
/** Time synced with accel packet */
|
||||
const OBCIAccelAxisX = 0;
|
||||
const OBCIAccelAxisY = 1;
|
||||
const OBCIAccelAxisZ = 2;
|
||||
const OBCIAccelAxisX = 7;
|
||||
const OBCIAccelAxisY = 8;
|
||||
const OBCIAccelAxisZ = 9;
|
||||
|
||||
/** Firmware version indicator */
|
||||
const OBCIFirmwareV1 = 'v1';
|
||||
@@ -306,12 +309,15 @@ const OBCISimulatorStandard = 'standard';
|
||||
|
||||
/** OpenBCI Radio Limits */
|
||||
const OBCIRadioChannelMax = 25;
|
||||
const OBCIRadioChannelMin = 0;
|
||||
const OBCIRadioChannelMin = 1;
|
||||
const OBCIRadioPollTimeMax = 255;
|
||||
const OBCIRadioPollTimeMin = 0;
|
||||
|
||||
/** Time sync array size */
|
||||
const OBCITimeSyncArraySize = 10;
|
||||
/** Time sync stuff */
|
||||
const OBCITimeSyncArraySize = 10;
|
||||
const OBCITimeSyncMultiplierWithSyncConf = 0.9;
|
||||
const OBCITimeSyncMultiplierWithoutSyncConf = 0.75;
|
||||
const OBCITimeSyncThresholdTransFailureMS = 10; //ms
|
||||
|
||||
/** Baud Rates */
|
||||
const OBCIRadioBaudRateDefault = 115200;
|
||||
@@ -726,6 +732,8 @@ module.exports = {
|
||||
/** Possible Sample Rates */
|
||||
OBCISampleRate125,
|
||||
OBCISampleRate250,
|
||||
/** Max sample number */
|
||||
OBCISampleNumberMax,
|
||||
/** Packet Size */
|
||||
OBCIPacketSize,
|
||||
/** Notable Bytes */
|
||||
@@ -845,8 +853,11 @@ module.exports = {
|
||||
OBCIRadioChannelMin,
|
||||
OBCIRadioPollTimeMax,
|
||||
OBCIRadioPollTimeMin,
|
||||
/** Time sync array size */
|
||||
/** Time sync stuff */
|
||||
OBCITimeSyncArraySize,
|
||||
OBCITimeSyncMultiplierWithSyncConf,
|
||||
OBCITimeSyncMultiplierWithoutSyncConf,
|
||||
OBCITimeSyncThresholdTransFailureMS,
|
||||
/** Baud Rates */
|
||||
OBCIRadioBaudRateDefault,
|
||||
OBCIRadioBaudRateDefaultStr,
|
||||
|
||||
+57
-7
@@ -521,7 +521,43 @@ var sampleModule = {
|
||||
isSuccessInBuffer,
|
||||
isTimeSyncSetConfirmationInBuffer,
|
||||
makeTailByteFromPacketType,
|
||||
isStopByte
|
||||
isStopByte,
|
||||
newSyncObject,
|
||||
/**
|
||||
* @description Checks to make sure the previous sample number is one less
|
||||
* then the new sample number. Takes into account sample numbers wrapping
|
||||
* around at 255.
|
||||
* @param `previousSampleNumber` {Number} - An integer number of the previous
|
||||
* sample number.
|
||||
* @param `newSampleNumber` {Number} - An integer number of the new sample
|
||||
* number.
|
||||
* @returns {Array} - Returns null if there is no dropped packets, otherwise,
|
||||
* or on a missed packet, an array of their packet numbers is returned.
|
||||
*/
|
||||
droppedPacketCheck: (previousSampleNumber, newSampleNumber) => {
|
||||
if (previousSampleNumber === k.OBCISampleNumberMax && newSampleNumber === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (newSampleNumber - previousSampleNumber === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var missedPacketArray = [];
|
||||
|
||||
if (previousSampleNumber > newSampleNumber) {
|
||||
var numMised = k.OBCISampleNumberMax - previousSampleNumber;
|
||||
for (var i = 0; i < numMised; i++) {
|
||||
missedPacketArray.push(previousSampleNumber + i + 1);
|
||||
}
|
||||
previousSampleNumber = -1;
|
||||
}
|
||||
|
||||
for (var i = 1; i < (newSampleNumber - previousSampleNumber); i++) {
|
||||
missedPacketArray.push(previousSampleNumber + i);
|
||||
}
|
||||
return missedPacketArray;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = sampleModule;
|
||||
@@ -540,6 +576,20 @@ function newImpedanceObject(channelNumber) {
|
||||
}
|
||||
}
|
||||
|
||||
function newSyncObject() {
|
||||
return {
|
||||
boardTime: 0,
|
||||
correctedTransmissionTime: false,
|
||||
timeSyncSent: 0,
|
||||
timeSyncSentConfirmation: 0,
|
||||
timeSyncSetPacket: 0,
|
||||
timeRoundTrip: 0,
|
||||
timeTransmission: 0,
|
||||
timeOffset: 0,
|
||||
valid: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description This method parses a 33 byte OpenBCI V3 packet and converts to a sample object
|
||||
* @param dataBuf - 33 byte packet that has bytes:
|
||||
@@ -761,9 +811,9 @@ function getFromTimePacketTime(dataBuf) {
|
||||
|
||||
/**
|
||||
* @description Grabs an accel value from a raw but time synced packet. Important that this utilizes the fact that:
|
||||
* X axis data is sent with every sampleNumber % 10 === 0
|
||||
* Y axis data is sent with every sampleNumber % 10 === 1
|
||||
* Z axis data is sent with every sampleNumber % 10 === 2
|
||||
* X axis data is sent with every sampleNumber % 10 === 7
|
||||
* Y axis data is sent with every sampleNumber % 10 === 8
|
||||
* Z axis data is sent with every sampleNumber % 10 === 9
|
||||
* @param dataBuf {Buffer} - The 33byte raw time synced accel packet
|
||||
* @param accelArray {Array} - A 3 element array that allows us to have inter packet memory of x and y axis data and emit only on the z axis packets.
|
||||
* @returns {Promise} - Fulfills with a boolean that is true only when the accel array is ready to be emitted... i.e. when this is a Z axis packet
|
||||
@@ -776,15 +826,15 @@ function getFromTimePacketAccel(dataBuf, accelArray) {
|
||||
var sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber];
|
||||
switch (sampleNumber % 10) { // The accelerometer is on a 25Hz sample rate, so every ten channel samples, we can get new data
|
||||
case k.OBCIAccelAxisX:
|
||||
accelArray[k.OBCIAccelAxisX] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
accelArray[0] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
resolve(false);
|
||||
break;
|
||||
case k.OBCIAccelAxisY:
|
||||
accelArray[k.OBCIAccelAxisY] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
accelArray[1] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
resolve(false);
|
||||
break;
|
||||
case k.OBCIAccelAxisZ:
|
||||
accelArray[k.OBCIAccelAxisZ] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
accelArray[2] = sampleModule.interpret16bitAsInt32(dataBuf.slice(lastBytePosition, lastBytePosition + 2)) * SCALE_FACTOR_ACCEL; // slice is not inclusive on the right
|
||||
resolve(true);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -219,7 +219,9 @@ function OpenBCISimulatorFactory() {
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._syncUp = function() {
|
||||
this.sendSyncSetPacket = true;
|
||||
setTimeout(() => {
|
||||
this.sendSyncSetPacket = true;
|
||||
}, 12); // 3 packets later
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printEOT = function () {
|
||||
|
||||
+4
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openbci",
|
||||
"version": "1.0.1",
|
||||
"version": "1.3.0",
|
||||
"description": "The official Node.js SDK for the OpenBCI Biosensor Board.",
|
||||
"main": "openBCIBoard",
|
||||
"scripts": {
|
||||
@@ -17,10 +17,9 @@
|
||||
"dependencies": {
|
||||
"buffer-equal": "^1.0.0",
|
||||
"gaussian": "^1.0.0",
|
||||
"istanbul": "^0.4.2",
|
||||
"mathjs": "^3.3.0",
|
||||
"performance-now": "^0.2.0",
|
||||
"serialport": "3.1.2",
|
||||
"serialport": "4.0.1",
|
||||
"sntp": "^2.0.0",
|
||||
"streamsearch": "^0.1.2"
|
||||
},
|
||||
@@ -28,11 +27,11 @@
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"buffer-equal": "^1.0.0",
|
||||
"chai": "^3.4.1",
|
||||
"chai-as-promised": "^5.2.0",
|
||||
"codecov": "^1.0.1",
|
||||
"mocha": "^2.3.4",
|
||||
"istanbul": "^0.4.4",
|
||||
"mocha": "^3.0.2",
|
||||
"sandboxed-module": "^2.0.3",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0"
|
||||
|
||||
@@ -291,6 +291,20 @@ describe('OpenBCIConstants', function() {
|
||||
assert.equal('1', k.OBCIChannelImpedanceTestSignalApplied);
|
||||
});
|
||||
});
|
||||
describe('Time Sync Stuff',function() {
|
||||
it('Can get proper array size',function() {
|
||||
assert.equal(10, k.OBCITimeSyncArraySize);
|
||||
});
|
||||
it('Get correct time sync with conf',function() {
|
||||
assert.equal(0.9, k.OBCITimeSyncMultiplierWithSyncConf);
|
||||
});
|
||||
it('Get correct time sync without conf',function() {
|
||||
assert.equal(0.75, k.OBCITimeSyncMultiplierWithoutSyncConf);
|
||||
});
|
||||
it('Get correct time sync transmission threshold',function() {
|
||||
assert.equal(10, k.OBCITimeSyncThresholdTransFailureMS);
|
||||
});
|
||||
});
|
||||
describe('SD card Commands',function() {
|
||||
it('logs for 1 hour', function() {
|
||||
assert.equal('G', k.OBCISDLogForHour1);
|
||||
@@ -460,13 +474,13 @@ describe('OpenBCIConstants', function() {
|
||||
});
|
||||
describe('Time synced with accel packet',function() {
|
||||
it('X axis',function () {
|
||||
assert.equal(0, k.OBCIAccelAxisX);
|
||||
assert.equal(7, k.OBCIAccelAxisX);
|
||||
});
|
||||
it('Y axis',function () {
|
||||
assert.equal(1, k.OBCIAccelAxisY);
|
||||
assert.equal(8, k.OBCIAccelAxisY);
|
||||
});
|
||||
it('Z axis',function () {
|
||||
assert.equal(2, k.OBCIAccelAxisZ);
|
||||
assert.equal(9, k.OBCIAccelAxisZ);
|
||||
});
|
||||
});
|
||||
describe('Time sync useful numbers',function() {
|
||||
@@ -653,78 +667,78 @@ describe('OpenBCIConstants', function() {
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 2', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '@';
|
||||
var result = k.commandChannelOn(2);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 3', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '#';
|
||||
var result = k.commandChannelOn(3);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 4', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '$';
|
||||
var result = k.commandChannelOn(4);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 5', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '%';
|
||||
var result = k.commandChannelOn(5);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 6', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '^';
|
||||
var result = k.commandChannelOn(6);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 7', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '&';
|
||||
var result = k.commandChannelOn(7);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 8', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = '*';
|
||||
var result = k.commandChannelOn(8);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 9', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'Q';
|
||||
var result = k.commandChannelOn(9);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 10', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'W';
|
||||
var result = k.commandChannelOn(10);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 11', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'E';
|
||||
var result = k.commandChannelOn(11);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 12', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'R';
|
||||
var result = k.commandChannelOn(12);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 13', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'T';
|
||||
var result = k.commandChannelOn(13);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 14', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'Y';
|
||||
var result = k.commandChannelOn(14);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 15', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'U';
|
||||
var result = k.commandChannelOn(15);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Channel 16', function() {
|
||||
var expectation = '!';
|
||||
var result = k.commandChannelOn(1);
|
||||
var expectation = 'I';
|
||||
var result = k.commandChannelOn(16);
|
||||
return expect(result).to.eventually.equal(expectation);
|
||||
});
|
||||
it('Invalid channel request', function() {
|
||||
@@ -751,12 +765,17 @@ describe('OpenBCIConstants', function() {
|
||||
assert.equal(250, k.OBCISampleRate250);
|
||||
});
|
||||
});
|
||||
describe('Max sample number',function() {
|
||||
it('should be 255',function () {
|
||||
assert.equal(255, k.OBCISampleNumberMax);
|
||||
});
|
||||
});
|
||||
describe("Radio Channel Limits", function() {
|
||||
it("should get the right channel number max",function () {
|
||||
expect(k.OBCIRadioChannelMax).to.be.equal(25);
|
||||
});
|
||||
it("should get the right channel number min",function () {
|
||||
expect(k.OBCIRadioChannelMin).to.be.equal(0);
|
||||
expect(k.OBCIRadioChannelMin).to.be.equal(1);
|
||||
});
|
||||
it("should get the right poll time max",function () {
|
||||
expect(k.OBCIRadioPollTimeMax).to.be.equal(255);
|
||||
|
||||
@@ -5,9 +5,9 @@ var assert = require('assert');
|
||||
var openBCISample = require('../openBCISample');
|
||||
var sinon = require('sinon');
|
||||
var chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should(),
|
||||
expect = chai.expect;
|
||||
expect = chai.expect,
|
||||
assert = chai.assert;
|
||||
|
||||
var chaiAsPromised = require("chai-as-promised");
|
||||
var sinonChai = require("sinon-chai");
|
||||
@@ -259,9 +259,9 @@ describe('openBCISample',function() {
|
||||
describe('#getFromTimePacketAccel',function() {
|
||||
var packet;
|
||||
|
||||
it('should emit and array if z axis i.e. sampleNumber % 10 === 2', function(done) {
|
||||
it('should emit and array if z axis i.e. sampleNumber % 10 === 9', function(done) {
|
||||
// Make a packet with a sample number that represents z axis
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(2);
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(9);
|
||||
openBCISample.getFromTimePacketAccel(packet,accelArray)
|
||||
.then(isZAxis => {
|
||||
// accel array ready
|
||||
@@ -272,19 +272,19 @@ describe('openBCISample',function() {
|
||||
done(err);
|
||||
})
|
||||
});
|
||||
it('false if sample number is not sampleNumber % 10 === 2', function(done) {
|
||||
it(`false if sample number is not sampleNumber % 10 === ${k.OBCIAccelAxisZ}`, function(done) {
|
||||
// Make a packet that is anything but the z axis
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(0);
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX);
|
||||
openBCISample.getFromTimePacketAccel(packet,accelArray)
|
||||
.then(isZAxis => {
|
||||
// Accel array not ready for sampleNumber % 10 === 0
|
||||
// Accel array not ready for sampleNumber % 10 === 7
|
||||
isZAxis.should.equal(false);
|
||||
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(1);
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY);
|
||||
return openBCISample.getFromTimePacketAccel(packet,accelArray);
|
||||
})
|
||||
.then(isZAxis => {
|
||||
// Accel array not ready for sampleNumber % 10 === 1
|
||||
// Accel array not ready for sampleNumber % 10 === 8
|
||||
isZAxis.should.equal(false);
|
||||
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(34);
|
||||
@@ -294,11 +294,11 @@ describe('openBCISample',function() {
|
||||
// Accel array not ready for sampleNumber % 10 === 4
|
||||
isZAxis.should.equal(false);
|
||||
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(99);
|
||||
packet = openBCISample.samplePacketAccelTimeSynced(100);
|
||||
return openBCISample.getFromTimePacketAccel(packet,accelArray);
|
||||
})
|
||||
.then(isZAxis => {
|
||||
// Accel array not ready for sampleNumber % 10 === 9
|
||||
// Accel array not ready for sampleNumber % 10 === 0
|
||||
isZAxis.should.equal(false);
|
||||
done();
|
||||
})
|
||||
@@ -316,14 +316,14 @@ describe('openBCISample',function() {
|
||||
// Global array (at least it's global in practice) to store accel data between packets
|
||||
var packet1, packet2, packet3;
|
||||
|
||||
it('should only include accel data array on sampleNumber%10 === 2', function(done) {
|
||||
it(`should only include accel data array on sampleNumber%10 === ${k.OBCIAccelAxisZ}`, function(done) {
|
||||
// Generate three packets, packets only get one axis value per packet
|
||||
// X axis data is sent with every sampleNumber % 10 === 0
|
||||
packet1 = openBCISample.samplePacketAccelTimeSynced(0);
|
||||
// Y axis data is sent with every sampleNumber % 10 === 1
|
||||
packet2 = openBCISample.samplePacketAccelTimeSynced(1);
|
||||
// Z axis data is sent with every sampleNumber % 10 === 2
|
||||
packet3 = openBCISample.samplePacketAccelTimeSynced(2);
|
||||
// X axis data is sent with every sampleNumber % 10 === 7
|
||||
packet1 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX);
|
||||
// Y axis data is sent with every sampleNumber % 10 === 8
|
||||
packet2 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY);
|
||||
// Z axis data is sent with every sampleNumber % 10 === 9
|
||||
packet3 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisZ);
|
||||
|
||||
openBCISample.parsePacketTimeSyncedAccel(packet1,defaultChannelSettingsArray,0,accelArray)
|
||||
.then(sampleObject => {
|
||||
@@ -342,12 +342,12 @@ describe('openBCISample',function() {
|
||||
});
|
||||
it('should convert raw numbers into g\'s with scale factor',function(done) {
|
||||
// Generate three packets, packets only get one axis value per packet
|
||||
// X axis data is sent with every sampleNumber % 10 === 0
|
||||
packet1 = openBCISample.samplePacketAccelTimeSynced(0);
|
||||
// Y axis data is sent with every sampleNumber % 10 === 1
|
||||
packet2 = openBCISample.samplePacketAccelTimeSynced(1);
|
||||
// Z axis data is sent with every sampleNumber % 10 === 2
|
||||
packet3 = openBCISample.samplePacketAccelTimeSynced(2);
|
||||
// X axis data is sent with every sampleNumber % 10 === 7
|
||||
packet1 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisX);
|
||||
// Y axis data is sent with every sampleNumber % 10 === 8
|
||||
packet2 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisY);
|
||||
// Z axis data is sent with every sampleNumber % 10 === 9
|
||||
packet3 = openBCISample.samplePacketAccelTimeSynced(k.OBCIAccelAxisZ);
|
||||
|
||||
openBCISample.parsePacketTimeSyncedAccel(packet1,defaultChannelSettingsArray,0,accelArray)
|
||||
.then(() => {
|
||||
@@ -1344,8 +1344,73 @@ describe('openBCISample',function() {
|
||||
expect(openBCISample.makeTailByteFromPacketType(-2)).to.equal(0xC0);
|
||||
});
|
||||
});
|
||||
describe('#newSyncObject',function() {
|
||||
var syncObj = openBCISample.newSyncObject();
|
||||
it("should have property timeSyncSent",function() {
|
||||
expect(syncObj).to.have.property("timeSyncSent",0);
|
||||
});
|
||||
it("should have property timeOffset",function() {
|
||||
expect(syncObj).to.have.property("timeOffset",0);
|
||||
});
|
||||
it("should have property timeRoundTrip",function() {
|
||||
expect(syncObj).to.have.property("timeRoundTrip",0);
|
||||
});
|
||||
it("should have property timeTransmission",function() {
|
||||
expect(syncObj).to.have.property("timeTransmission",0);
|
||||
});
|
||||
it("should have property timeSyncSentConfirmation",function() {
|
||||
expect(syncObj).to.have.property("timeSyncSentConfirmation",0);
|
||||
});
|
||||
it("should have property timeSyncSetPacket",function() {
|
||||
expect(syncObj).to.have.property("timeSyncSetPacket",0);
|
||||
});
|
||||
it("should have property valid",function() {
|
||||
expect(syncObj).to.have.property("valid",false);
|
||||
});
|
||||
it("should have property correctedTransmissionTime",function() {
|
||||
expect(syncObj).to.have.property("correctedTransmissionTime",false);
|
||||
});
|
||||
it("should have property boardTime",function() {
|
||||
expect(syncObj).to.have.property("boardTime",0);
|
||||
});
|
||||
});
|
||||
describe('#droppedPacketCheck',function() {
|
||||
it("should return an array of missed packet numbers",function() {
|
||||
previous = 0;
|
||||
current = previous + 2;
|
||||
assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [1], "dropped one packet");
|
||||
|
||||
previous = 0;
|
||||
current = previous + 4;
|
||||
assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [1,2,3], "dropped three packets");
|
||||
|
||||
previous = 255;
|
||||
current = 2;
|
||||
assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [0,1], "dropped two packets on wrap edge!");
|
||||
|
||||
previous = 254;
|
||||
current = 2;
|
||||
assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [255,0,1], "dropped three packets on wrap!");
|
||||
|
||||
previous = 250;
|
||||
current = 1;
|
||||
assert.sameMembers(openBCISample.droppedPacketCheck(previous, current), [251,252,253,254,255,0], "dropped a bunch of packets on wrap!");
|
||||
|
||||
});
|
||||
it("should roll over when 255 was previous and current is 0", function() {
|
||||
previous = 255;
|
||||
current = 0;
|
||||
expect(openBCISample.droppedPacketCheck(previous, current)).to.be.null;
|
||||
});
|
||||
it("should return null when previous is one less then new sample number", function() {
|
||||
previous = 0;
|
||||
current = previous + 1;
|
||||
expect(openBCISample.droppedPacketCheck(previous, current)).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#goertzelProcessSample', function() {
|
||||
var numberOfChannels = k.OBCINumberOfChannelsDefault;
|
||||
var goertzelObj = openBCISample.goertzelNewObject(numberOfChannels);
|
||||
|
||||
+383
-74
@@ -77,7 +77,8 @@ describe('openbci-sdk',function() {
|
||||
expect(board.options.simulatorInjectLineNoise).to.equal(k.OBCISimulatorLineNoiseHz60);
|
||||
expect(board.options.simulatorSampleRate).to.equal(k.OBCISampleRate250);
|
||||
expect(board.options.simulatorSerialPortFailure).to.be.false;
|
||||
expect(board.options.timeSync).to.be.false;
|
||||
expect(board.options.sntpTimeSync).to.be.false;
|
||||
expect(board.options.sntpTimeSyncHost).to.equal('pool.ntp.org');
|
||||
expect(board.options.verbose).to.be.false;
|
||||
expect(board.sampleRate()).to.equal(250);
|
||||
expect(board.numberOfChannels()).to.equal(8);
|
||||
@@ -241,14 +242,38 @@ describe('openbci-sdk',function() {
|
||||
});
|
||||
it('should be able to enter sync mode', function() {
|
||||
var ourBoard1 = new openBCIBoard.OpenBCIBoard({
|
||||
timeSync: true
|
||||
sntpTimeSync: true
|
||||
});
|
||||
expect(ourBoard1.options.timeSync).to.be.true;
|
||||
expect(ourBoard1.options.sntpTimeSync).to.be.true;
|
||||
// Verify multi case support
|
||||
var ourBoard2 = new openBCIBoard.OpenBCIBoard({
|
||||
timesync: true
|
||||
sntptimesync: true
|
||||
});
|
||||
expect(ourBoard2.options.timeSync).to.be.true;
|
||||
expect(ourBoard2.options.sntpTimeSync).to.be.true;
|
||||
});
|
||||
it('should be able to change the ntp pool host', function() {
|
||||
var expectedPoolName = 'time.apple.com';
|
||||
var ourBoard1 = new openBCIBoard.OpenBCIBoard({
|
||||
sntpTimeSyncHost: expectedPoolName
|
||||
});
|
||||
expect(ourBoard1.options.sntpTimeSyncHost).to.equal(expectedPoolName);
|
||||
// Verify multi case support
|
||||
var ourBoard2 = new openBCIBoard.OpenBCIBoard({
|
||||
sntptimesynchost: expectedPoolName
|
||||
});
|
||||
expect(ourBoard2.options.sntpTimeSyncHost).to.equal(expectedPoolName);
|
||||
});
|
||||
it('should be able to change the ntp pool port', function() {
|
||||
var expectedPortNumber = 73;
|
||||
var ourBoard1 = new openBCIBoard.OpenBCIBoard({
|
||||
sntpTimeSyncPort: expectedPortNumber
|
||||
});
|
||||
expect(ourBoard1.options.sntpTimeSyncPort).to.equal(expectedPortNumber);
|
||||
// Verify multi case support
|
||||
var ourBoard2 = new openBCIBoard.OpenBCIBoard({
|
||||
sntptimesyncport: expectedPortNumber
|
||||
});
|
||||
expect(ourBoard2.options.sntpTimeSyncPort).to.equal(expectedPortNumber);
|
||||
});
|
||||
it('can enter verbose mode', function() {
|
||||
ourBoard = new openBCIBoard.OpenBCIBoard({
|
||||
@@ -375,9 +400,6 @@ describe('openbci-sdk',function() {
|
||||
}).catch(err => done(err));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('#boardTests', function() {
|
||||
this.timeout(3000);
|
||||
before(function() {
|
||||
@@ -889,6 +911,8 @@ describe('openbci-sdk',function() {
|
||||
funcSpyTimeSyncSet.reset();
|
||||
funcSpyTimeSyncedAccel.reset();
|
||||
funcSpyTimeSyncedRawAux.reset();
|
||||
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
});
|
||||
after(function() {
|
||||
// ourBoard = null;
|
||||
@@ -986,8 +1010,296 @@ describe('openbci-sdk',function() {
|
||||
funcSpyTimeSyncedAccel.should.not.have.been.called;
|
||||
funcSpyTimeSyncedRawAux.should.not.have.been.called;
|
||||
});
|
||||
it("should emit a dropped packet on dropped packet",function(done) {
|
||||
// Set to default state
|
||||
ourBoard.previousSampleNumber = -1;
|
||||
var sampleNumber0 = openBCISample.samplePacket(0);
|
||||
ourBoard.once('droppedPacket',() => {
|
||||
done();
|
||||
});
|
||||
var sampleNumber2 = openBCISample.samplePacket(2);
|
||||
// Call the function under test
|
||||
ourBoard._processDataBuffer(sampleNumber0);
|
||||
ourBoard._processDataBuffer(sampleNumber2);
|
||||
});
|
||||
it("should emit a dropped packet on dropped packet with edge",function(done) {
|
||||
// Set to default state
|
||||
var count = 0;
|
||||
ourBoard.previousSampleNumber = 253;
|
||||
var buf1 = openBCISample.samplePacket(254);
|
||||
var countFunc = arr => {
|
||||
count++;
|
||||
};
|
||||
ourBoard.on('droppedPacket', countFunc);
|
||||
var buf2 = openBCISample.samplePacket(0);
|
||||
var buf3 = openBCISample.samplePacket(1);
|
||||
// Call the function under test
|
||||
ourBoard._processDataBuffer(buf1);
|
||||
ourBoard._processDataBuffer(buf2);
|
||||
ourBoard._processDataBuffer(buf3);
|
||||
setTimeout(() => {
|
||||
ourBoard.removeListener('droppedPacket', countFunc);
|
||||
expect(count).to.equal(1);
|
||||
done();
|
||||
}, 10)
|
||||
});
|
||||
});
|
||||
|
||||
describe("#_processPacketTimeSyncSet", function() {
|
||||
var timeSyncSetPacket;
|
||||
var ourBoard;
|
||||
before(() => {
|
||||
ourBoard = new openBCIBoard.OpenBCIBoard({
|
||||
verbose: false
|
||||
});
|
||||
|
||||
});
|
||||
beforeEach(() => {
|
||||
timeSyncSetPacket = openBCISample.samplePacketRawAuxTimeSyncSet();
|
||||
ourBoard.sync.timeOffsetArray = [];
|
||||
});
|
||||
afterEach(() => {
|
||||
ourBoard.sync.curSyncObj = null;
|
||||
});
|
||||
it("should reject if no sync in progress", function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived).should.be.rejected.and.notify(done);
|
||||
});
|
||||
it("should reject if no sent confimation found", function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived).should.be.rejected.and.notify(done);
|
||||
});
|
||||
it("should reset global sync variables if no sent confimation found", function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived)
|
||||
.then(() => {
|
||||
done("failed to get rejected");
|
||||
})
|
||||
.catch(function(err) {
|
||||
expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal);
|
||||
expect(ourBoard.sync.curSyncObj).to.be.null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should emit sycned event with valid false", function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.valid).to.be.false;
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
it("should calculate round trip time as the difference between time sent and time set packet arrived",function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
var expectedRoundTripTime = 20; //ms
|
||||
ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found
|
||||
// Make a new object!
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// Set the sent time
|
||||
ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime;
|
||||
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.timeRoundTrip).to.equal(expectedRoundTripTime);
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
it("should calculate transmission time as the difference between round trip time and (sentConf - sent) when set arrived - sent conf is larger than threshold",function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
var expectedRoundTripTime = 20; //ms
|
||||
var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 1; // 9 ms
|
||||
// Setup
|
||||
ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found
|
||||
// Make a new object!
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// Set the sent time
|
||||
ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime;
|
||||
ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf;
|
||||
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.timeTransmission).to.equal(obj.timeRoundTrip - (obj.timeSyncSentConfirmation - obj.timeSyncSent));
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
it("should calculate transmission time as a percentage of round trip time when set arrived - sent conf is smaller than threshold",function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
var expectedRoundTripTime = 20; //ms
|
||||
var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS + 1; // 11 ms
|
||||
// Setup
|
||||
ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found
|
||||
// Make a new object!
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// Set the sent time
|
||||
ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime;
|
||||
ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf;
|
||||
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.timeTransmission).to.equal(obj.timeRoundTrip * k.OBCITimeSyncMultiplierWithSyncConf);
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
it("should calculate offset time as a time packet arrived - transmission time - board time",function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
var expectedRoundTripTime = 20; //ms
|
||||
var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms
|
||||
// Set the board time
|
||||
var boardTime = 5000;
|
||||
timeSyncSetPacket.writeInt32BE(boardTime,k.OBCIPacketPositionTimeSyncTimeStart);
|
||||
|
||||
// Setup
|
||||
ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found
|
||||
// Make a new object!
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// Set the sent time
|
||||
ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime;
|
||||
ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf;
|
||||
|
||||
var expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent);
|
||||
|
||||
var expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime;
|
||||
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.timeOffset,"object timeOffset").to.equal(expectedTimeOffset);
|
||||
expect(ourBoard.sync.timeOffsetMaster,"master time offset").to.equal(expectedTimeOffset);
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
it("should calculate offset time as an average of previous offset times",function(done) {
|
||||
var timeSetPacketArrived = ourBoard.time();
|
||||
var expectedRoundTripTime = 20; //ms
|
||||
var expectedTimeTillSentConf = expectedRoundTripTime - k.OBCITimeSyncThresholdTransFailureMS - 2; // 8 ms
|
||||
// Set the board time
|
||||
var boardTime = 5000;
|
||||
timeSyncSetPacket.writeInt32BE(boardTime,k.OBCIPacketPositionTimeSyncTimeStart);
|
||||
|
||||
// Setup
|
||||
ourBoard.curParsingMode = k.OBCIParsingNormal; // indicates the sent conf was found
|
||||
// Make a new object!
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// Set the sent time
|
||||
ourBoard.sync.curSyncObj.timeSyncSent = timeSetPacketArrived - expectedRoundTripTime;
|
||||
ourBoard.sync.curSyncObj.timeSyncSentConfirmation = ourBoard.sync.curSyncObj.timeSyncSent + expectedTimeTillSentConf;
|
||||
|
||||
var expectedTransmissionTime = expectedRoundTripTime - (ourBoard.sync.curSyncObj.timeSyncSentConfirmation - ourBoard.sync.curSyncObj.timeSyncSent);
|
||||
|
||||
var expectedTimeOffset = timeSetPacketArrived - expectedTransmissionTime - boardTime;
|
||||
|
||||
var dif1 = 3;
|
||||
ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif1);
|
||||
var dif2 = 1;
|
||||
ourBoard.sync.timeOffsetArray.push(expectedTimeOffset + dif2);
|
||||
|
||||
ourBoard.once('synced',obj => {
|
||||
expect(obj.timeOffset,"object timeOffset").to.equal(expectedTimeOffset);
|
||||
|
||||
var expectedMasterTimeoffset = math.floor((obj.timeOffset + (obj.timeOffset + dif1) + (obj.timeOffset + dif2)) / 3);
|
||||
expect(ourBoard.sync.timeOffsetMaster,"master time offset").to.equal(expectedMasterTimeoffset);
|
||||
done();
|
||||
})
|
||||
ourBoard._processPacketTimeSyncSet(timeSyncSetPacket,timeSetPacketArrived);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#time", function() {
|
||||
it("should use sntp time when sntpTimeSync specified in options", function() {
|
||||
var board = new openBCIBoard.OpenBCIBoard({
|
||||
verbose: true,
|
||||
sntpTimeSync: true
|
||||
});
|
||||
var funcSpySntpNow = sinon.spy(board,"_sntpNow");
|
||||
|
||||
board.time();
|
||||
|
||||
funcSpySntpNow.should.have.been.calledOnce;
|
||||
|
||||
funcSpySntpNow.reset();
|
||||
|
||||
funcSpySntpNow = null;
|
||||
});
|
||||
it("should use Date.now() for time when sntpTimeSync is not specified in options", function() {
|
||||
var board = new openBCIBoard.OpenBCIBoard({
|
||||
verbose: true
|
||||
});
|
||||
var funcSpySntpNow = sinon.spy(board,"_sntpNow");
|
||||
|
||||
board.time();
|
||||
|
||||
funcSpySntpNow.should.not.have.been.called;
|
||||
|
||||
funcSpySntpNow.reset();
|
||||
|
||||
funcSpySntpNow = null;
|
||||
});
|
||||
it("should emit sntpTimeLock event after sycned with ntp server", function(done) {
|
||||
var board = new openBCIBoard.OpenBCIBoard({
|
||||
verbose: true,
|
||||
sntpTimeSync: true
|
||||
});
|
||||
|
||||
board.once('sntpTimeLock', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
describe("#sntpStart",function() {
|
||||
var board;
|
||||
before(() => {
|
||||
board = new openBCIBoard.OpenBCIBoard();
|
||||
board.sntpStop();
|
||||
});
|
||||
after(() => {
|
||||
board.sntpStop();
|
||||
})
|
||||
it("should be able to start ntp server", done => {
|
||||
expect(board.sntp.isLive()).to.be.false;
|
||||
board.sntpStart()
|
||||
.then(() => {
|
||||
expect(board.sntp.isLive()).to.be.true;
|
||||
})
|
||||
board.once("sntpTimeLock", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#sntpStop",function() {
|
||||
var board;
|
||||
before(done => {
|
||||
board = new openBCIBoard.OpenBCIBoard({
|
||||
sntpTimeSync: true
|
||||
});
|
||||
board.once("sntpTimeLock", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
after(() => {
|
||||
board.sntpStop();
|
||||
})
|
||||
it("should be able to stop the ntp server and set the globals correctly",function() {
|
||||
// Verify the before condition is correct
|
||||
expect(board.options.sntpTimeSync).to.be.true;
|
||||
expect(board.sync.sntpActive).to.be.true;
|
||||
expect(board.sntp.isLive()).to.be.true;
|
||||
|
||||
// Call the function under test
|
||||
board.sntpStop();
|
||||
|
||||
// Ensure the globals were set off
|
||||
expect(board.options.sntpTimeSync).to.be.false;
|
||||
expect(board.sync.sntpActive).to.be.false;
|
||||
expect(board.sntp.isLive()).to.be.false;
|
||||
|
||||
});
|
||||
});
|
||||
describe("#_processParseBufferForReset",function() {
|
||||
var ourBoard;
|
||||
|
||||
@@ -1062,6 +1374,9 @@ describe('openbci-sdk',function() {
|
||||
verbose: true
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
})
|
||||
afterEach(() => {
|
||||
ourBoard.buffer = null;
|
||||
});
|
||||
@@ -1112,6 +1427,7 @@ describe('openbci-sdk',function() {
|
||||
});
|
||||
beforeEach(() => {
|
||||
ourBoard.curParsingMode = k.OBCIParsingTimeSyncSent;
|
||||
ourBoard.sync.curSyncObj = openBCISample.newSyncObject();
|
||||
// spy.reset();
|
||||
});
|
||||
it("should call to find the time sync set character in the buffer", function(done) {
|
||||
@@ -1174,8 +1490,6 @@ describe('openbci-sdk',function() {
|
||||
|
||||
var sampleCounter = 0;
|
||||
|
||||
ourBoard.sync.timeSent = 0;
|
||||
|
||||
var newSample = sample => {
|
||||
// console.log(`sample ${JSON.stringify(sample)}`);
|
||||
if (sampleCounter == 0) {
|
||||
@@ -1185,8 +1499,7 @@ describe('openbci-sdk',function() {
|
||||
// bufferEqual(buf1, buffer).should.be.true;
|
||||
// ourBoard.buffer.length.should.equal(buf1.length);
|
||||
ourBoard.removeListener("sample", newSample);
|
||||
ourBoard.curParsingMode.should.equal(k.OBCIParsingNormal);
|
||||
ourBoard.sync.timeGotPacketSent.should.be.greaterThan(0);
|
||||
expect(ourBoard.curParsingMode).to.equal(k.OBCIParsingNormal);
|
||||
done();
|
||||
}
|
||||
sampleCounter++;
|
||||
@@ -2247,7 +2560,7 @@ describe('openbci-sdk',function() {
|
||||
});
|
||||
|
||||
describe('#radioTestsWithBoard',function() {
|
||||
this.timeout(3000);
|
||||
this.timeout(0);
|
||||
before(function(done) {
|
||||
ourBoard = new openBCIBoard.OpenBCIBoard({
|
||||
verbose: true
|
||||
@@ -2300,11 +2613,11 @@ describe('openbci-sdk',function() {
|
||||
done();
|
||||
}).catch(err => done(err));
|
||||
});
|
||||
it("should be able to set the channel to 0", function(done) {
|
||||
it("should be able to set the channel to 5", function(done) {
|
||||
// Don't test if not using real board
|
||||
if (!realBoard) return done();
|
||||
ourBoard.radioChannelSet(0).then(channelNumber => {
|
||||
expect(channelNumber).to.equal(0);
|
||||
ourBoard.radioChannelSet(5).then(channelNumber => {
|
||||
expect(channelNumber).to.equal(5);
|
||||
done();
|
||||
}).catch(err => done(err));
|
||||
});
|
||||
@@ -2313,11 +2626,13 @@ describe('openbci-sdk',function() {
|
||||
if (!realBoard) return done();
|
||||
var systemChanNumber = 0;
|
||||
var newChanNum = 0;
|
||||
// var timey = Date.now();
|
||||
// Get the current system channel
|
||||
ourBoard.radioChannelGet()
|
||||
.then(res => {
|
||||
// Store it
|
||||
systemChanNumber = res.channelNumber;
|
||||
// console.log(`system channel number: ${res.channelNumber}`);
|
||||
if (systemChanNumber == 25) {
|
||||
newChanNum = 24;
|
||||
} else {
|
||||
@@ -2328,9 +2643,24 @@ describe('openbci-sdk',function() {
|
||||
})
|
||||
.then(newChanNumActual => {
|
||||
expect(newChanNumActual).to.equal(newChanNum);
|
||||
return ourBoard.radioSystemStatusGet();
|
||||
// timey = Date.now();
|
||||
// console.log(`new chan ${newChanNumActual} got`, timey, '0ms');
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(function() {
|
||||
// console.log(`get status`, Date.now(), `${Date.now() - timey}ms`);
|
||||
ourBoard.radioSystemStatusGet().
|
||||
then(isUp => {
|
||||
// console.log('resolving', Date.now(), `${Date.now() - timey}ms`);
|
||||
resolve(isUp);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
})
|
||||
}, 270); // Should be accurate after 270 seconds
|
||||
});
|
||||
})
|
||||
.then(isUp => {
|
||||
// console.log(`isUp test`, Date.now(), `${Date.now() - timey}ms`);
|
||||
expect(isUp).to.be.false;
|
||||
return ourBoard.radioChannelSetHostOverride(systemChanNumber); // Set back to good
|
||||
})
|
||||
@@ -2382,10 +2712,13 @@ describe('openbci-sdk',function() {
|
||||
|
||||
describe('#hardwareValidation', function() {
|
||||
this.timeout(20000); // long timeout for pleanty of stream time :)
|
||||
var runHardwareValidation = masterPortName !== k.OBCISimulatorPortName;
|
||||
var runHardwareValidation = true;
|
||||
var wstream;
|
||||
var board;
|
||||
before(function(done) {
|
||||
if (masterPortName === k.OBCISimulatorPortName) {
|
||||
runHardwareValidation = false;
|
||||
}
|
||||
if (runHardwareValidation) {
|
||||
board = new openBCIBoard.OpenBCIBoard({
|
||||
verbose:true
|
||||
@@ -2546,14 +2879,12 @@ describe('#daisy', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Need a better test
|
||||
describe('#sync', function() {
|
||||
var ourBoard;
|
||||
this.timeout(4000);
|
||||
before(function (done) {
|
||||
ourBoard = new openBCIBoard.OpenBCIBoard({
|
||||
verbose:true,
|
||||
// timeSync: true,
|
||||
simulatorFirmwareVersion: 'v2'
|
||||
});
|
||||
|
||||
@@ -2587,7 +2918,11 @@ describe('#sync', function() {
|
||||
|
||||
|
||||
ourBoard.once('ready', () => {
|
||||
done();
|
||||
ourBoard.streamStart()
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => done);
|
||||
});
|
||||
});
|
||||
after(function(done) {
|
||||
@@ -2603,72 +2938,46 @@ describe('#sync', function() {
|
||||
this.buffer = null;
|
||||
});
|
||||
describe('#syncClocks', function() {
|
||||
// it('can get time sync set packet', done => {
|
||||
// ourBoard.syncClocks()
|
||||
// .catch(err => {
|
||||
// done(err);
|
||||
// });
|
||||
// ourBoard.once('synced',() => {
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// it('can sync multiple times and compute average', done => {
|
||||
// var trials = 5;
|
||||
// var trialCount = 0;
|
||||
//
|
||||
// // Clear the array for sure
|
||||
// ourBoard.sync.timeOffsetArray = [];
|
||||
//
|
||||
// // The function executed on each synced event emitted
|
||||
// var synced = syncObj => {
|
||||
// trialCount++;
|
||||
// if (trialCount >= trials) {
|
||||
// // Verify the length of the
|
||||
// expect(syncObj.timeOffsetArray.length).to.equal(trials);
|
||||
// // Verify the mean is correct
|
||||
// expect(math.mean(syncObj.timeOffsetArray)).to.equal(syncObj.timeOffsetAvg);
|
||||
// // Remove the event listener
|
||||
// ourBoard.removeListener('synced',synced);
|
||||
// done();
|
||||
// } else {
|
||||
// ourBoard.syncClocks()
|
||||
// .catch(err => {
|
||||
// done(err);
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// // Attached the emitted
|
||||
// ourBoard.on('synced',synced);
|
||||
//
|
||||
// // Call the first one
|
||||
// ourBoard.syncClocks()
|
||||
// .catch(err => {
|
||||
// done(err);
|
||||
// });
|
||||
// });
|
||||
it('can sync while streaming', done => {
|
||||
var syncAfterSamples = 50;
|
||||
|
||||
var notSynced = true;
|
||||
var samp = sample => {
|
||||
if (sample.sampleNumber >= syncAfterSamples) {
|
||||
if (sample.sampleNumber >= syncAfterSamples && notSynced) {
|
||||
notSynced = false;
|
||||
// Call the first one
|
||||
ourBoard.syncClocks().catch(err => done);
|
||||
}
|
||||
};
|
||||
ourBoard.on('sample',samp);
|
||||
ourBoard.streamStart().catch(err => done);
|
||||
// Attached the emitted
|
||||
ourBoard.once('synced',() => {
|
||||
ourBoard.disconnect()
|
||||
.then(() => {
|
||||
done();
|
||||
});
|
||||
ourBoard.once('synced',obj => {
|
||||
console.log('syhnc obj', obj);
|
||||
ourBoard.removeListener('sample',samp);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#syncClocksFull', function() {
|
||||
it('can run a full clock sync', done => {
|
||||
var notSynced = true;
|
||||
var sampleFun = sample => {
|
||||
console.log('sample',sample);
|
||||
|
||||
|
||||
if (notSynced) {
|
||||
notSynced = false;
|
||||
// Call the first one
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
console.log(syncObj);
|
||||
if (syncObj.valid) {
|
||||
done();
|
||||
} else {
|
||||
done("Not able to sync");
|
||||
}
|
||||
}).catch(err => done);
|
||||
}
|
||||
};
|
||||
ourBoard.on('sample',sampleFun);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -638,21 +638,26 @@ describe('openBCISimulator',function() {
|
||||
it("should emit a time sync set packet followed by a time synced accel packet after sync up call", function(done) {
|
||||
simulator.synced = false;
|
||||
var emitCounter = 0;
|
||||
var maxPacketsBetweenSetPacket = 5;
|
||||
var newData = data => {
|
||||
if (emitCounter === 0) { // the time sync packet is emitted here
|
||||
// Make a call to start streaming
|
||||
simulator.write(k.OBCIStreamStart, err => {
|
||||
if (err) done(err);
|
||||
});
|
||||
} else if (emitCounter === 1) {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketAccelTimeSyncSet);
|
||||
} else if (emitCounter === 2) {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketAccelTimeSynced);
|
||||
simulator.write(k.OBCIStreamStop, err => {
|
||||
if (err) done(err);
|
||||
} else if (emitCounter < maxPacketsBetweenSetPacket) {
|
||||
if (openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketAccelTimeSyncSet) {
|
||||
simulator.removeListener('data', newData);
|
||||
done();
|
||||
});
|
||||
simulator.write(k.OBCIStreamStop, err => {
|
||||
if (err) done(err);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketAccelTimeSynced);
|
||||
|
||||
}
|
||||
} else {
|
||||
done("Failed to get set packet in time")
|
||||
}
|
||||
emitCounter++;
|
||||
};
|
||||
@@ -669,22 +674,27 @@ describe('openBCISimulator',function() {
|
||||
simulator.synced = false;
|
||||
simulator.options.accel = false;
|
||||
var emitCounter = 0;
|
||||
var maxPacketsBetweenSetPacket = 5;
|
||||
var newData = data => {
|
||||
if (emitCounter === 0) { // the time sync packet is emitted here
|
||||
// Make a call to start streaming
|
||||
simulator.write(k.OBCIStreamStart, err => {
|
||||
if (err) done(err);
|
||||
});
|
||||
} else if (emitCounter === 1) {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketRawAuxTimeSyncSet);
|
||||
} else if (emitCounter === 2) {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketRawAuxTimeSynced);
|
||||
simulator.write(k.OBCIStreamStop, err => {
|
||||
if (err) done(err);
|
||||
} else if (emitCounter < maxPacketsBetweenSetPacket) {
|
||||
if (openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketRawAuxTimeSyncSet) {
|
||||
simulator.removeListener('data', newData);
|
||||
done();
|
||||
});
|
||||
simulator.write(k.OBCIStreamStop, err => {
|
||||
if (err) done(err);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
expect(openBCISample.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketRawAuxTimeSynced);
|
||||
}
|
||||
} else {
|
||||
done("Failed to get set packet in time")
|
||||
}
|
||||
|
||||
emitCounter++;
|
||||
};
|
||||
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário