Comparar commits
12 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| a552248f21 | |||
| 5013112b36 | |||
| b65694575b | |||
| e47128cc4a | |||
| 1a6029770f | |||
| a404703d2e | |||
| f618dcd038 | |||
| 0f4cb8baf0 | |||
| 1ad709659b | |||
| b871bd159d | |||
| 9d778b64f8 | |||
| 95d6529e3a |
@@ -20,4 +20,5 @@ node_js:
|
||||
install:
|
||||
- npm install --all
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test-cov
|
||||
|
||||
+16
-37
@@ -39,6 +39,19 @@ Python researcher or developer? Check out how easy it is to [get access to the e
|
||||
```
|
||||
npm install openbci
|
||||
```
|
||||
#### serialport dependency
|
||||
If you encounter this error when trying to run:
|
||||
```
|
||||
Error: The module '/path/to/your/project/node_modules/serialport/build/Release/serialport.node'
|
||||
was compiled against a different Node.js version using
|
||||
NODE_MODULE_VERSION 48. This version of Node.js requires
|
||||
NODE_MODULE_VERSION 51. Please try re-compiling or re-installing
|
||||
the module (for instance, using `npm rebuild` or`npm install`).
|
||||
```
|
||||
...the issue can be resolved by running:
|
||||
```
|
||||
npm rebuild --build-from-source
|
||||
```
|
||||
### <a name="tldr"></a> TL;DR:
|
||||
Get connected and [start streaming right now with the example code](examples/getStreaming/getStreaming.js).
|
||||
|
||||
@@ -1172,46 +1185,12 @@ The name of the simulator port.
|
||||
|
||||
### <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).
|
||||
[LabStreamingLayer](https://github.com/sccn/labstreaminglayer) is a tool for streaming or recording time-series data. It can be used to interface with [Matlab](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Matlab), [Python](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Python), [Unity](https://github.com/xfleckx/LSL4Unity), and many other programs.
|
||||
|
||||
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.
|
||||
To use LSL with the NodeJS SDK, go to our [labstreaminglayer example](https://github.com/OpenBCI/OpenBCI_NodeJS/tree/master/examples/labstreaminglayer), which contains code that is ready to start an LSL stream of OpenBCI data.
|
||||
|
||||
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:
|
||||
Follow the directions in the [readme](https://github.com/OpenBCI/OpenBCI_NodeJS/blob/master/examples/labstreaminglayer/readme.md) to get started.
|
||||
|
||||
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:
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
# 1.5.1
|
||||
|
||||
### New Features
|
||||
* Add new example for Lab stream layer (#139) thanks @gabrielibagon
|
||||
|
||||
### Breaking changes
|
||||
* Removed `impedanceCalculationForChannel()` and `impedanceCalculationForAllChannels` from `OpenBCISample.js`
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes #28- Impedance not working properly.
|
||||
|
||||
# 1.5.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -45,7 +45,6 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
*/
|
||||
var readyFunc = () => {
|
||||
// Get the sample rate after 'ready'
|
||||
sampleRate = ourBoard.sampleRate();
|
||||
if (stream) {
|
||||
ourBoard.streamStart()
|
||||
.catch(err => {
|
||||
@@ -65,10 +64,10 @@ var sampleFunc = sample => {
|
||||
ourBoard.on('ready', readyFunc);
|
||||
ourBoard.on('sample', sampleFunc);
|
||||
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
ourBoard.removeAllListeners();
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
@@ -81,14 +80,14 @@ function exitHandler (options, err) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,4 +104,4 @@ process.on('SIGINT', exitHandler.bind(null, {
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.1"
|
||||
"openbci": "^1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
*/
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(() => {
|
||||
ourBoard.on('ready',() => {
|
||||
ourBoard.on('ready', () => {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample',(sample) => {
|
||||
ourBoard.on('sample', (sample) => {
|
||||
/** Work with sample */
|
||||
for (var i = 0; i < ourBoard.numberOfChannels(); i++) {
|
||||
console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts.");
|
||||
for (let i = 0; i < ourBoard.numberOfChannels(); i++) {
|
||||
console.log(`Channel ${(i + 1)}: ${sample.channelData[i].toFixed(8)} Volts.`);
|
||||
// prints to the console
|
||||
// "Channel 1: 0.00001987 Volts."
|
||||
// "Channel 2: 0.00002255 Volts."
|
||||
@@ -51,6 +51,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
ourBoard.removeAllListeners();
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
@@ -60,14 +61,14 @@ function exitHandler (options, err) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,4 +85,4 @@ process.on('SIGINT', exitHandler.bind(null, {
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.1"
|
||||
"openbci": "^1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
*/
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(() => {
|
||||
ourBoard.once('ready',() => {
|
||||
ourBoard.once('ready', () => {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample',(sample) => {
|
||||
ourBoard.on('sample', (sample) => {
|
||||
/** Work with sample */
|
||||
for (var i = 0; i < ourBoard.numberOfChannels(); i++) {
|
||||
console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts.");
|
||||
for (let i = 0; i < ourBoard.numberOfChannels(); i++) {
|
||||
console.log(`Channel ${(i + 1)}: ${sample.channelData[i].toFixed(8)} Volts.`);
|
||||
// prints to the console
|
||||
// "Channel 1: 0.00001987 Volts."
|
||||
// "Channel 2: 0.00002255 Volts."
|
||||
@@ -57,6 +57,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
ourBoard.removeAllListeners();
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
@@ -66,14 +67,14 @@ function exitHandler (options, err) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,4 +91,4 @@ process.on('SIGINT', exitHandler.bind(null, {
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* This is an example from the readme.md
|
||||
* On windows you should run with PowerShell not git bash.
|
||||
* Install
|
||||
* [nodejs](https://nodejs.org/en/)
|
||||
*
|
||||
* To run:
|
||||
* change directory to this file `cd examples/debug`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
var debug = false; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
var k = require('openbci').OpenBCIConstants;
|
||||
|
||||
let startedImpedance = false;
|
||||
let iBuffer = [];
|
||||
let count = 0;
|
||||
const window = 50;
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* Only works if one board is plugged in
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(() => {
|
||||
ourBoard.on('ready', () => {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample', (sample) => {
|
||||
if (startedImpedance === false) {
|
||||
startedImpedance = true;
|
||||
k.getImpedanceSetter(1, false, true)
|
||||
.then((commands) => {
|
||||
return ourBoard.write(commands);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('wrote commands to board');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('errr', err);
|
||||
});
|
||||
}
|
||||
/** Work with sample */
|
||||
const chan1ValInVolts = sample.channelData[0];
|
||||
|
||||
// const impedance = chan1ValInVolts / k.OBCILeadOffDriveInAmps;
|
||||
|
||||
// console.log(`impedance:\t${impedance} kOhms`);
|
||||
iBuffer.push(chan1ValInVolts);
|
||||
count++;
|
||||
if (count >= window) {
|
||||
let max = 0.0; // sumSquared
|
||||
for (let i = 0; i < window; i++) {
|
||||
if (iBuffer[i] > max) {
|
||||
max = iBuffer[i];
|
||||
}
|
||||
// sumSquared += iBuffer[i] * iBuffer[i];
|
||||
}
|
||||
let min = 0.0;
|
||||
for (let i = 0; i < window; i++) {
|
||||
if (iBuffer[i] < min) {
|
||||
min = iBuffer[i];
|
||||
}
|
||||
// sumSquared += iBuffer[i] * iBuffer[i];
|
||||
}
|
||||
const vP2P = max - min; // peak to peak
|
||||
|
||||
console.log(`impedance: \t${vP2P / 2 / k.OBCILeadOffDriveInAmps}`);
|
||||
// console.log(`impedance: ${vRms/k.OBCILeadOffDriveInAmps}`);
|
||||
|
||||
// const mean_squared = sumSquared / window;
|
||||
// const root_mean_squared = Math.sqrt(mean_squared);
|
||||
// console.log(`impedance: ${root_mean_squared/k.OBCILeadOffDriveInAmps}`);
|
||||
|
||||
count = 0;
|
||||
iBuffer = [];
|
||||
}
|
||||
// console.log(`uV:\t${chan1ValInVolts/(10*6)}\nimpedance:\t${impedance}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
/** Do additional clean up here */
|
||||
ourBoard.disconnect().catch(console.log);
|
||||
ourBoard.removeAllListeners();
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
if (options.exit) {
|
||||
if (verbose) console.log('exit');
|
||||
ourBoard.streamStop()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, {
|
||||
cleanup: true
|
||||
}));
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "get-streaming",
|
||||
"version": "1.0.0",
|
||||
"description": "Get impedance example",
|
||||
"main": "getStreaming.js",
|
||||
"scripts": {
|
||||
"start": "node impedance.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"get"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import json
|
||||
import sys
|
||||
import numpy as np
|
||||
import time
|
||||
import zmq
|
||||
from pylsl import StreamInfo, StreamOutlet
|
||||
|
||||
class Interface:
|
||||
def __init__(self, verbose=False):
|
||||
context = zmq.Context()
|
||||
self._socket = context.socket(zmq.PAIR)
|
||||
self._socket.connect("tcp://localhost:3004")
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
if self.verbose:
|
||||
print("Client Ready!")
|
||||
|
||||
# Send a quick message to tell node process we are up and running
|
||||
self.send(json.dumps({
|
||||
'action': 'started',
|
||||
'command': 'status',
|
||||
'message': time.time()*1000.0
|
||||
}))
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Sends a message to TCP server
|
||||
:param msg: str
|
||||
A string to send to node TCP server, could be a JSON dumps...
|
||||
:return: None
|
||||
"""
|
||||
if self.verbose:
|
||||
print('<- out ' + msg)
|
||||
self._socket.send_string(msg)
|
||||
return
|
||||
|
||||
def recv(self):
|
||||
"""
|
||||
Checks the ZeroMQ for data
|
||||
:return: str
|
||||
String of data
|
||||
"""
|
||||
return self._socket.recv()
|
||||
|
||||
|
||||
def initializeOutlet(interface):
|
||||
"""
|
||||
Initializes and returns an LSL outlet
|
||||
:param interface: Interface
|
||||
the Python interface to communicate with node
|
||||
:return: StreamOutlet
|
||||
returns a labstreaminglayer StreamOutlet
|
||||
"""
|
||||
numChans = None
|
||||
while not numChans:
|
||||
msg = interface.recv()
|
||||
try:
|
||||
dicty = json.loads(msg)
|
||||
numChans = dicty.get('numChans')
|
||||
sampleRate = dicty.get('sampleRate')
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
info = StreamInfo('OpenBCI_EEG', 'EEG', numChans, sampleRate, 'float32', 'myuid34234')
|
||||
outlet = StreamOutlet(info)
|
||||
print('init')
|
||||
return outlet
|
||||
|
||||
def main(argv):
|
||||
verbose = False
|
||||
# Create a new python interface.
|
||||
interface = Interface(verbose)
|
||||
# Create a new labstreaminglayer outlet
|
||||
outlet = initializeOutlet(interface);
|
||||
|
||||
# Stream incoming data to LSL
|
||||
while True:
|
||||
msg = interface.recv()
|
||||
try:
|
||||
dicty = json.loads(msg)
|
||||
message = dicty.get('message')
|
||||
data=message.get('channelData')
|
||||
timeStamp = message.get('timeStamp')
|
||||
outlet.push_sample(data,timeStamp)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* This is an example from the readme.md
|
||||
* On windows you should run with PowerShell not git bash.
|
||||
* Install
|
||||
* [nodejs](https://nodejs.org/en/)
|
||||
*
|
||||
* To run:
|
||||
* change directory to this file `cd examples/debug`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var portPub = 'tcp://127.0.0.1:3004';
|
||||
var zmq = require('zmq-prebuilt');
|
||||
var socket = zmq.socket('pair');
|
||||
var debug = false; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
simulatorFirmwareVersion: 'v2',
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
|
||||
var timeSyncPossible = false;
|
||||
var resyncPeriodMin = 1;
|
||||
var secondsInMinute = 60;
|
||||
var numChans = 8;
|
||||
var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute;
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
// Call to connect
|
||||
ourBoard.connect(portName)
|
||||
.then(() => {
|
||||
ourBoard.on('ready', () => {
|
||||
// Get the sample rate after 'ready'
|
||||
numChans = ourBoard.numberOfChannels();
|
||||
if (numChans === 16) {
|
||||
ourBoard.overrideInfoForBoardType('daisy');
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
sendToPython({'numChans': numChans, 'sampleRate': ourBoard.sampleRate()});
|
||||
if (timeSyncPossible) {
|
||||
ourBoard.streamStart()
|
||||
.catch(err => {
|
||||
console.log(`stream start: ${err}`);
|
||||
});
|
||||
} else {
|
||||
console.log('not able to time sync');
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect: ${err}`);
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
var sampleFunc = sample => {
|
||||
if (sample._count % resyncPeriod === 0) {
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
// Sync was successful
|
||||
if (syncObj.valid) {
|
||||
// Log the object to check it out!
|
||||
console.log(`timeOffset`, syncObj.timeOffsetMaster);
|
||||
} else {
|
||||
// Retry it
|
||||
console.log(`Was not able to sync... retry!`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sample.timeStamp) { // true after the first successful sync
|
||||
if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours
|
||||
console.log(`Bad time sync ${sample.timeStamp}`);
|
||||
} else {
|
||||
sendToPython({
|
||||
action: 'process',
|
||||
command: 'sample',
|
||||
message: sample
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to your functions
|
||||
ourBoard.on('sample', sampleFunc);
|
||||
|
||||
// ZMQ
|
||||
socket.bind(portPub, function (err) {
|
||||
if (err) throw err;
|
||||
console.log(`bound to ${portPub}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Used to send a message to the Python process.
|
||||
* @param {Object} interProcessObject The standard inter-process object.
|
||||
* @return {None}
|
||||
*/
|
||||
var sendToPython = (interProcessObject, verbose) => {
|
||||
if (verbose) {
|
||||
console.log(`<- out ${JSON.stringify(interProcessObject)}`);
|
||||
}
|
||||
if (socket) {
|
||||
socket.send(JSON.stringify(interProcessObject));
|
||||
}
|
||||
};
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
if (options.exit) {
|
||||
if (verbose) console.log('exit');
|
||||
ourBoard.disconnect().catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, {
|
||||
cleanup: true
|
||||
}));
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "labstreaminglayer",
|
||||
"version": "1.0.0",
|
||||
"description": "labstreaminglayer example",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"",
|
||||
"start-node": "node index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"python",
|
||||
"openbci",
|
||||
"node",
|
||||
"labstreaminglayer",
|
||||
"LSL"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.2",
|
||||
"zmq-prebuilt": "^2.1.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "<=6.x",
|
||||
"npm": ">=3.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
# OpenBCI Node SDK to Lab Streaming Layer
|
||||
|
||||
## About
|
||||
|
||||
This code provides an example of how to stream OpenBCI data through the [lab streaming layer](https://github.com/sccn/labstreaminglayer) using the NodeJS SDK.
|
||||
|
||||
Follow the steps in this README to start streaming. The code is ready to run as-is, but can be modified and extended to customize how you are sending your data. This is designed to be used with the **OpenBCI Cyton** (for **Ganglion support**, see the [Ganglion Node SDK](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion/tree/master/examples/labstreaminglayer)).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* [Python](https://www.python.org/downloads/)
|
||||
* [ZeroMQ](http://zeromq.org/bindings:python)
|
||||
|
||||
```py
|
||||
pip install pyzmq
|
||||
```
|
||||
* [Node.js LTS](https://nodejs.org/en/)
|
||||
* [Lab Streaming Layer](https://github.com/sccn/labstreaminglayer)
|
||||
|
||||
```py
|
||||
pip install pylsl
|
||||
```
|
||||
|
||||
|
||||
## Installation
|
||||
First, install Python dependencies:
|
||||
```bash
|
||||
python setup.py install
|
||||
```
|
||||
Next, install NodeJS dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
Note: depending on your computer settings, you may need to run as administrator or with `sudo`.
|
||||
|
||||
## Running
|
||||
```
|
||||
npm start
|
||||
```
|
||||
For running just the node, for example if you were running the python in a separate ide and debugging, it's useful.
|
||||
```python
|
||||
npm run start-node
|
||||
```
|
||||
Note: depending on your computer settings, you may need to run as administrator or with `sudo`.
|
||||
|
||||
## Writing Lab Streaming Layer Code
|
||||
If you would like to use lab streaming layer in a custom OpenBCI NodeJS application, you must include an instance of the OpenBCI NodeJS library and an instance of a Python interface. A basic example is shown below:
|
||||
|
||||
index.js
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var portPub = 'tcp://127.0.0.1:3004';
|
||||
var zmq = require('zmq-prebuilt');
|
||||
var socket = zmq.socket('pair');
|
||||
var ourBoard = new OpenBCIBoard();
|
||||
|
||||
socket.bind(portPub);
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(() => {
|
||||
ourBoard.on('ready',() => {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample',(sample) => {
|
||||
if (socket) {
|
||||
socket.send(JSON.stringify({message: sample}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
/* Insert additional exit handlers and cleanup below*/
|
||||
```
|
||||
|
||||
handoff.py
|
||||
```python
|
||||
import json
|
||||
import zmq
|
||||
from pylsl import StreamInfo, StreamOutlet
|
||||
|
||||
# Create ZMQ socket
|
||||
context = zmq.Context()
|
||||
_socket = context.socket(zmq.PAIR)
|
||||
_socket.connect("tcp://localhost:3004")
|
||||
|
||||
# Create a new labstreaminglayer outlet
|
||||
numChans = 8;
|
||||
sampleRate = 250;
|
||||
info = StreamInfo('OpenBCI_EEG', 'EEG', numChans, sampleRate, 'float32', 'openbci_12345')
|
||||
outlet = StreamOutlet(info)
|
||||
# Stream incoming data to LSL
|
||||
while True:
|
||||
msg = _socket.recv()
|
||||
try:
|
||||
dicty = json.loads(msg)
|
||||
message = dicty.get('message')
|
||||
data=message.get('channelData')
|
||||
timeStamp = message.get('timeStamp')
|
||||
outlet.push_sample(data,timeStamp)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
Please PR if you have code to contribute!
|
||||
@@ -0,0 +1,12 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='openbci-node-labstreaminglayer',
|
||||
version='0.0.1',
|
||||
description='Labstreaminglayer with NodeJS',
|
||||
url='',
|
||||
author='AJ Keller',
|
||||
author_email='pushtheworldllc@gmail.com',
|
||||
license='MIT',
|
||||
packages=find_packages(),
|
||||
install_requires=['numpy', 'pyzmq','pylsl'],
|
||||
zip_safe=False)
|
||||
+13
-17
@@ -10,21 +10,20 @@
|
||||
* then `npm start`
|
||||
*/
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var port_pub = 'tcp://127.0.0.1:3004';
|
||||
var portPub = 'tcp://127.0.0.1:3004';
|
||||
var zmq = require('zmq-prebuilt');
|
||||
var socket = zmq.socket('pair');
|
||||
var simulate = true; // Sends synthetic data
|
||||
var simulate = false; // Sends synthetic data
|
||||
var debug = false; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
// simulate: simulate, // Uncomment to see how it works with simulator!
|
||||
simulate: simulate, // Uncomment to see how it works with simulator!
|
||||
simulatorFirmwareVersion: 'v2',
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
|
||||
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
|
||||
var timeSyncPossible = false;
|
||||
var resyncPeriodMin = 1;
|
||||
var secondsInMinute = 60;
|
||||
@@ -40,9 +39,6 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
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();
|
||||
|
||||
@@ -54,7 +50,7 @@ ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
} else {
|
||||
console.log('not able to time sync');
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect: ${err}`);
|
||||
@@ -98,9 +94,9 @@ ourBoard.on('sample', sampleFunc);
|
||||
|
||||
// ZMQ fun
|
||||
|
||||
socket.bind(port_pub, function (err) {
|
||||
socket.bind(portPub, function (err) {
|
||||
if (err) throw err;
|
||||
console.log(`bound to ${port_pub}`);
|
||||
console.log(`bound to ${portPub}`);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -117,9 +113,9 @@ var sendToPython = (interProcessObject, verbose) => {
|
||||
}
|
||||
};
|
||||
|
||||
var receiveFromPython = (raw_data) => {
|
||||
var receiveFromPython = (rawData) => {
|
||||
try {
|
||||
let body = JSON.parse(raw_data); // five because `resp `
|
||||
let body = JSON.parse(rawData); // five because `resp `
|
||||
processInterfaceObject(body);
|
||||
} catch (err) {
|
||||
console.log('in -> ' + 'bad json');
|
||||
@@ -185,14 +181,14 @@ function exitHandler (options, err) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,4 +205,4 @@ process.on('SIGINT', exitHandler.bind(null, {
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
|
||||
var ourBoard = new OpenBCIBoard({});
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
|
||||
var timeSyncPossible = false;
|
||||
@@ -100,14 +101,14 @@ function exitHandler (options, err) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
if (process.platform === 'win32') {
|
||||
const rl = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
rl.on('SIGINT', function () {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -124,4 +125,4 @@ process.on('SIGINT', exitHandler.bind(null, {
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
}));
|
||||
|
||||
+7
-22
@@ -174,16 +174,7 @@ function OpenBCIFactory () {
|
||||
this.buffer = null;
|
||||
this.masterBuffer = masterBufferMaker();
|
||||
// Objects
|
||||
this.goertzelObject = openBCISample.goertzelNewObject(k.numberOfChannelsForBoardType(this.options.boardType));
|
||||
this.impedanceTest = {
|
||||
active: false,
|
||||
isTestingPInput: false,
|
||||
isTestingNInput: false,
|
||||
onChannel: 0,
|
||||
sampleNumber: 0,
|
||||
continuousMode: false,
|
||||
impedanceForChannel: 0
|
||||
};
|
||||
this.impedanceTest = openBCISample.impedanceTestObjDefault();
|
||||
this.info = {
|
||||
boardType: this.options.boardType,
|
||||
sampleRate: k.OBCISampleRate125,
|
||||
@@ -390,7 +381,6 @@ function OpenBCIFactory () {
|
||||
return this.serial.isOpen();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @description Checks if the board is currently sending samples.
|
||||
* @returns {boolean} - True if streaming.
|
||||
@@ -407,7 +397,6 @@ function OpenBCIFactory () {
|
||||
return this._streaming;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @description Sends a start streaming command to the board.
|
||||
* @returns {Promise} indicating if the signal was able to be sent.
|
||||
@@ -1038,7 +1027,7 @@ function OpenBCIFactory () {
|
||||
* Get the board type.
|
||||
* @return boardType: string
|
||||
*/
|
||||
OpenBCIBoard.prototype.getBoardType = function() {
|
||||
OpenBCIBoard.prototype.getBoardType = function () {
|
||||
return this.info.boardType;
|
||||
};
|
||||
|
||||
@@ -1046,7 +1035,7 @@ function OpenBCIFactory () {
|
||||
* Get the core info object.
|
||||
* @return {{boardType: string, sampleRate: number, firmware: string, numberOfChannels: number, missedPackets: number}}
|
||||
*/
|
||||
OpenBCIBoard.prototype.getInfo = function() {
|
||||
OpenBCIBoard.prototype.getInfo = function () {
|
||||
return this.info;
|
||||
};
|
||||
|
||||
@@ -1055,14 +1044,13 @@ function OpenBCIFactory () {
|
||||
* @param boardType {String}
|
||||
* `default` or `daisy`. Defaults to `default`.
|
||||
*/
|
||||
OpenBCIBoard.prototype.overrideInfoForBoardType = function(boardType) {
|
||||
OpenBCIBoard.prototype.overrideInfoForBoardType = function (boardType) {
|
||||
switch (boardType) {
|
||||
case k.OBCIBoardDaisy:
|
||||
this.info.boardType = k.OBCIBoardDaisy;
|
||||
this.info.numberOfChannels = k.OBCINumberOfChannelsDaisy;
|
||||
this.info.sampleRate = k.OBCISampleRate125;
|
||||
this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDaisy);
|
||||
this.goertzelObject = openBCISample.goertzelNewObject(k.OBCINumberOfChannelsDaisy);
|
||||
this.impedanceArray = openBCISample.impedanceArray(k.OBCINumberOfChannelsDaisy);
|
||||
break;
|
||||
case k.OBCIBoardDefault:
|
||||
@@ -1071,7 +1059,6 @@ function OpenBCIFactory () {
|
||||
this.info.numberOfChannels = k.OBCINumberOfChannelsDefault;
|
||||
this.info.sampleRate = k.OBCISampleRate250;
|
||||
this.channelSettingsArray = k.channelSettingsArrayInit(k.OBCINumberOfChannelsDefault);
|
||||
this.goertzelObject = openBCISample.goertzelNewObject(k.OBCINumberOfChannelsDefault);
|
||||
this.impedanceArray = openBCISample.impedanceArray(k.OBCINumberOfChannelsDefault);
|
||||
break;
|
||||
}
|
||||
@@ -1831,18 +1818,16 @@ function OpenBCIFactory () {
|
||||
this.emit(k.OBCIEmitterHardSet);
|
||||
this.hardSetBoardType(this.options.boardType)
|
||||
.then(() => {
|
||||
this.emit(k.OBCIEmitterReady)
|
||||
this.emit(k.OBCIEmitterReady);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.emit(k.OBCIEmitterError, err);
|
||||
});
|
||||
|
||||
} else {
|
||||
this.curParsingMode = k.OBCIParsingNormal;
|
||||
this.emit(k.OBCIEmitterReady);
|
||||
this.buffer = openBCISample.stripToEOTBuffer(data);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this.getBoardType() !== this.options.boardType && this.options.verbose) {
|
||||
console.log(`Module detected ${this.getBoardType()} board type but you specified ${this.options.boardType}, use 'hardSet' to force the module to correct itself`);
|
||||
@@ -2021,13 +2006,13 @@ function OpenBCIFactory () {
|
||||
if (this.impedanceTest.continuousMode) {
|
||||
// console.log('running in continuous mode...')
|
||||
// openBCISample.debugPrettyPrint(sampleObject)
|
||||
impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject);
|
||||
impedanceArray = openBCISample.impedanceCalculateArray(sampleObject, this.impedanceTest);
|
||||
if (impedanceArray) {
|
||||
this.emit('impedanceArray', impedanceArray);
|
||||
}
|
||||
} else if (this.impedanceTest.onChannel !== 0) {
|
||||
// Only calculate impedance for one channel
|
||||
impedanceArray = openBCISample.goertzelProcessSample(sampleObject, this.goertzelObject);
|
||||
impedanceArray = openBCISample.impedanceCalculateArray(sampleObject, this.impedanceTest);
|
||||
if (impedanceArray) {
|
||||
this.impedanceTest.impedanceForChannel = impedanceArray[this.impedanceTest.onChannel - 1];
|
||||
}
|
||||
|
||||
+41
-122
@@ -13,14 +13,6 @@ const SCALE_FACTOR_ACCEL = 0.002 / Math.pow(2, 4);
|
||||
const ACCEL_NUMBER_AXIS = 3;
|
||||
// Default ADS1299 gains array
|
||||
|
||||
// For computing Goertzel Algorithm
|
||||
// See: http://www.embedded.com/design/configurable-systems/4024443/The-Goertzel-Algorithm
|
||||
// In the tutorial cited above, GOERTZEL_BLOCK_SIZE is referred to as N
|
||||
const GOERTZEL_BLOCK_SIZE = 62;
|
||||
const GOERTZEL_K_250 = Math.floor(0.5 + ((GOERTZEL_BLOCK_SIZE * k.OBCILeadOffFrequencyHz) / k.OBCISampleRate250));
|
||||
const GOERTZEL_W_250 = ((2 * Math.PI) / GOERTZEL_BLOCK_SIZE) * GOERTZEL_K_250;
|
||||
const GOERTZEL_COEFF_250 = 2 * Math.cos(GOERTZEL_W_250);
|
||||
|
||||
var sampleModule = {
|
||||
|
||||
/**
|
||||
@@ -211,59 +203,6 @@ var sampleModule = {
|
||||
},
|
||||
floatTo3ByteBuffer,
|
||||
floatTo2ByteBuffer,
|
||||
/**
|
||||
* @description Calculate the impedance for one channel only.
|
||||
* @param sampleObject - Standard OpenBCI sample object
|
||||
* @param channelNumber - Number, the channel you want to calculate impedance for.
|
||||
* @returns {Promise} - Fulfilled with impedance value for the specified channel.
|
||||
* @author AJ Keller
|
||||
*/
|
||||
impedanceCalculationForChannel: (sampleObject, channelNumber) => {
|
||||
const sqrt2 = Math.sqrt(2);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (sampleObject === undefined || sampleObject === null) reject('Sample Object cannot be null or undefined');
|
||||
if (sampleObject.channelData === undefined || sampleObject.channelData === null) reject('Channel cannot be null or undefined');
|
||||
if (channelNumber < 1 || channelNumber > k.OBCINumberOfChannelsDefault) reject('Channel number invalid.');
|
||||
|
||||
var index = channelNumber - 1;
|
||||
|
||||
if (sampleObject.channelData[index] < 0) {
|
||||
sampleObject.channelData[index] *= -1;
|
||||
}
|
||||
var impedance = (sqrt2 * sampleObject.channelData[index]) / k.OBCILeadOffDriveInAmps;
|
||||
// if (index === 0) console.log("Voltage: " + (sqrt2*sampleObject.channelData[index]) + " leadoff amps: " + k.OBCILeadOffDriveInAmps + " impedance: " + impedance)
|
||||
resolve(impedance);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description Calculate the impedance for all channels.
|
||||
* @param sampleObject - Standard OpenBCI sample object
|
||||
* @returns {Promise} - Fulfilled with impedances for the sample
|
||||
* @author AJ Keller
|
||||
*/
|
||||
impedanceCalculationForAllChannels: sampleObject => {
|
||||
const sqrt2 = Math.sqrt(2);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (sampleObject === undefined || sampleObject === null) reject('Sample Object cannot be null or undefined');
|
||||
if (sampleObject.channelData === undefined || sampleObject.channelData === null) reject('Channel cannot be null or undefined');
|
||||
|
||||
var sampleImpedances = [];
|
||||
var numChannels = sampleObject.channelData.length;
|
||||
for (var index = 0; index < numChannels; index++) {
|
||||
if (sampleObject.channelData[index] < 0) {
|
||||
sampleObject.channelData[index] *= -1;
|
||||
}
|
||||
var impedance = (sqrt2 * sampleObject.channelData[index]) / k.OBCILeadOffDriveInAmps;
|
||||
sampleImpedances.push(impedance);
|
||||
|
||||
// if (index === 0) console.log("Voltage: " + (sqrt2*sampleObject.channelData[index]) + " leadoff amps: " + k.OBCILeadOffDriveInAmps + " impedance: " + impedance)
|
||||
}
|
||||
|
||||
sampleObject.impedances = sampleImpedances;
|
||||
|
||||
resolve(sampleObject);
|
||||
});
|
||||
},
|
||||
interpret16bitAsInt32: twoByteBuffer => {
|
||||
var prefix = 0;
|
||||
|
||||
@@ -418,73 +357,53 @@ var sampleModule = {
|
||||
scaleFactorAux: SCALE_FACTOR_ACCEL,
|
||||
k,
|
||||
/**
|
||||
* @description Use the Goertzel algorithm to calculate impedances
|
||||
* @param sample - a sample with channelData Array
|
||||
* @param goertzelObj - An object that was created by a call to this.goertzelNewObject()
|
||||
* @returns {Array} - Returns an array if finished computing
|
||||
*/
|
||||
goertzelProcessSample: (sample, goertzelObj) => {
|
||||
// calculate the goertzel values for all channels
|
||||
for (var i = 0; i < goertzelObj.numberOfChannels; i++) {
|
||||
var q0 = GOERTZEL_COEFF_250 * goertzelObj.q1[i] - goertzelObj.q2[i] + sample.channelData[i];
|
||||
goertzelObj.q2[i] = goertzelObj.q1[i];
|
||||
goertzelObj.q1[i] = q0;
|
||||
* Calculate the impedance
|
||||
* @param sample {Object} - Standard sample
|
||||
* @param impedanceTest {Object} - Impedance Object from openBCIBoard.js
|
||||
* @return {null | Object} - Null if not enough samples have passed to calculate an accurate
|
||||
*/
|
||||
impedanceCalculateArray: (sample, impedanceTest) => {
|
||||
impedanceTest.buffer.push(sample.channelData);
|
||||
impedanceTest.count++;
|
||||
|
||||
// console.log('Q1: ' + goertzelObj.q1[i] + ' Q2: ' + goertzelObj.q2[i])
|
||||
}
|
||||
if (impedanceTest.count >= impedanceTest.window) {
|
||||
let output = [];
|
||||
for (let i = 0; i < sample.channelData.length; i++) {
|
||||
let max = 0.0; // sumSquared
|
||||
for (let j = 0; j < impedanceTest.window; j++) {
|
||||
if (impedanceTest.buffer[i][j] > max) {
|
||||
max = impedanceTest.buffer[i][j];
|
||||
}
|
||||
}
|
||||
let min = 0.0;
|
||||
for (let j = 0; j < impedanceTest.window; j++) {
|
||||
if (impedanceTest.buffer[i][j] < min) {
|
||||
min = impedanceTest.buffer[i][j];
|
||||
}
|
||||
}
|
||||
const vP2P = max - min; // peak to peak
|
||||
|
||||
// Increment the index counter
|
||||
goertzelObj.index++;
|
||||
|
||||
// Have we iterated more times then block size?
|
||||
if (goertzelObj.index > GOERTZEL_BLOCK_SIZE) {
|
||||
var impedanceArray = [];
|
||||
for (var j = 0; j < goertzelObj.numberOfChannels; j++) {
|
||||
// Calculate the magnitude of the voltage
|
||||
// var q1SQRD = goertzelObj.q1[j] * goertzelObj.q1[j];
|
||||
// var q2SQRD = goertzelObj.q2[j] * goertzelObj.q2[j];
|
||||
// var lastPart = goertzelObj.q1[j] * goertzelObj.q2[j] * GOERTZEL_COEFF_250;
|
||||
|
||||
// console.log('Chan ' + j + ', Q1^2: ' + q1SQRD + ', Q2^2: ' + q2SQRD + ', Last Part: ' + lastPart)
|
||||
|
||||
var voltage = Math.sqrt((goertzelObj.q1[j] * goertzelObj.q1[j]) + (goertzelObj.q2[j] * goertzelObj.q2[j]) - goertzelObj.q1[j] * goertzelObj.q2[j] * GOERTZEL_COEFF_250);
|
||||
|
||||
// Calculate the impedance r = v/i
|
||||
var impedance = voltage / k.OBCILeadOffDriveInAmps;
|
||||
// Push the impedance into the final array
|
||||
impedanceArray.push(impedance);
|
||||
|
||||
// Reset the goertzel variables to get ready for the next iteration
|
||||
goertzelObj.q1[j] = 0;
|
||||
goertzelObj.q2[j] = 0;
|
||||
output.push(vP2P / 2 / k.OBCILeadOffDriveInAmps);
|
||||
}
|
||||
|
||||
// Reset the goertzel index counter
|
||||
goertzelObj.index = 0;
|
||||
|
||||
// Pass out the impedance array
|
||||
return impedanceArray;
|
||||
} else {
|
||||
// This reject is really just for debugging
|
||||
return;
|
||||
impedanceTest.count = 0;
|
||||
return output;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
goertzelNewObject: numberOfChannels => {
|
||||
// Object to help calculate the goertzel
|
||||
var q1 = [];
|
||||
var q2 = [];
|
||||
for (var i = 0; i < numberOfChannels; i++) {
|
||||
q1.push(0);
|
||||
q2.push(0);
|
||||
}
|
||||
return {
|
||||
q1: q1,
|
||||
q2: q2,
|
||||
index: 0,
|
||||
numberOfChannels: numberOfChannels
|
||||
};
|
||||
impedanceTestObjDefault: (impedanceTestObj) => {
|
||||
let newObj = impedanceTestObj || {};
|
||||
newObj['active'] = false;
|
||||
newObj['buffer'] = [];
|
||||
newObj['count'] = 0;
|
||||
newObj['isTestingPInput'] = false;
|
||||
newObj['isTestingNInput'] = false;
|
||||
newObj['onChannel'] = 0;
|
||||
newObj['sampleNumber'] = 0;
|
||||
newObj['continuousMode'] = false;
|
||||
newObj['impedanceForChannel'] = 0;
|
||||
newObj['window'] = 40;
|
||||
return newObj;
|
||||
},
|
||||
GOERTZEL_BLOCK_SIZE,
|
||||
samplePacket: sampleNumber => {
|
||||
return new Buffer([0xA0, sampleNumberNormalize(sampleNumber), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 0, 1, 0, 2, makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)]);
|
||||
},
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "openbci",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"description": "The official Node.js SDK for the OpenBCI Biosensor Board.",
|
||||
"main": "openBCIBoard",
|
||||
"scripts": {
|
||||
"lint": "semistandard | snazzy",
|
||||
"start": "node index",
|
||||
"test": "semistandard | snazzy && mocha test",
|
||||
"test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov"
|
||||
|
||||
@@ -708,19 +708,6 @@ describe('openBCISample', function () {
|
||||
assert(passed, 'a sample with accel data was produced');
|
||||
});
|
||||
});
|
||||
describe('#impedanceCalculationForChannel', function () {
|
||||
it('rejects when undefined sampleObject', function (done) {
|
||||
var bad;
|
||||
openBCISample.impedanceCalculationForChannel(bad, 1).should.be.rejected.and.notify(done);
|
||||
});
|
||||
it('rejects when undefined channel number', function (done) {
|
||||
var bad;
|
||||
openBCISample.impedanceCalculationForChannel('taco', bad).should.be.rejected.and.notify(done);
|
||||
});
|
||||
it('rejects when invalid channel number', function (done) {
|
||||
openBCISample.impedanceCalculationForChannel('taco', 69).should.be.rejected.and.notify(done);
|
||||
});
|
||||
});
|
||||
describe('#impedanceSummarize', function () {
|
||||
var impedanceArray = [];
|
||||
var numberOfChannels = 8;
|
||||
@@ -1270,34 +1257,43 @@ $$$`);
|
||||
expect(openBCISample.stripToEOTBuffer(totalBuf)).to.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#impedanceTestObjDefault', function () {
|
||||
it('should give a new impedance object', function () {
|
||||
const expectedImpedanceObj = {
|
||||
active: false,
|
||||
buffer: [],
|
||||
count: 0,
|
||||
isTestingPInput: false,
|
||||
isTestingNInput: false,
|
||||
onChannel: 0,
|
||||
sampleNumber: 0,
|
||||
continuousMode: false,
|
||||
impedanceForChannel: 0,
|
||||
window: 40
|
||||
};
|
||||
expect(openBCISample.impedanceTestObjDefault()).to.deep.equal(expectedImpedanceObj);
|
||||
});
|
||||
});
|
||||
describe('#impedanceCalculateArray', function () {
|
||||
const numberOfChannels = k.OBCINumberOfChannelsDefault;
|
||||
const newRandomSample = openBCISample.randomSample(numberOfChannels, k.OBCISampleRate250, false, 'none');
|
||||
|
||||
describe('#goertzelProcessSample', function () {
|
||||
var numberOfChannels = k.OBCINumberOfChannelsDefault;
|
||||
var goertzelObj = openBCISample.goertzelNewObject(numberOfChannels);
|
||||
var newRandomSample = openBCISample.randomSample(numberOfChannels, k.OBCISampleRate250);
|
||||
afterEach(() => bluebirdChecks.noPendingPromises());
|
||||
|
||||
afterEach(() => bluebirdChecks.noPendingPromises());
|
||||
|
||||
it('produces an array of impedances', function (done) {
|
||||
var passed = false;
|
||||
for (var i = 0; i < openBCISample.GOERTZEL_BLOCK_SIZE + 1; i++) {
|
||||
// console.log('Iteration ' + i)
|
||||
var impedanceArray = openBCISample.goertzelProcessSample(newRandomSample(i), goertzelObj);
|
||||
if (impedanceArray) {
|
||||
// console.log('Impedance Array: ')
|
||||
for (var j = 0; j < numberOfChannels; j++) {
|
||||
console.log('Channel ' + (j + 1) + ': ' + impedanceArray[j].toFixed(8));
|
||||
}
|
||||
passed = true;
|
||||
it('should not produce an array of impedances till window', function () {
|
||||
const impTestObj = openBCISample.impedanceTestObjDefault();
|
||||
for (let i = 0; i < impTestObj.window - 1; i++) {
|
||||
expect(openBCISample.impedanceCalculateArray(newRandomSample(i), impTestObj)).to.equal(null);
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (passed) {
|
||||
done();
|
||||
} else {
|
||||
done('Failed to produce impedance array within block size + 1');
|
||||
expect(impTestObj.buffer.length).to.equal(impTestObj.window - 1);
|
||||
});
|
||||
it('should produce and array of impedances at window', function () {
|
||||
const impTestObj = openBCISample.impedanceTestObjDefault();
|
||||
let impedanceArray = null;
|
||||
for (let i = 0; i < impTestObj.window; i++) {
|
||||
impedanceArray = openBCISample.impedanceCalculateArray(newRandomSample(i), impTestObj);
|
||||
}
|
||||
expect(impedanceArray.length).to.equal(numberOfChannels);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
var bluebirdChecks = require('./bluebirdChecks');
|
||||
var sinon = require('sinon');
|
||||
var sinon = require('sinon'); // eslint-disable-line no-unused-vars
|
||||
var chai = require('chai');
|
||||
var expect = chai.expect;
|
||||
var should = chai.should(); // eslint-disable-line no-unused-vars
|
||||
@@ -9,28 +9,24 @@ var openBCISample = openBCIBoard.OpenBCISample;
|
||||
var k = openBCISample.k;
|
||||
var chaiAsPromised = require('chai-as-promised');
|
||||
var sinonChai = require('sinon-chai');
|
||||
var sinonAsPromised = require('sinon-as-promised')(bluebirdChecks.BluebirdPromise);
|
||||
var fs = require('fs');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('openbci-radios', function () {
|
||||
this.timeout(2000);
|
||||
var ourBoard, masterPortName, realBoard;
|
||||
var ourBoard, masterPortName;
|
||||
|
||||
before(function (done) {
|
||||
ourBoard = new openBCIBoard.OpenBCIBoard();
|
||||
ourBoard.autoFindOpenBCIBoard()
|
||||
.then(portName => {
|
||||
ourBoard = null;
|
||||
realBoard = true;
|
||||
masterPortName = portName;
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
ourBoard = null;
|
||||
realBoard = false;
|
||||
masterPortName = k.OBCISimulatorPortName;
|
||||
done();
|
||||
});
|
||||
@@ -950,5 +946,4 @@ describe('openbci-radios', function () {
|
||||
}).catch(err => done(err));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ var openBCISample = openBCIBoard.OpenBCISample;
|
||||
var k = openBCISample.k;
|
||||
var chaiAsPromised = require('chai-as-promised');
|
||||
var sinonChai = require('sinon-chai');
|
||||
var sinonAsPromised = require('sinon-as-promised')(bluebirdChecks.BluebirdPromise);
|
||||
var bufferEqual = require('buffer-equal');
|
||||
var fs = require('fs');
|
||||
var math = require('mathjs');
|
||||
@@ -484,7 +483,6 @@ describe('openbci-sdk', function () {
|
||||
expect(ourBoard.getInfo().numberOfChannels).to.be.equal(k.OBCINumberOfChannelsDefault);
|
||||
expect(ourBoard.getInfo().sampleRate).to.be.equal(k.OBCISampleRate250);
|
||||
});
|
||||
|
||||
});
|
||||
describe('#debug', function () {
|
||||
before(function (done) {
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário