OpenBCI Node.js Ganglion SDK
A Node.js module for OpenBCI.
We are proud to support all functionality of the Ganglion (4 channel).
The purpose of this module is to get connected and start streaming as fast as possible.
Table of Contents:
- TL;DR
- Prerequisites
- Installation
- Ganglion
- General Overview
- SDK Reference Guide
- Interfacing With Other Tools
- Developing
- Testing
- Contribute
- License
- Roadmap
TL;DR:
Get connected and start streaming right now
const Ganglion = require("openbci-ganglion");
const ganglion = new Ganglion();
ganglion.once("ganglionFound", peripheral => {
// Stop searching for BLE devices once a ganglion is found.
ganglion.searchStop();
ganglion.on("sample", sample => {
/** Work with sample */
console.log(sample.sampleNumber);
for (let i = 0; i < ganglion.numberOfChannels(); i++) {
console.log(
"Channel " +
(i + 1) +
": " +
sample.channelData[i].toFixed(8) +
" Volts."
);
}
});
ganglion.once("ready", () => {
ganglion.streamStart();
});
ganglion.connect(peripheral);
});
// Start scanning for BLE devices
ganglion.searchStart();
Prerequisites:
Please ensure Python 2.7 is installed for all OS.
macOS
- install Xcode
Linux
- Kernel version 3.6 or above
libbluetooth-dev
libudev-dev
Running without sudo
In order to stream data on Linux without root/sudo access, you may need to give the node
binary privileges to start and stop BLE advertising.
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)
Note: this command requires setcap
to be installed. Install it with the libcap2-bin
package.
sudo apt-get install libcap2-bin
Windows 8+
-
node-gyp requirements for Windows
- Python 2.7
- Visual Studio (Express)
-
node-bluetooth-hci-socket prerequisites
- Compatible Bluetooth 4.0 USB adapter
- WinUSB driver setup for Bluetooth 4.0 USB adapter, using Zadig tool
See @don's set up guide on Bluetooth LE with Node.js and Noble on Windows.
Installation:
Install from npm:
npm install @openbci/ganglion
About:
The Ganglion driver used by OpenBCI's Processing GUI and Electron Hub.
Check out the automatic tests written for it!
General Overview:
Initialization
Initializing the board:
const Ganglion = require("openbci-ganglion");
const ganglion = new Ganglion();
For initializing with options, such as verbose print outs:
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion({
verbose: true
});
For initializing with callback, such as to catch errors on noble
startup:
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion(error => {
if (error) {
console.log("error", error);
} else {
console.log("no error");
}
});
For initializing with options and callback, such as verbose and to catch errors on noble
startup:
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion(
{
verbose: true
},
error => {
if (error) {
console.log("error", error);
} else {
console.log("no error");
}
}
);
'ready' event
You MUST wait for the 'ready' event to be emitted before streaming/talking with the board. The ready happens asynchronously so installing the 'sample' listener and writing before the ready event might result in... nothing at all.
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion();
ourBoard
.connect(portName)
.then(function(boardSerial) {
ourBoard.on("ready", function() {
/** Start streaming, reading registers, what ever your heart desires */
});
})
.catch(function(err) {
/** Handle connection errors */
});
Sample properties:
-
sampleNumber
(aNumber
between 0-255) -
channelData
(channel data indexed at 0 filled with floating pointNumbers
in Volts) -
accelData
(Array
with X, Y, Z accelerometer values when new data available) -
timeStamp
(Number
theboardTime
plus the NTP calculated offset)
The power of this module is in using the sample emitter, to be provided with samples to do with as you wish.
'sample' event, you need to:
To get a- Call
.connect(localName | peripheral)
- Install the 'ready' event emitter on resolved promise
- In callback for 'ready' emitter, call
streamStart()
- Install the 'sample' event emitter
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion();
ourBoard
.connect(localName)
.then(function() {
ourBoard.on("ready", function() {
ourBoard.streamStart();
ourBoard.on("sample", function(sample) {
/** Work with sample */
});
});
})
.catch(function(err) {
/** Handle connection errors */
});
Close the connection with .streamStop()
and disconnect with .disconnect()
const Ganglion = require("openbci-ganglion");
const ourBoard = new Ganglion();
ourBoard.streamStop().then(ourBoard.disconnect());
See Reference Guide for a complete list of impedance tests.
SDK Reference Guide:
Classes
Typedefs
-
InitializationObject :
Object
-
Board optional configurations.
-
BLED112AttributeValue :
Object
-
BLED112AttributeWrite :
Object
-
BLEDConnection :
Object
-
BLED112FindInformationFound :
Object
-
BLED112GapConnectDirect :
Object
-
BLED112GroupService :
Object
-
BLED112ParseRawAttributeValue :
Object
-
BLED112ParseRawHeadTail :
Object
-
BLED112ParseRawWord :
Object
-
BLED112Peripheral :
Object
-
BLED112RspGroupType :
Object
Ganglion
Kind: global class
Author: AJ Keller (@pushtheworldllc)
-
Ganglion
- new Ganglion(options, callback)
-
instance
-
.options :
InitializationObject
- ._accelArray
-
._bled112WriteCharacteristic :
BLED112FindInformationFound
- .buffer
-
.accelStart() ⇒
Promise
-
.accelStop() ⇒
Promise
- .autoReconnect()
-
.channelOff(channelNumber) ⇒
Promise.<T>
-
.channelOn(channelNumber) ⇒
Promise.<T>
|*
- .cleanupEmitters()
-
.connect(id) ⇒
Promise
- .destroyNoble()
- .destroyBLED112()
- .destroyMultiPacketBuffer()
-
.disconnect(stopStreaming) ⇒
Promise
-
.getLocalName() ⇒
null
|String
-
.getMutliPacketBuffer() ⇒
null
|Buffer
-
.impedanceStart() ⇒
global.Promise
|Promise
-
.impedanceStop() ⇒
global.Promise
|Promise
-
.initDriver() ⇒
Promise.<any>
-
.isConnected() ⇒
boolean
-
.isNobleReady() ⇒
boolean
-
.isSearching() ⇒
boolean
-
.isStreaming() ⇒
boolean
-
.numberOfChannels() ⇒
Number
-
.printRegisterSettings() ⇒
Promise.<T>
|*
-
.sampleRate() ⇒
Number
-
.searchStart(`maxSearchTime`) ⇒
Promise
-
.searchStop() ⇒
global.Promise
|Promise
-
.softReset() ⇒
Promise
-
.streamStart() ⇒
Promise
-
.streamStop() ⇒
Promise
-
.syntheticEnable() ⇒
Promise
-
.syntheticDisable() ⇒
Promise
-
.write(data) ⇒
Promise
-
._bled112WriteAndDrain(data) ⇒
Promise
-
.options :
- inner
new Ganglion(options, callback)
The initialization method to call first, before any other method.
Param | Type | Description |
---|---|---|
options | InitializationObject |
(optional) - Board optional configurations. |
callback | function |
(optional) - A callback function used to determine if the noble module was able to be started. This can be very useful on Windows when there is no compatible BLE device found. |
InitializationObject
ganglion.options : Kind: instance property of Ganglion
ganglion._accelArray
Private Properties (keep alphabetical)
Kind: instance property of Ganglion
BLED112FindInformationFound
ganglion._bled112WriteCharacteristic : Kind: instance property of Ganglion
ganglion.buffer
Public Properties (keep alphabetical)
Kind: instance property of Ganglion
Promise
ganglion.accelStart() ⇒ Used to enable the accelerometer. Will result in accelerometer packets arriving 10 times a second. Note that the accelerometer is enabled by default.
Kind: instance method of Ganglion
Promise
ganglion.accelStop() ⇒ Used to disable the accelerometer. Prevents accelerometer data packets from arriving.
Kind: instance method of Ganglion
ganglion.autoReconnect()
Used to start a scan if power is on. Useful if a connection is dropped.
Kind: instance method of Ganglion
Promise.<T>
ganglion.channelOff(channelNumber) ⇒ Send a command to the board to turn a specified channel off
Kind: instance method of Ganglion
Author: AJ Keller (@pushtheworldllc)
Param |
---|
channelNumber |
Promise.<T>
| *
ganglion.channelOn(channelNumber) ⇒ Send a command to the board to turn a specified channel on
Kind: instance method of Ganglion
Author: AJ Keller (@pushtheworldllc)
Param |
---|
channelNumber |
ganglion.cleanupEmitters()
Used to clean up emitters
Kind: instance method of Ganglion
Promise
ganglion.connect(id) ⇒ The essential precursor method to be called initially to establish a ble connection to the OpenBCI ganglion board.
Kind: instance method of Ganglion
Returns: Promise
- If the board was able to connect.
Author: AJ Keller (@pushtheworldllc)
Param | Type | Description |
---|---|---|
id |
String | Object
|
a string local name or peripheral object |
ganglion.destroyNoble()
Destroys the noble!
Kind: instance method of Ganglion
ganglion.destroyBLED112()
Destroys the noble!
Kind: instance method of Ganglion
ganglion.destroyMultiPacketBuffer()
Destroys the multi packet buffer.
Kind: instance method of Ganglion
Promise
ganglion.disconnect(stopStreaming) ⇒ Closes the connection to the board. Waits for stop streaming command to be sent if currently streaming.
Kind: instance method of Ganglion
Returns: Promise
- - fulfilled by a successful close, rejected otherwise.
Author: AJ Keller (@pushtheworldllc)
Param | Type | Description |
---|---|---|
stopStreaming | Boolean |
(optional) - True if you want to stop streaming before disconnecting. |
null
| String
ganglion.getLocalName() ⇒ Return the local name of the attached Ganglion device.
Kind: instance method of Ganglion
null
| Buffer
ganglion.getMutliPacketBuffer() ⇒ Get's the multi packet buffer.
Kind: instance method of Ganglion
Returns: null
| Buffer
- - Can be null if no multi packets received.
global.Promise
| Promise
ganglion.impedanceStart() ⇒ Call to start testing impedance.
Kind: instance method of Ganglion
global.Promise
| Promise
ganglion.impedanceStop() ⇒ Call to stop testing impedance.
Kind: instance method of Ganglion
Promise.<any>
ganglion.initDriver() ⇒ Initialize the drivers
Kind: instance method of Ganglion
boolean
ganglion.isConnected() ⇒ Checks if the driver is connected to a board.
Kind: instance method of Ganglion
Returns: boolean
- - True if connected.
boolean
ganglion.isNobleReady() ⇒ Checks if bluetooth is powered on.
Kind: instance method of Ganglion
Returns: boolean
- - True if bluetooth is powered on.
boolean
ganglion.isSearching() ⇒ Checks if noble is currently scanning.
Kind: instance method of Ganglion
Returns: boolean
- - True if streaming.
boolean
ganglion.isStreaming() ⇒ Checks if the board is currently sending samples.
Kind: instance method of Ganglion
Returns: boolean
- - True if streaming.
Number
ganglion.numberOfChannels() ⇒ This function is used as a convenience method to determine how many channels the current board is using.
Kind: instance method of Ganglion
Returns: Number
- A number
Note: This is dependent on if you configured the board correctly on setup options
Author: AJ Keller (@pushtheworldllc)
Promise.<T>
| *
ganglion.printRegisterSettings() ⇒ To print out the register settings to the console
Kind: instance method of Ganglion
Author: AJ Keller (@pushtheworldllc)
Number
ganglion.sampleRate() ⇒ Get the the current sample rate is.
Kind: instance method of Ganglion
Returns: Number
- The sample rate
Note: This is dependent on if you configured the board correctly on setup options
Promise
ganglion.searchStart(`maxSearchTime`) ⇒ List available peripherals so the user can choose a device when not automatically found.
Kind: instance method of Ganglion
Returns: Promise
- - If scan was started
Param | Type | Description |
---|---|---|
maxSearchTime |
Number |
The amount of time to spend searching. (Default is 20 seconds) |
global.Promise
| Promise
ganglion.searchStop() ⇒ Called to end a search.
Kind: instance method of Ganglion
Promise
ganglion.softReset() ⇒ Sends a soft reset command to the board
Kind: instance method of Ganglion
Returns: Promise
- - Fulfilled if the command was sent to board.
Author: AJ Keller (@pushtheworldllc)
Promise
ganglion.streamStart() ⇒ Sends a start streaming command to the board.
Kind: instance method of Ganglion
Returns: Promise
- indicating if the signal was able to be sent.
Note: You must have successfully connected to an OpenBCI board using the connect
method. Just because the signal was able to be sent to the board, does not
mean the board will start streaming.
Author: AJ Keller (@pushtheworldllc)
Promise
ganglion.streamStop() ⇒ Sends a stop streaming command to the board.
Kind: instance method of Ganglion
Returns: Promise
- indicating if the signal was able to be sent.
Note: You must have successfully connected to an OpenBCI board using the connect
method. Just because the signal was able to be sent to the board, does not
mean the board stopped streaming.
Author: AJ Keller (@pushtheworldllc)
Promise
ganglion.syntheticEnable() ⇒ Puts the board in synthetic data generation mode. Must call streamStart still.
Kind: instance method of Ganglion
Returns: Promise
- indicating if the signal was able to be sent.
Author: AJ Keller (@pushtheworldllc)
Promise
ganglion.syntheticDisable() ⇒ Takes the board out of synthetic data generation mode. Must call streamStart still.
Kind: instance method of Ganglion
Returns: Promise
- - fulfilled if the command was sent.
Author: AJ Keller (@pushtheworldllc)
Promise
ganglion.write(data) ⇒ Used to send data to the board.
Kind: instance method of Ganglion
Returns: Promise
- - fulfilled if command was able to be sent
Author: AJ Keller (@pushtheworldllc)
Param | Type | Description |
---|---|---|
data |
Array | Buffer | Number | String
|
The data to write out |
Example
Sends a single character command to the board.
// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously
ourBoard.write("a");
Sends an array of bytes
// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously
ourBoard.write(["x", "0", "1", "0", "0", "0", "0", "0", "0", "X"]);
Call crazy? Go for it...
ourBoard.write("t");
ourBoard.write("a");
ourBoard.write("c");
ourBoard.write("o");
Promise
ganglion._bled112WriteAndDrain(data) ⇒ Should be used to send data to the board
Kind: instance method of Ganglion
Returns: Promise
- if signal was able to be sent
Author: AJ Keller (@pushtheworldllc)
Param | Type | Description |
---|---|---|
data |
Buffer | Buffer2
|
The data to write out |
Ganglion~o
Configuring Options
Kind: inner property of Ganglion
kOBCIBLED112ParsingConnectDirect
Used in parsing incoming serial data
Object
InitializationObject : Board optional configurations.
Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
bled112 | Boolean |
Whether to use bled112 as bluetooth driver or default to first available. (Default false ) |
debug | Boolean |
Print out a raw dump of bytes sent and received. (Default false ) |
driverAutoInit | Boolean |
Used to auto start either noble or the bled112 drivers (Default true ) |
nobleAutoStart | Boolean |
Automatically initialize noble . Subscribes to blue tooth state changes and such. (Default true ) |
nobleScanOnPowerOn | Boolean |
Start scanning for Ganglion BLE devices as soon as power turns on. (Default true ) |
sendCounts | Boolean |
Send integer raw counts instead of scaled floats. (Default false ) |
simulate | Boolean |
(IN-OP) Full functionality, just mock data. (Default false ) |
simulatorBoardFailure | Boolean |
(IN-OP) Simulates board communications failure. This occurs when the RFduino on the board is not polling the RFduino on the dongle. (Default false ) |
simulatorHasAccelerometer | Boolean |
Sets simulator to send packets with accelerometer data. (Default true ) |
simulatorInjectAlpha | Boolean |
Inject a 10Hz alpha wave in Channels 1 and 2 (Default true ) |
simulatorInjectLineNoise | String |
Injects line noise on channels. 3 Possible Options: 60Hz - 60Hz line noise (Default) [America] 50Hz - 50Hz line noise [Europe] none - Do not inject line noise. |
simulatorSampleRate | Number |
The sample rate to use for the simulator. Simulator will set to 125 if simulatorDaisyModuleAttached is set true . However, setting this option overrides that setting and this sample rate will be used. (Default is 250 ) |
Boolean |
Print out useful debugging events. (Default false ) |
Object
BLED112AttributeValue : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
characteristicHandle | Number |
|
characteristicHandleRaw | Buffer |
The string of the advertisement data, not the full ad data |
connection | Number |
The connection the info is from |
type | Number |
The type, where 0x01 is data? |
value | Buffer |
The value from device |
Object
BLED112AttributeWrite : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
characteristicHandleRaw | Buffer |
Buffer of length 2 for the service number in the att database |
connection | Number |
Which connection is being used |
value |
String | Buffer
|
The value to send to the device |
Object
BLEDConnection : Kind: global typedef
Properties
Name | Type |
---|---|
addressType | Number |
bonding | Number |
connection | Number |
connectionInterval | Number |
flags | Number |
latency | Number |
sender | Buffer |
timeout | Number |
Object
BLED112FindInformationFound : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
characteristicHandle | Number |
|
characteristicHandleRaw | Buffer |
The string of the advertisement data, not the full ad data |
connection | Number |
The entire end of ad data |
type | Number |
The type, where 0x02 is short uuid and 0x10 is long, it's hex for length |
uuid | Buffer |
Object
BLED112GapConnectDirect : Kind: global typedef
Properties
Name | Type |
---|---|
connection | Number |
result | Buffer |
Object
BLED112GroupService : Kind: global typedef
Properties
Name | Type |
---|---|
connection | number |
end | number |
endRaw | Buffer |
start | number |
startRaw | Buffer |
uuid | Buffer |
Object
BLED112ParseRawAttributeValue : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
buffer |
Buffer | Buffer2
|
The raw data buffer to parse |
ignore | Number |
The position to ignore in the word
|
length | Number |
The length of raw you want to extract |
lengthPosition | Number |
The position of the byte that stores the length of the value |
verify | Object |
|
verify.comparePosition | Number |
The value to compare with position
|
verify.difference | Number |
The difference between position and comparePostion
|
verify.ignore | Number |
The difference between position and comparePostion
|
verify.position | Number |
The position of the verification byte |
word |
Buffer | Buffer2
|
The 4 byte word to search for, ignore byte in postion 1 |
Object
BLED112ParseRawHeadTail : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
buffer |
Buffer | Buffer2
|
The raw data buffer to parse |
head | Number |
The head byte to search for |
length | Number |
The length of raw you want to extract |
tail | Number |
The tail byte to search for |
Object
BLED112ParseRawWord : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
buffer |
Buffer | Buffer2
|
The raw data buffer to parse |
length | Number |
The length of raw you want to extract |
verify | Object |
|
verify.position | Number |
The position of the verification byte |
verify.value | Number |
The value of the verification byte |
word |
Buffer | Buffer2
|
The 4 byte word to search for |
Object
BLED112Peripheral : Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
addressType | Number |
|
advertisement | Object |
|
advertisement.localName | String |
Same as advertisementDataString but mimics what noble outputs |
advertisementDataString | String |
The string of the advertisement data, not the full ad data |
advertisementDataRaw |
Buffer | Buffer2
|
The entire end of ad data |
bond | Number |
|
packetType | Number |
- |
rssi | Number |
The RSSI which stands for receive signal strength indicator and is in db so it's negative, and lower the better. |
sender |
Buffer | Buffer2
|
The mac address |
Object
BLED112RspGroupType : Kind: global typedef
Properties
Name | Type |
---|---|
connection | Number |
result | Buffer |
Events:
.on('accelerometer', callback)
Emitted when the module receives accelerometer data.
Returns an object with properties:
accelData {Array}
Array of floats for each dimension in g's.
NOTE: Only present if sendCounts
is true
.
accelDataCounts {Array}
Array of integers for each dimension in counts.
NOTE: Only present if sendCounts
is false
.
Example (if sendCounts
is false
):
{
"accelData": [0.0, 0.0, 0.0, 0.0]
}
Example (if sendCounts
is true
):
{
"accelDataCounts": [0, 0, 0, 0]
}
.on('droppedPacket', callback)
Emitted when a packet (or packets) are dropped. Returns an array.
.on('error', callback)
Emitted when there is an on the serial port.
.on('impedance', callback)
Emitted when there is a new impedance available.
Returns an object with properties:
channelNumber {Number}
The channel number: 1, 2, 3, 4 respectively and 0 for reference.
impedanceValue {Number}
The impedance in ohms.
Example:
{
"channelNumber": 0,
"impedanceValue": 0
}
.on('rawDataPacket', callback)
Emitted when there is a new raw data packet available.
.on('ready', callback)
Emitted when the board is in a ready to start streaming state.
.on('sample', callback)
Emitted when there is a new sample available.
Returns an object with properties:
channelData {Array}
Array of floats for each channel in volts..
NOTE: Only present if sendCounts
is true
.
channelDataCounts {Array}
Array of integers for each channel in counts.
NOTE: Only present if sendCounts
is false
.
sampleNumber {Number}
The sample number. Only goes up to 254.
timeStamp {Number}
The time the sample is packed up. Not accurate for ERP.
Example (if sendCounts
is false
):
{
"channelData": [0.0, 0.0, 0.0, 0.0],
"sampleNumber": 0,
"timeStamp": 0
}
Example (if sendCounts
is true
):
{
"channelDataCounts": [0, 0, 0, 0],
"sampleNumber": 0,
"timeStamp": 0
}
.on('scanStart', callback)
Emitted when a noble scan is started.
.on('scanStop', callback)
Emitted when a noble scan is stopped.
Interfacing With Other Tools:
LabStreamingLayer
LabStreamingLayer is a tool for streaming or recording time-series data. It can be used to interface with Matlab, Python, Unity, and many other programs.
To use LSL with the NodeJS SDK, go to our labstreaminglayer example, which contains code that is ready to start an LSL stream of OpenBCI data.
Follow the directions in the readme to get started.
Developing:
Running:
npm install
Testing:
npm test
Contribute:
- Fork it!
- Branch off of
development
:git checkout development
- Create your feature branch:
git checkout -b my-new-feature
- Make changes
- If adding a feature, please add test coverage.
- Ensure tests all pass. (
npm test
) - Commit your changes:
git commit -m 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request. Make sure it is based off of the
development
branch when submitting! :D
License:
MIT