├── .eslintrc ├── .npmignore ├── .travis.yml ├── examples ├── python │ ├── setup.py │ ├── package.json │ ├── readme.md │ ├── handoff.py │ └── index.js ├── debug │ ├── package.json │ └── debug.js ├── labstreaminglayer │ ├── setup.py │ ├── package.json │ ├── handoff.py │ ├── readme.md │ └── index.js ├── getStreaming │ ├── package.json │ └── getStreaming.js ├── timeSync │ ├── package.json │ └── timeSync.js ├── impedance │ ├── package.json │ └── impedance.js └── getStreamingDaisy │ ├── package.json │ └── getStreamingDaisy.js ├── .gitignore ├── openBCISerialFormat ├── changelog.md ├── package.json ├── test ├── timingEventsAsPromises.js ├── bluebirdChecks.js ├── openBCICyton-Impedance-test.js ├── openBCICyton-radio-test.js └── openBCISimulator-test.js └── openBCISimulator.js /.eslintrc: -------------------------------------------------------------------------------- 1 | {"extends": ["standard"], "parser": "babel-eslint"} 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | test_mocks/ 3 | 4 | # MIRRORED FROM .gitignore PLEASE MAINTAIN 5 | logs 6 | *.log 7 | pids 8 | *.pid 9 | *.seed 10 | lib-cov 11 | coverage 12 | .grunt 13 | .lock-wscript 14 | build/Release 15 | node_modules 16 | .idea 17 | .DS_Store 18 | public 19 | myOutput.txt 20 | *.tgz 21 | openBCISerialFormat 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | - "8" 6 | - "9" 7 | - "10" 8 | env: 9 | - CXX=g++-4.8 10 | addons: 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | packages: 15 | - g++-4.8 16 | install: 17 | - npm install --all 18 | script: 19 | - npm run test-lint 20 | - npm run test-cov 21 | -------------------------------------------------------------------------------- /examples/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='openbci-node-python', 4 | version='0.0.1', 5 | description='Node to Python the right way', 6 | url='', 7 | author='AJ Keller', 8 | author_email='pushtheworldllc@gmail.com', 9 | license='MIT', 10 | packages=find_packages(), 11 | install_requires=['numpy', 'pyzmq'], 12 | zip_safe=False) -------------------------------------------------------------------------------- /examples/debug/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug", 3 | "version": "1.0.0", 4 | "description": "Debug example", 5 | "main": "debug.js", 6 | "scripts": { 7 | "start": "node debug.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "debug" 12 | ], 13 | "author": "AJ Keller", 14 | "license": "MIT", 15 | "dependencies": { 16 | "openbci": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/labstreaminglayer/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='openbci-node-labstreaminglayer', 4 | version='0.0.1', 5 | description='Labstreaminglayer with NodeJS', 6 | url='', 7 | author='AJ Keller', 8 | author_email='pushtheworldllc@gmail.com', 9 | license='MIT', 10 | packages=find_packages(), 11 | install_requires=['numpy', 'pyzmq','pylsl'], 12 | zip_safe=False) 13 | -------------------------------------------------------------------------------- /examples/getStreaming/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-streaming", 3 | "version": "1.0.0", 4 | "description": "Get streaming example", 5 | "main": "getStreaming.js", 6 | "scripts": { 7 | "start": "node getStreaming.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "get" 12 | ], 13 | "author": "AJ Keller", 14 | "license": "MIT", 15 | "dependencies": { 16 | "openbci": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/timeSync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timesync", 3 | "version": "1.1.0", 4 | "description": "Time sync example", 5 | "main": "timeSync.js", 6 | "scripts": { 7 | "start": "node timeSync.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "time", 12 | "sync" 13 | ], 14 | "author": "AJ Keller", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@openbci/cyton": "^2.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/impedance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-streaming", 3 | "version": "1.0.0", 4 | "description": "Get impedance example", 5 | "main": "getStreaming.js", 6 | "scripts": { 7 | "start": "node impedance.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "get" 12 | ], 13 | "author": "AJ Keller", 14 | "license": "MIT", 15 | "dependencies": { 16 | "openbci": "^2.0.0", 17 | "@openbci/utilities": "0.0.9" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/getStreamingDaisy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-streaming-daisy", 3 | "version": "1.0.0", 4 | "description": "Get streaming with hard set daisy example", 5 | "main": "getStreaming.js", 6 | "scripts": { 7 | "start": "node getStreamingDaisy.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "get" 12 | ], 13 | "author": "AJ Keller", 14 | "license": "MIT", 15 | "dependencies": { 16 | "openbci": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python", 3 | "version": "1.1.0", 4 | "description": "node to python example", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"", 8 | "start-node": "node index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "python", 13 | "openbci", 14 | "node" 15 | ], 16 | "author": "AJ Keller", 17 | "license": "MIT", 18 | "dependencies": { 19 | "concurrently": "^3.1.0", 20 | "zeromq": "^4.6.0" 21 | }, 22 | "devEngines": { 23 | "node": "<=6.x", 24 | "npm": ">=3.x" 25 | }, 26 | "devDependencies": { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/labstreaminglayer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labstreaminglayer", 3 | "version": "1.0.0", 4 | "description": "labstreaminglayer example", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"", 8 | "start-node": "node index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "python", 13 | "openbci", 14 | "node", 15 | "labstreaminglayer", 16 | "LSL" 17 | ], 18 | "author": "AJ Keller", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@openbci/cyton": "^2.0.0", 22 | "zmq-prebuilt": "^2.1.0" 23 | }, 24 | "devEngines": { 25 | "node": "<=6.x", 26 | "npm": ">=3.x" 27 | }, 28 | "devDependencies": { 29 | "concurrently": "^3.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/python/readme.md: -------------------------------------------------------------------------------- 1 | # OpenBCI Node SDK to Python 2 | 3 | ## About 4 | 5 | Written to end the struggles of python researchers and developers. ~ written with love by [Push The World!](http://www.pushtheworldllc.com) 6 | 7 | This module has every feature available on the OpenBCI Board. 8 | 9 | ## Prerequisites 10 | 11 | * [Python 2.7](https://www.python.org/downloads/) 12 | * [ZeroMQ](http://zeromq.org/bindings:python) 13 | 14 | ```py 15 | pip install pyzmq 16 | ``` 17 | * [Node.js LTS](https://nodejs.org/en/) 18 | 19 | 20 | ## Installation 21 | For Python 2.7 do: 22 | ```bash 23 | python setup.py install 24 | ``` 25 | For Node: 26 | ```bash 27 | npm install 28 | ``` 29 | 30 | ## Running 31 | ``` 32 | npm start 33 | ``` 34 | For running just the node, for example if you were running the python in a separate ide and debugging, it's useful. 35 | ```python 36 | npm run start-node 37 | ``` 38 | 39 | ## Contributing 40 | Please PR if you have code to contribute! 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Adapted from OpenEXP 30 | .idea 31 | .DS_Store 32 | public 33 | 34 | # Test output files 35 | myOutput.txt 36 | hardwareVoltageOutputAll.txt 37 | 38 | # Local npm builds for testing end in .tgz 39 | *.tgz 40 | 41 | # For git 42 | *.orig 43 | 44 | # Text editor temporary files 45 | .*.sw* # vi/vim 46 | 47 | # For python 48 | examples/python/dist/* 49 | examples/python/openbci_node_python.egg-info/* 50 | 51 | .vscode/ -------------------------------------------------------------------------------- /openBCISerialFormat: -------------------------------------------------------------------------------- 1 | Start Byte 2 | 0xA0 3 | 10100000 4 | 5 | ID 6 | 0-255 7 | 00000001 8 | 9 | Channel 0 10 | 3 bytes 11 | Signed Int 12 | 00000001 13 | 00000001 14 | 00000001 15 | 16 | Channel 1 17 | 3 bytes 18 | Signed Int 19 | 00000001 20 | 00000001 21 | 00000001 22 | 23 | Channel 2 24 | 3 bytes 25 | Signed Int 26 | 00000001 27 | 00000001 28 | 00000001 29 | 30 | Channel 3 31 | 3 bytes 32 | Signed Int 33 | 00000001 34 | 00000001 35 | 00000001 36 | 37 | Channel 4 38 | 3 bytes 39 | Signed Int 40 | 00000001 41 | 00000001 42 | 00000001 43 | 44 | Channel 5 45 | 3 bytes 46 | Signed Int 47 | 00000001 48 | 00000001 49 | 00000001 50 | 51 | Channel 6 52 | 3 bytes 53 | Signed Int 54 | 00000001 55 | 00000001 56 | 00000001 57 | 58 | Channel 7 59 | 3 bytes 60 | Signed Int 61 | 00000001 62 | 00000001 63 | 00000001 64 | 65 | Aux Data 0 66 | 2 byte 67 | Signed Int 68 | 00000001 69 | 00000001 70 | 71 | Aux Data 1 72 | 2 byte 73 | Signed Int 74 | 00000001 75 | 00000001 76 | 77 | Aux Data 2 78 | 2 byte 79 | Signed Int 80 | 00000001 81 | 00000001 82 | 83 | Stop Byte 84 | 0xC0 85 | 10100000 -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 2.0.1 2 | 3 | ### Bug Fixes 4 | 5 | - Fixing references to openbci-utilities and openbci-cyton 6 | 7 | # 2.0.0 8 | 9 | ### Breaking Changes 10 | 11 | - Name of package is now @openbci/cyton 12 | 13 | ### Enhancements 14 | 15 | - Include node 10 in testing 16 | 17 | # 1.1.3 18 | 19 | ### Bug Fixes 20 | 21 | - Connect function failed and forced close of port when serial port flushed too fast 22 | 23 | # 1.1.2 24 | 25 | ### Bug Fixes 26 | 27 | - Connected function failed 28 | 29 | # 1.1.1 30 | 31 | Bumping the mathjs to 4.0.1 to fix insecurity 32 | 33 | # 1.1.0 34 | 35 | Bumping the minor version of openbci-utilities allows this module to move forward 36 | 37 | ### Breaking Changes 38 | 39 | - Drop support for node 4 because of new OpenBCI utilities 40 | - Connect will now automatically call `softReset` and resolve once it's complete. 41 | 42 | # 1.0.8 43 | 44 | Bump serial port to 1.0.8 45 | 46 | ### Bug Fixes 47 | 48 | - Fix `python` example 49 | 50 | # 1.0.7 51 | 52 | ### Bug Fixes 53 | 54 | - Add simulator back into this repo 55 | - Fixed bug where timestamp was not calculated with timeOffset, big fail. 56 | - Fixed the `python` example 57 | - Fixed the `labstreaminglayer` example 58 | 59 | # 1.0.6 60 | 61 | ### Bug Fixes 62 | 63 | - Daisy samples now get stop bytes with utilities bump to v0.2.7 64 | 65 | # 1.0.4/5 66 | 67 | ### New Features 68 | 69 | - Add function `impedanceSet` which is similar to `channelSet` in that it just sends commands and then forgets about them. 70 | 71 | ### Bug Fixes 72 | 73 | - Fixed lead off command sending for v2 firmware and later. 74 | 75 | # 1.0.3 76 | 77 | ### Bug Fixes 78 | 79 | - Bumped utilities module to v0.2.1 80 | - Fixes issue with `getStreamingDaisy.js` 81 | 82 | # 1.0.2 83 | 84 | ### Chores 85 | 86 | - Removed false badges 87 | 88 | # 1.0.1 89 | 90 | ### Bug Fixes 91 | 92 | - Problem in package.json prevented publishing 93 | 94 | # 1.0.0 95 | 96 | Port `openbci` v2.2.0 to this repo. Please see the change log for `openbci` if you need a history of changes. 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbci/cyton", 3 | "version": "2.0.1", 4 | "description": "The official Node.js SDK for the OpenBCI Cyton with Dongle.", 5 | "main": "openBCICyton.js", 6 | "scripts": { 7 | "start": "node openBCICyton.js", 8 | "test": "semistandard | snazzy && mocha test", 9 | "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov", 10 | "test-lint": "semistandard | snazzy" 11 | }, 12 | "keywords": [ 13 | "openbci", 14 | "openbci-node" 15 | ], 16 | "author": "AJ Keller (www.openbci.com)", 17 | "license": "MIT", 18 | "dependencies": { 19 | "buffer-equal": "^1.0.0", 20 | "lodash": "^4.17.4", 21 | "mathjs": "^4.0.1", 22 | "@openbci/cyton": "^2.0.1", 23 | "@openbci/utilities": "^1.0.0", 24 | "performance-now": "^2.1.0", 25 | "safe-buffer": "^5.1.1", 26 | "serialport": "^6.1.1", 27 | "sntp": "^2.0.2" 28 | }, 29 | "directories": { 30 | "test": "test" 31 | }, 32 | "devDependencies": { 33 | "bluebird": "3.5.0", 34 | "chai": "^4.1.0", 35 | "chai-as-promised": "^7.1.1", 36 | "codecov": "^2.1.0", 37 | "dirty-chai": "^2.0.1", 38 | "eslint-config-semistandard": "^11.0.0", 39 | "eslint-config-standard": "^10.2.1", 40 | "eslint-plugin-promise": "^3.5.0", 41 | "eslint-plugin-react": "^7.3.0", 42 | "eslint-plugin-standard": "^3.0.1", 43 | "istanbul": "^0.4.4", 44 | "mocha": "^3.4.2", 45 | "sandboxed-module": "^2.0.3", 46 | "semistandard": "^11.0.0", 47 | "sinon": "^1.17.7", 48 | "sinon-as-promised": "^4.0.3", 49 | "sinon-chai": "^2.11.0", 50 | "snazzy": "^7.0.0" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/openbci/openbci_nodejs_cyton.git" 55 | }, 56 | "bugs": { 57 | "url": "https://github.com/openbci/openbci_nodejs_cyton/issues" 58 | }, 59 | "homepage": "https://github.com/openbci/openbci_nodejs_cyton#readme", 60 | "engines": { 61 | "node": ">=6.0.0" 62 | }, 63 | "semistandard": { 64 | "globals": [ 65 | "describe", 66 | "xdescribe", 67 | "context", 68 | "before", 69 | "beforeEach", 70 | "after", 71 | "afterEach", 72 | "it", 73 | "expect", 74 | "should" 75 | ] 76 | }, 77 | "publishConfig": { 78 | "access": "public" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /examples/labstreaminglayer/handoff.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import numpy as np 4 | import time 5 | import zmq 6 | from pylsl import StreamInfo, StreamOutlet 7 | 8 | class Interface: 9 | def __init__(self, verbose=False): 10 | context = zmq.Context() 11 | self._socket = context.socket(zmq.PAIR) 12 | self._socket.connect("tcp://localhost:3004") 13 | 14 | self.verbose = verbose 15 | 16 | if self.verbose: 17 | print("Client Ready!") 18 | 19 | # Send a quick message to tell node process we are up and running 20 | self.send(json.dumps({ 21 | 'action': 'started', 22 | 'command': 'status', 23 | 'message': time.time()*1000.0 24 | })) 25 | 26 | def send(self, msg): 27 | """ 28 | Sends a message to TCP server 29 | :param msg: str 30 | A string to send to node TCP server, could be a JSON dumps... 31 | :return: None 32 | """ 33 | if self.verbose: 34 | print('<- out ' + msg) 35 | self._socket.send_string(msg) 36 | return 37 | 38 | def recv(self): 39 | """ 40 | Checks the ZeroMQ for data 41 | :return: str 42 | String of data 43 | """ 44 | return self._socket.recv() 45 | 46 | 47 | def initializeOutlet(interface): 48 | """ 49 | Initializes and returns an LSL outlet 50 | :param interface: Interface 51 | the Python interface to communicate with node 52 | :return: StreamOutlet 53 | returns a labstreaminglayer StreamOutlet 54 | """ 55 | numChans = None 56 | while not numChans: 57 | msg = interface.recv() 58 | try: 59 | dicty = json.loads(msg) 60 | numChans = dicty.get('numChans') 61 | sampleRate = dicty.get('sampleRate') 62 | except ValueError as e: 63 | print(e) 64 | info = StreamInfo('OpenBCI_EEG', 'EEG', numChans, sampleRate, 'float32', 'myuid34234') 65 | outlet = StreamOutlet(info) 66 | print('init') 67 | return outlet 68 | 69 | def main(argv): 70 | verbose = False 71 | # Create a new python interface. 72 | interface = Interface(verbose) 73 | # Create a new labstreaminglayer outlet 74 | outlet = initializeOutlet(interface); 75 | 76 | # Stream incoming data to LSL 77 | while True: 78 | msg = interface.recv() 79 | try: 80 | dicty = json.loads(msg) 81 | message = dicty.get('message') 82 | data=message.get('channelData') 83 | timeStamp = message.get('timeStamp') 84 | outlet.push_sample(data,timeStamp) 85 | except BaseException as e: 86 | print(e) 87 | 88 | 89 | if __name__ == '__main__': 90 | main(sys.argv[1:]) 91 | -------------------------------------------------------------------------------- /examples/getStreamingDaisy/getStreamingDaisy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example from the readme.md 3 | * On windows you should run with PowerShell not git bash. 4 | * Install 5 | * [nodejs](https://nodejs.org/en/) 6 | * 7 | * To run: 8 | * change directory to this file `cd examples/debug` 9 | * do `npm install` 10 | * then `npm start` 11 | */ 12 | const debug = false; // Pretty print any bytes in and out... it's amazing... 13 | const verbose = true; // Adds verbosity to functions 14 | 15 | const Cyton = require('../../openBCICyton'); 16 | let ourBoard = new Cyton({ 17 | boardType: 'daisy', 18 | debug: debug, 19 | hardSet: true, 20 | verbose: verbose 21 | }); 22 | 23 | ourBoard.on('error', (err) => { 24 | console.log(err); 25 | }); 26 | 27 | ourBoard.autoFindOpenBCIBoard().then(portName => { 28 | if (portName) { 29 | /** 30 | * Connect to the board with portName 31 | * Only works if one board is plugged in 32 | * i.e. ourBoard.connect(portName)..... 33 | */ 34 | ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` 35 | .then(() => { 36 | ourBoard.streamStart(); 37 | ourBoard.on('sample', (sample) => { 38 | /** Work with sample */ 39 | for (let i = 0; i < ourBoard.numberOfChannels(); i++) { 40 | console.log(`Channel ${(i + 1)}: ${sample.channelDataCounts[i].toFixed(8)} Volts.`); 41 | // prints to the console 42 | // "Channel 1: 0.00001987 Volts." 43 | // "Channel 2: 0.00002255 Volts." 44 | // ... 45 | // "Channel 16: -0.00001875 Volts." 46 | } 47 | }); 48 | }); 49 | } else { 50 | /** Unable to auto find OpenBCI board */ 51 | console.log('Unable to auto find OpenBCI board'); 52 | } 53 | }); 54 | 55 | function exitHandler (options, err) { 56 | if (options.cleanup) { 57 | if (verbose) console.log('clean'); 58 | ourBoard.removeAllListeners(); 59 | /** Do additional clean up here */ 60 | } 61 | if (err) console.log(err.stack); 62 | if (options.exit) { 63 | if (verbose) console.log('exit'); 64 | ourBoard.disconnect().catch(console.log); 65 | } 66 | } 67 | 68 | if (process.platform === 'win32') { 69 | const rl = require('readline').createInterface({ 70 | input: process.stdin, 71 | output: process.stdout 72 | }); 73 | 74 | rl.on('SIGINT', function () { 75 | process.emit('SIGINT'); 76 | }); 77 | } 78 | 79 | // do something when app is closing 80 | process.on('exit', exitHandler.bind(null, { 81 | cleanup: true 82 | })); 83 | 84 | // catches ctrl+c event 85 | process.on('SIGINT', exitHandler.bind(null, { 86 | exit: true 87 | })); 88 | 89 | // catches uncaught exceptions 90 | process.on('uncaughtException', exitHandler.bind(null, { 91 | exit: true 92 | })); 93 | -------------------------------------------------------------------------------- /examples/debug/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of debugging the board. Thanks to Karl @baffo32 3 | * On windows you should run with PowerShell not git bash. 4 | * Install 5 | * [nodejs](https://nodejs.org/en/) 6 | * 7 | * To run: 8 | * change directory to this file `cd examples/debug` 9 | * do `npm install` 10 | * then `npm start` 11 | */ 12 | 13 | const stream = true; 14 | const debug = true; // Pretty print any bytes in and out... it's amazing... 15 | const verbose = true; // Adds verbosity to functions 16 | const Cyton = require('../../openBCICyton'); 17 | 18 | let ourBoard = new Cyton({ 19 | debug: debug, 20 | verbose: verbose 21 | }); 22 | 23 | ourBoard.autoFindOpenBCIBoard().then(portName => { 24 | if (portName) { 25 | /** 26 | * Connect to the board with portName 27 | * Only works if one board is plugged in 28 | * i.e. ourBoard.connect(portName)..... 29 | */ 30 | // Call to connect 31 | ourBoard 32 | .connect(portName) 33 | .then(() => { 34 | console.log(`connected`); 35 | }) 36 | .catch(err => { 37 | console.log(`connect: ${err}`); 38 | }); 39 | } else { 40 | /** Unable to auto find OpenBCI board */ 41 | console.log('Unable to auto find OpenBCI board'); 42 | } 43 | }); 44 | 45 | /** 46 | * The board is ready to start streaming after the ready function is fired. 47 | */ 48 | var readyFunc = () => { 49 | // Get the sample rate after 'ready' 50 | if (stream) { 51 | ourBoard.streamStart().catch(err => { 52 | console.log(`stream start: ${err}`); 53 | }); 54 | } 55 | }; 56 | 57 | var sampleFunc = sample => { 58 | /** 59 | * Checkout the README.md for all other API functions. 60 | * We support every feature. 61 | * */ 62 | }; 63 | 64 | // Subscribe to your functions 65 | ourBoard.on('ready', readyFunc); 66 | ourBoard.on('sample', sampleFunc); 67 | 68 | function exitHandler (options, err) { 69 | if (options.cleanup) { 70 | if (verbose) console.log('clean'); 71 | ourBoard.removeAllListeners(); 72 | /** Do additional clean up here */ 73 | } 74 | if (err) console.log(err.stack); 75 | if (options.exit) { 76 | if (verbose) console.log('exit'); 77 | if (stream) { 78 | ourBoard.streamStop().catch(console.log); 79 | } 80 | ourBoard.disconnect().catch(console.log); 81 | } 82 | } 83 | 84 | if (process.platform === 'win32') { 85 | const rl = require('readline').createInterface({ 86 | input: process.stdin, 87 | output: process.stdout 88 | }); 89 | 90 | rl.on('SIGINT', function () { 91 | process.emit('SIGINT'); 92 | }); 93 | } 94 | 95 | // do something when app is closing 96 | process.on( 97 | 'exit', 98 | exitHandler.bind(null, { 99 | cleanup: true 100 | }) 101 | ); 102 | 103 | // catches ctrl+c event 104 | process.on( 105 | 'SIGINT', 106 | exitHandler.bind(null, { 107 | exit: true 108 | }) 109 | ); 110 | 111 | // catches uncaught exceptions 112 | process.on( 113 | 'uncaughtException', 114 | exitHandler.bind(null, { 115 | exit: true 116 | }) 117 | ); 118 | -------------------------------------------------------------------------------- /examples/getStreaming/getStreaming.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example from the readme.md 3 | * On windows you should run with PowerShell not git bash. 4 | * Install 5 | * [nodejs](https://nodejs.org/en/) 6 | * 7 | * To run: 8 | * change directory to this file `cd examples/debug` 9 | * do `npm install` 10 | * then `npm start` 11 | */ 12 | const debug = false; // Pretty print any bytes in and out... it's amazing... 13 | const verbose = true; // Adds verbosity to functions 14 | 15 | const Cyton = require('../../openBCICyton'); 16 | let ourBoard = new Cyton({ 17 | debug: debug, 18 | verbose: verbose 19 | }); 20 | 21 | // If you know your port number then enter below if, you don't 22 | // uncommecnt the listPorts function below to print the serial ports 23 | // attached to your computer. You do need to have the FTDI VCP driver 24 | // installed. 25 | let portName = 'COM4'; 26 | // ourBoard.listPorts() 27 | // .then((ports) => { 28 | // console.log('ports', JSON.stringify(ports)); 29 | // }); 30 | 31 | // You can also pass the port name as a command line argument! 32 | // i.e. windows 33 | // > node .\examples\getStreaming\getStreaming.js COM5 34 | // REMBEMER THE COM NUMBER CHANGES ON WINDOWS!! 35 | // i.e. macOS 36 | // $ node examples/getStreaming/getStreaming.js /dev/tty/usbserial-DB008JAM 37 | const myArgs = process.argv.slice(2); 38 | if (myArgs.length === 1) { 39 | portName = myArgs[0]; 40 | } 41 | 42 | ourBoard.on('sample', (sample) => { 43 | /** Work with sample */ 44 | for (let i = 0; i < ourBoard.numberOfChannels(); i++) { 45 | console.log(`Channel ${(i + 1)}: ${sample.channelData[i].toFixed(8)} Volts.`); 46 | // prints to the console 47 | // "Channel 1: 0.00001987 Volts." 48 | // "Channel 2: 0.00002255 Volts." 49 | // ... 50 | // "Channel 8: -0.00001875 Volts." 51 | } 52 | }); 53 | 54 | /** 55 | * Connect to the board with portName 56 | * Only works if one board is plugged in 57 | * i.e. ourBoard.connect(portName)..... 58 | */ 59 | ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` 60 | .then(() => { 61 | return ourBoard.syncRegisterSettings(); 62 | }) 63 | .then((cs) => { 64 | return ourBoard.streamStart(); 65 | }) 66 | .catch((err) => { 67 | console.log('err', err); 68 | return ourBoard.streamStart(); 69 | }) 70 | .catch((err) => { 71 | console.log('fatal err', err); 72 | process.exit(0); 73 | }); 74 | 75 | function exitHandler (options, err) { 76 | if (options.cleanup) { 77 | if (verbose) console.log('clean'); 78 | ourBoard.removeAllListeners(); 79 | /** Do additional clean up here */ 80 | } 81 | if (err) console.log(err.stack); 82 | if (options.exit) { 83 | if (verbose) console.log('exit'); 84 | ourBoard.disconnect() 85 | .then(() => { 86 | process.exit(0); 87 | }) 88 | .catch((err) => { 89 | console.log(err); 90 | process.exit(0); 91 | }); 92 | } 93 | } 94 | 95 | if (process.platform === 'win32') { 96 | const rl = require('readline').createInterface({ 97 | input: process.stdin, 98 | output: process.stdout 99 | }); 100 | 101 | rl.on('SIGINT', function () { 102 | process.emit('SIGINT'); 103 | }); 104 | } 105 | 106 | // do something when app is closing 107 | process.on('exit', exitHandler.bind(null, { 108 | cleanup: true 109 | })); 110 | 111 | // catches ctrl+c event 112 | process.on('SIGINT', exitHandler.bind(null, { 113 | exit: true 114 | })); 115 | 116 | // catches uncaught exceptions 117 | process.on('uncaughtException', exitHandler.bind(null, { 118 | exit: true 119 | })); 120 | -------------------------------------------------------------------------------- /examples/labstreaminglayer/readme.md: -------------------------------------------------------------------------------- 1 | # OpenBCI Node SDK to Lab Streaming Layer 2 | 3 | ## About 4 | 5 | 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. 6 | 7 | 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)). 8 | 9 | ## Prerequisites 10 | 11 | * [Python](https://www.python.org/downloads/) 12 | * [ZeroMQ](http://zeromq.org/bindings:python) 13 | 14 | ```py 15 | pip install pyzmq 16 | ``` 17 | * [Node.js LTS](https://nodejs.org/en/) 18 | * [Lab Streaming Layer](https://github.com/sccn/labstreaminglayer) 19 | 20 | ```py 21 | pip install pylsl 22 | ``` 23 | 24 | 25 | ## Installation 26 | First, install Python dependencies: 27 | ```bash 28 | python setup.py install 29 | ``` 30 | Next, install NodeJS dependencies: 31 | ```bash 32 | npm install 33 | ``` 34 | Note: depending on your computer settings, you may need to run as administrator or with `sudo`. 35 | 36 | ## Running 37 | ``` 38 | npm start 39 | ``` 40 | For running just the node, for example if you were running the python in a separate ide and debugging, it's useful. 41 | ```python 42 | npm run start-node 43 | ``` 44 | Note: depending on your computer settings, you may need to run as administrator or with `sudo`. 45 | 46 | ## Writing Lab Streaming Layer Code 47 | 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: 48 | 49 | index.js 50 | ```js 51 | var OpenBCIBoard = require('openbci').OpenBCIBoard; 52 | var portPub = 'tcp://127.0.0.1:3004'; 53 | var zmq = require('zmq-prebuilt'); 54 | var socket = zmq.socket('pair'); 55 | var ourBoard = new OpenBCIBoard(); 56 | 57 | socket.bind(portPub); 58 | 59 | ourBoard.autoFindOpenBCIBoard().then(portName => { 60 | if (portName) { 61 | ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` 62 | .then(() => { 63 | ourBoard.on('ready',() => { 64 | ourBoard.streamStart(); 65 | ourBoard.on('sample',(sample) => { 66 | if (socket) { 67 | socket.send(JSON.stringify({message: sample})); 68 | } 69 | } 70 | }); 71 | }); 72 | }); 73 | } else { 74 | console.log('Unable to auto find OpenBCI board'); 75 | } 76 | }); 77 | 78 | /* Insert additional exit handlers and cleanup below*/ 79 | ``` 80 | 81 | handoff.py 82 | ```python 83 | import json 84 | import zmq 85 | from pylsl import StreamInfo, StreamOutlet 86 | 87 | # Create ZMQ socket 88 | context = zmq.Context() 89 | _socket = context.socket(zmq.PAIR) 90 | _socket.connect("tcp://localhost:3004") 91 | 92 | # Create a new labstreaminglayer outlet 93 | numChans = 8; 94 | sampleRate = 250; 95 | info = StreamInfo('OpenBCI_EEG', 'EEG', numChans, sampleRate, 'float32', 'openbci_12345') 96 | outlet = StreamOutlet(info) 97 | # Stream incoming data to LSL 98 | while True: 99 | msg = _socket.recv() 100 | try: 101 | dicty = json.loads(msg) 102 | message = dicty.get('message') 103 | data=message.get('channelData') 104 | timeStamp = message.get('timeStamp') 105 | outlet.push_sample(data,timeStamp) 106 | except BaseException as e: 107 | print(e) 108 | ``` 109 | 110 | ## Contributing 111 | Please PR if you have code to contribute! 112 | -------------------------------------------------------------------------------- /test/timingEventsAsPromises.js: -------------------------------------------------------------------------------- 1 | // Converts timing events to use promises, to gain bluebird's checks 2 | 3 | function instrumentTimingEvents (module, type) { 4 | // replaces module[type] with a function that does the same thing but uses a promise 5 | // assumes the first argument will be to a callback function 6 | 7 | // if this a setSomething function with a clearSomething partner, the partner will also be instrumented 8 | var setName = type; 9 | var clearName = null; 10 | if (type.substring(0, 3) === 'set') { 11 | clearName = 'clear' + type.substring(3); 12 | } 13 | 14 | if (!module[setName]) return; 15 | 16 | // store original functions 17 | var originalSet = module[setName]; 18 | var originalClear = module[clearName]; 19 | exports[setName + 'Ignored'] = originalSet; 20 | 21 | // dictionary to store promise details for each call 22 | var events = {}; 23 | 24 | // setAsPromise() is the brunt of the function. It replaces the previous global function, 25 | // and sets up a promise to resolve when the callback is called 26 | var eventCount = 0; 27 | var setAsPromise = function (callback) { 28 | var args = [].slice.call(arguments); 29 | 30 | var eventNum = ++eventCount; 31 | var eventHandle; 32 | 33 | if (setAsPromise._ignoreCount > 0) { 34 | --setAsPromise._ignoreCount; 35 | 36 | return originalSet.apply(this, args); 37 | } 38 | 39 | // actual callback is replaced by promise resolve 40 | args[0] = function () { 41 | if (!events[eventNum]) throw new Error(setName + ' ' + eventNum + ' disappeared'); 42 | events[eventNum].resolve([].slice.call(arguments)); 43 | }; 44 | 45 | eventHandle = originalSet.apply(this, args) || eventNum; 46 | 47 | // this portion is a function so that setInterval may be handled via recursion 48 | function dispatch () { 49 | var handlerDetails = { handle: eventHandle }; 50 | 51 | var promise = new Promise((resolve, reject) => { 52 | handlerDetails.resolve = resolve; 53 | handlerDetails.reject = reject; 54 | }); 55 | 56 | handlerDetails.promise = promise; 57 | events[eventNum] = handlerDetails; 58 | 59 | promise.then(function (argumentArray) { 60 | if (type !== 'setInterval') { 61 | delete events[eventNum]; 62 | } else { 63 | dispatch(); 64 | } 65 | 66 | // call original handler 67 | callback.apply(this, argumentArray); 68 | }, () => { 69 | originalClear(eventHandle); 70 | delete events[eventNum]; 71 | }); 72 | 73 | return promise; 74 | } 75 | 76 | dispatch(); 77 | 78 | return eventNum; 79 | }; 80 | 81 | // actually replace functions with instrumented ones 82 | setAsPromise._ignoreCount = 0; 83 | module[setName] = setAsPromise; 84 | Object.defineProperty(setAsPromise, 'name', { value: setName + 'AsPromise' }); 85 | 86 | if (clearName) { 87 | module[clearName] = (eventNum) => { 88 | if (!events[eventNum]) { 89 | originalClear(eventNum); 90 | } else { 91 | events[eventNum].reject(new Error('cleared')); 92 | } 93 | }; 94 | Object.defineProperty(module[clearName], 'name', { value: clearName + 'AsPromise' }); 95 | } 96 | } 97 | 98 | instrumentTimingEvents(global, 'setTimeout'); 99 | instrumentTimingEvents(global, 'setInterval'); 100 | instrumentTimingEvents(global, 'setImmediate'); 101 | // // Possible TODO: nextTick needs some exceptions included to prevent infinite recursion 102 | // instrumentTimingEvents(process, 'nextTick'); 103 | 104 | // the next call to the passed function should not be promisified 105 | // may be queued multiple times 106 | exports.ignoreOnce = (ignored) => { 107 | ignored._ignoreCount++; 108 | }; 109 | -------------------------------------------------------------------------------- /examples/timeSync/timeSync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of time syncing every second for one minute. Used with a 3 | * real board. 4 | * To run: 5 | * change directory to this file `cd examples/timeSync` 6 | * do `npm install` 7 | * then `npm start` 8 | */ 9 | const verbose = true; // Adds verbosity to functions 10 | 11 | const Cyton = require('@openbci/cyton').Cyton; 12 | let ourBoard = new Cyton({ 13 | simulatorFirmwareVersion: 'v2', 14 | verbose: verbose 15 | }); 16 | 17 | let sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! 18 | let timeSyncPossible = false; 19 | 20 | ourBoard.autoFindOpenBCIBoard().then(portName => { 21 | if (portName) { 22 | /** 23 | * Connect to the board with portName 24 | * i.e. ourBoard.connect(portName)..... 25 | */ 26 | // Call to connect 27 | ourBoard 28 | .connect(portName) 29 | .then(() => { 30 | console.log(`connected`); 31 | readyFunc(); 32 | }) 33 | .catch(err => { 34 | console.log(`connect: ${err}`); 35 | }); 36 | } else { 37 | /** Unable to auto find OpenBCI board */ 38 | console.log('Unable to auto find OpenBCI board'); 39 | } 40 | }); 41 | 42 | const readyFunc = () => { 43 | // Get the sample rate after 'ready' 44 | sampleRate = ourBoard.sampleRate(); 45 | // 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. 46 | timeSyncPossible = ourBoard.usingVersionTwoFirmware(); 47 | 48 | if (timeSyncPossible) { 49 | ourBoard.streamStart().catch(err => { 50 | console.log(`stream start: ${err}`); 51 | }); 52 | } else { 53 | killFunc(); 54 | } 55 | }; 56 | 57 | const killFunc = () => { 58 | ourBoard.disconnect().then(() => { 59 | process.kill(); 60 | }); 61 | }; 62 | 63 | const sampleFunc = sample => { 64 | // Resynchronize every every second 65 | if (sample._count % (sampleRate * 1) === 0) { 66 | ourBoard.syncClocksFull().then(syncObj => { 67 | // Sync was successful 68 | if (syncObj.valid) { 69 | // Log the object to check it out! 70 | console.log(`timeOffset`, syncObj.timeOffsetMaster); 71 | } else { 72 | // Retry it 73 | console.log(`Was not able to sync... retry!`); 74 | } 75 | }); 76 | } 77 | 78 | if (sample.timeStamp) { 79 | // true after the first successful sync 80 | if (sample.timeStamp < 10 * 60 * 60 * 1000) { 81 | // Less than 10 hours 82 | console.log(`Bad time sync ${sample.timeStamp}`); 83 | } 84 | } 85 | 86 | // Stop after one minute 87 | if (sample._count > sampleRate * 60) { 88 | killFunc(); 89 | } 90 | }; 91 | 92 | // Subscribe to your functions 93 | // ourBoard.on('ready', readyFunc); 94 | ourBoard.on('sample', sampleFunc); 95 | 96 | function exitHandler (options, err) { 97 | if (options.cleanup) { 98 | if (verbose) console.log('clean'); 99 | /** Do additional clean up here */ 100 | } 101 | if (err) console.log(err.stack); 102 | if (options.exit) { 103 | if (verbose) console.log('exit'); 104 | ourBoard.disconnect().catch(console.log); 105 | } 106 | } 107 | 108 | if (process.platform === 'win32') { 109 | const rl = require('readline').createInterface({ 110 | input: process.stdin, 111 | output: process.stdout 112 | }); 113 | 114 | rl.on('SIGINT', function () { 115 | process.emit('SIGINT'); 116 | }); 117 | } 118 | 119 | // do something when app is closing 120 | process.on( 121 | 'exit', 122 | exitHandler.bind(null, { 123 | cleanup: true 124 | }) 125 | ); 126 | 127 | // catches ctrl+c event 128 | process.on( 129 | 'SIGINT', 130 | exitHandler.bind(null, { 131 | exit: true 132 | }) 133 | ); 134 | 135 | // catches uncaught exceptions 136 | process.on( 137 | 'uncaughtException', 138 | exitHandler.bind(null, { 139 | exit: true 140 | }) 141 | ); 142 | -------------------------------------------------------------------------------- /examples/python/handoff.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import numpy as np 4 | import time 5 | import zmq 6 | 7 | 8 | class Interface: 9 | def __init__(self, verbose=False): 10 | self._context = zmq.Context() 11 | self._socket = self._context.socket(zmq.PAIR) 12 | self._socket.connect("tcp://localhost:3004") 13 | 14 | self.verbose = verbose 15 | 16 | if self.verbose: 17 | print "Client Ready!" 18 | 19 | # Send a quick message to tell node process we are up and running 20 | self.send(json.dumps({ 21 | 'action': 'started', 22 | 'command': 'status', 23 | 'message': time.time()*1000.0 24 | })) 25 | 26 | def send(self, msg): 27 | """ 28 | Sends a message to TCP server 29 | :param msg: str 30 | A string to send to node TCP server, could be a JSON dumps... 31 | :return: None 32 | """ 33 | if self.verbose: 34 | print '<- out ' + msg 35 | self._socket.send(msg) 36 | return 37 | 38 | def recv(self): 39 | """ 40 | Checks the ZeroMQ for data 41 | :return: str 42 | String of data 43 | """ 44 | return self._socket.recv() 45 | 46 | def close(self): 47 | """ 48 | Closes the zmq context 49 | """ 50 | self._backend.close() 51 | self._context.term() 52 | 53 | 54 | class RingBuffer(np.ndarray): 55 | """A multidimensional ring buffer.""" 56 | 57 | def __new__(cls, input_array): 58 | obj = np.asarray(input_array).view(cls) 59 | return obj 60 | 61 | def __array_finalize__(self, obj): 62 | if obj is None: 63 | return 64 | 65 | def __array_wrap__(self, out_arr, context=None): 66 | return np.ndarray.__array_wrap__(self, out_arr, context) 67 | 68 | def append(self, x): 69 | """Adds element x to the ring buffer.""" 70 | x = np.asarray(x) 71 | self[:, :-1] = self[:, 1:] 72 | self[:, -1] = x 73 | 74 | 75 | def main(argv): 76 | nb_chan = 8 77 | verbose = True 78 | 79 | # Create a new python interface. 80 | interface = Interface(verbose=verbose) 81 | # Signal buffer 82 | signal = RingBuffer(np.zeros((nb_chan + 1, 2500))) 83 | try: 84 | while True: 85 | msg = interface.recv() 86 | print "woo" 87 | try: 88 | dicty = json.loads(msg) 89 | action = dicty.get('action') 90 | command = dicty.get('command') 91 | message = dicty.get('message') 92 | 93 | if command == 'sample': 94 | if action == 'process': 95 | # Do sample processing here 96 | try: 97 | if type(message) is not dict: 98 | print "sample is not a dict", message 99 | raise ValueError 100 | # Get keys of sample 101 | data = np.zeros(nb_chan + 1) 102 | 103 | data[:-1] = message.get('channelData') 104 | data[-1] = message.get('timeStamp') 105 | 106 | # Add data to end of ring buffer 107 | signal.append(data) 108 | 109 | print message.get('sampleNumber') 110 | except ValueError as e: 111 | print e 112 | elif command == 'status': 113 | if action == 'active': 114 | interface.send(json.dumps({ 115 | 'action': 'alive', 116 | 'command': 'status', 117 | 'message': time.time() * 1000.0 118 | })) 119 | except KeyboardInterrupt: 120 | print "W: interrupt received, stopping" 121 | print("Python ZMQ Link Clean Up") 122 | interface.close() 123 | raise ValueError("Peace") 124 | except BaseException as e: 125 | print e 126 | 127 | 128 | if __name__ == '__main__': 129 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /examples/impedance/impedance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example from the readme.md 3 | * On windows you should run with PowerShell not git bash. 4 | * Install 5 | * [nodejs](https://nodejs.org/en/) 6 | * 7 | * To run: 8 | * change directory to this file `cd examples/debug` 9 | * do `npm install` 10 | * then `npm start` 11 | */ 12 | const debug = false; // Pretty print any bytes in and out... it's amazing... 13 | const verbose = true; // Adds verbosity to functions 14 | 15 | const Cyton = require('@openbci/cyton'); 16 | let ourBoard = new Cyton({ 17 | debug: debug, 18 | verbose: verbose 19 | }); 20 | 21 | const k = require('@openbci/utilities').constants; 22 | 23 | let startedImpedance = false; 24 | let iBuffer = []; 25 | let count = 0; 26 | const window = 50; 27 | 28 | ourBoard.autoFindOpenBCIBoard().then(portName => { 29 | if (portName) { 30 | /** 31 | * Connect to the board with portName 32 | * Only works if one board is plugged in 33 | * i.e. ourBoard.connect(portName)..... 34 | */ 35 | ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()` 36 | .then(() => { 37 | ourBoard.streamStart(); 38 | ourBoard.on('sample', (sample) => { 39 | if (startedImpedance === false) { 40 | startedImpedance = true; 41 | k.getImpedanceSetter(1, false, true) 42 | .then((commands) => { 43 | return ourBoard.write(commands); 44 | }) 45 | .then(() => { 46 | console.log('wrote commands to board'); 47 | }) 48 | .catch((err) => { 49 | console.log('errr', err); 50 | }); 51 | } 52 | /** Work with sample */ 53 | const chan1ValInVolts = sample.channelData[0]; 54 | 55 | // const impedance = chan1ValInVolts / k.OBCILeadOffDriveInAmps; 56 | 57 | // console.log(`impedance:\t${impedance} kOhms`); 58 | iBuffer.push(chan1ValInVolts); 59 | count++; 60 | if (count >= window) { 61 | let max = 0.0; // sumSquared 62 | for (let i = 0; i < window; i++) { 63 | if (iBuffer[i] > max) { 64 | max = iBuffer[i]; 65 | } 66 | // sumSquared += iBuffer[i] * iBuffer[i]; 67 | } 68 | let min = 0.0; 69 | for (let i = 0; i < window; i++) { 70 | if (iBuffer[i] < min) { 71 | min = iBuffer[i]; 72 | } 73 | // sumSquared += iBuffer[i] * iBuffer[i]; 74 | } 75 | const vP2P = max - min; // peak to peak 76 | 77 | console.log(`impedance: \t${vP2P / 2 / k.OBCILeadOffDriveInAmps}`); 78 | // console.log(`impedance: ${vRms/k.OBCILeadOffDriveInAmps}`); 79 | 80 | // const mean_squared = sumSquared / window; 81 | // const root_mean_squared = Math.sqrt(mean_squared); 82 | // console.log(`impedance: ${root_mean_squared/k.OBCILeadOffDriveInAmps}`); 83 | 84 | count = 0; 85 | iBuffer = []; 86 | } 87 | // console.log(`uV:\t${chan1ValInVolts/(10*6)}\nimpedance:\t${impedance}`); 88 | }); 89 | }); 90 | } else { 91 | /** Unable to auto find OpenBCI board */ 92 | console.log('Unable to auto find OpenBCI board'); 93 | } 94 | }); 95 | 96 | function exitHandler (options, err) { 97 | if (options.cleanup) { 98 | if (verbose) console.log('clean'); 99 | /** Do additional clean up here */ 100 | ourBoard.disconnect().catch(console.log); 101 | ourBoard.removeAllListeners(); 102 | } 103 | if (err) console.log(err.stack); 104 | if (options.exit) { 105 | if (verbose) console.log('exit'); 106 | ourBoard.streamStop() 107 | .then(() => { 108 | process.exit(0); 109 | }) 110 | .catch((err) => { 111 | console.log(err); 112 | process.exit(0); 113 | }); 114 | } 115 | } 116 | 117 | if (process.platform === 'win32') { 118 | const rl = require('readline').createInterface({ 119 | input: process.stdin, 120 | output: process.stdout 121 | }); 122 | 123 | rl.on('SIGINT', function () { 124 | process.emit('SIGINT'); 125 | }); 126 | } 127 | 128 | // do something when app is closing 129 | process.on('exit', exitHandler.bind(null, { 130 | cleanup: true 131 | })); 132 | 133 | // catches ctrl+c event 134 | process.on('SIGINT', exitHandler.bind(null, { 135 | exit: true 136 | })); 137 | 138 | // catches uncaught exceptions 139 | process.on('uncaughtException', exitHandler.bind(null, { 140 | exit: true 141 | })); 142 | -------------------------------------------------------------------------------- /test/bluebirdChecks.js: -------------------------------------------------------------------------------- 1 | var timingEventsAsPromises = require('./timingEventsAsPromises'); 2 | exports.BluebirdPromise = require('bluebird'); 3 | exports.PromiseIgnored = global.Promise; 4 | 5 | // Enable bluebird for all promise usage during tests only 6 | // Fails tests for issues bluebird finds 7 | // Exports a function to list all promises (getPendingPromises) 8 | // Exports a function to verify no promises pending within a timeout (noPendingPromises) 9 | 10 | exports.BluebirdPromise.config({ 11 | // TODO: wForgottenReturn is disabled because timingEventsAsPromises triggers it; find a workaround 12 | warnings: { wForgottenReturn: false }, 13 | longStackTraces: true, 14 | monitoring: true, 15 | cancellation: true 16 | }); 17 | 18 | // nextTick conveniently not instrumented by timingEventsAsPromises 19 | exports.BluebirdPromise.setScheduler(process.nextTick); 20 | 21 | // unhandled rejections become test failures 22 | process.on('unhandledRejection', (reason, promise) => { 23 | if (!(reason instanceof Error)) { 24 | reason = new Error('unhandled promise rejection: ' + reason); 25 | } else { 26 | reason.message = 'unhandled promise rejection: ' + reason.message; 27 | } 28 | process.nextTick(() => { throw reason; }); 29 | }); 30 | 31 | // // warnings become test failures 32 | // process.on('warning', (warning) => { 33 | // var error = new Error(warning); 34 | // process.nextTick(() => { throw error; }); 35 | // }); 36 | 37 | // provide access to all currently pending promises 38 | var pendingPromises = {}; 39 | var promiseId = 0; 40 | var nested = 0; 41 | 42 | function promiseCreationHandler (promise) { 43 | // promise created already resolved; ignore 44 | if (!promise.isPending()) return; 45 | 46 | // need to create another promise to get access to the extended stack trace 47 | // nested detects if we are inside our own dummy promise 48 | ++nested; 49 | if (nested === 1) { 50 | // not the dummy promise 51 | promise.___id = ++promiseId; 52 | // store promise details 53 | var error = new Error('Promise ' + promise.___id + ' is still pending'); 54 | var entry = { 55 | promise: promise, 56 | id: promise.___id, 57 | error: error 58 | }; 59 | pendingPromises[promise.___id] = entry; 60 | // extract stack trace by rejecting an error; bluebird fills in expanded stack 61 | exports.BluebirdPromise.reject(error).catch(error => { 62 | entry.error = error; 63 | entry.stack = error.stack; 64 | }); 65 | } else { 66 | promise.___nested = nested; 67 | } 68 | --nested; 69 | } 70 | process.on('promiseCreated', promiseCreationHandler); 71 | 72 | function promiseDoneHandler (promise) { 73 | if (promise.___nested) return; 74 | delete pendingPromises[promise.___id]; 75 | } 76 | process.on('promiseFulfilled', promiseDoneHandler); 77 | process.on('promiseRejected', promiseDoneHandler); 78 | process.on('promiseResolved', promiseDoneHandler); 79 | process.on('promiseCancelled', promiseDoneHandler); 80 | 81 | exports.getPendingPromises = function () { 82 | var ret = []; 83 | for (var promise in pendingPromises) { 84 | ret.push(pendingPromises[promise]); 85 | } 86 | return ret; 87 | }; 88 | 89 | exports.noPendingPromises = function (milliseconds) { 90 | if (!milliseconds) milliseconds = 0; 91 | 92 | return new exports.PromiseIgnored((resolve, reject) => { 93 | function waited100 () { 94 | var promises = exports.getPendingPromises(); 95 | 96 | if (promises.length === 0) { 97 | return resolve(); 98 | } 99 | 100 | if (milliseconds > 0) { 101 | milliseconds -= 100; 102 | return timingEventsAsPromises.setTimeoutIgnored(waited100, 100); 103 | } 104 | 105 | // timed out, but promises remaining: cancel all 106 | 107 | console.log(promises.length + ' promises still pending'); 108 | 109 | promises.forEach(promise => { 110 | promise.promise.cancel(); 111 | }); 112 | 113 | // report one 114 | reject(promises[0].error); 115 | } 116 | 117 | timingEventsAsPromises.setTimeoutIgnored(waited100, 0); 118 | }); 119 | }; 120 | 121 | // now instrument the Promise object itself to always use a simplified version of bluebird 122 | // bluebird is composed inside a bare-bones Promise object providing only the official calls 123 | 124 | global.Promise = function (handler) { 125 | this._promise = new exports.BluebirdPromise(handler); 126 | }; 127 | 128 | // compose class methods 129 | ['all', 'race', 'reject', 'resolve'].forEach(classMethod => { 130 | global.Promise[classMethod] = function () { 131 | return exports.BluebirdPromise[classMethod].apply(exports.BluebirdPromise, [].slice.call(arguments)); 132 | }; 133 | Object.defineProperty(global.Promise[classMethod], 'name', { value: 'Promise.' + classMethod }); 134 | }); 135 | 136 | // compose object methods 137 | ['then', 'catch'].forEach(objectMethod => { 138 | global.Promise.prototype[objectMethod] = function () { 139 | return this._promise[objectMethod].apply(this._promise, [].slice.call(arguments)); 140 | }; 141 | Object.defineProperty(global.Promise.prototype[objectMethod], 'name', { value: 'Promise.' + objectMethod }); 142 | }); 143 | -------------------------------------------------------------------------------- /examples/labstreaminglayer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example from the readme.md 3 | * On windows you should run with PowerShell not git bash. 4 | * Install 5 | * [nodejs](https://nodejs.org/en/) 6 | * 7 | * To run: 8 | * change directory to this file `cd examples/debug` 9 | * do `npm install` 10 | * then `npm start` 11 | */ 12 | const portPub = 'tcp://127.0.0.1:3004'; 13 | const zmq = require('zmq-prebuilt'); 14 | const socket = zmq.socket('pair'); 15 | const debug = false; // Pretty print any bytes in and out... it's amazing... 16 | const verbose = true; // Adds verbosity to functions 17 | 18 | const Cyton = require('@openbci/cyton'); 19 | let ourBoard = new Cyton({ 20 | simulatorFirmwareVersion: 'v2', 21 | debug: debug, 22 | verbose: verbose 23 | }); 24 | 25 | let timeSyncPossible = false; 26 | let resyncPeriodMin = 1; 27 | let secondsInMinute = 60; 28 | let numChans = 8; 29 | let resyncPeriod = 30 | ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; 31 | 32 | ourBoard.autoFindOpenBCIBoard().then(portName => { 33 | if (portName) { 34 | /** 35 | * Connect to the board with portName 36 | * i.e. ourBoard.connect(portName)..... 37 | */ 38 | // Call to connect 39 | ourBoard 40 | .connect(portName) 41 | .then(() => { 42 | // Get the sample rate after 'ready' 43 | numChans = ourBoard.numberOfChannels(); 44 | if (numChans === 16) { 45 | ourBoard.overrideInfoForBoardType('daisy'); 46 | } 47 | 48 | // 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. 49 | timeSyncPossible = ourBoard.usingAtLeastVersionTwoFirmware(); 50 | 51 | sendToPython({ 52 | numChans: numChans, 53 | sampleRate: ourBoard.sampleRate() 54 | }); 55 | if (timeSyncPossible) { 56 | ourBoard.streamStart().catch(err => { 57 | console.log(`stream start: ${err}`); 58 | }); 59 | } else { 60 | console.log('not able to time sync'); 61 | } 62 | }) 63 | .catch(err => { 64 | console.log(`connect: ${err}`); 65 | }); 66 | } else { 67 | /** Unable to auto find OpenBCI board */ 68 | console.log('Unable to auto find OpenBCI board'); 69 | } 70 | }); 71 | 72 | const sampleFunc = sample => { 73 | if (sample._count % resyncPeriod === 0) { 74 | ourBoard.syncClocksFull().then(syncObj => { 75 | // Sync was successful 76 | if (syncObj.valid) { 77 | // Log the object to check it out! 78 | console.log(`timeOffset`, syncObj.timeOffsetMaster); 79 | } else { 80 | // Retry it 81 | console.log(`Was not able to sync... retry!`); 82 | } 83 | }); 84 | } 85 | 86 | if (sample.timestamp) { 87 | // true after the first successful sync 88 | if (sample.timestamp < 10 * 60 * 60 * 1000) { 89 | // Less than 10 hours 90 | console.log(`Bad time sync ${sample.timestamp}`); 91 | } else { 92 | sendToPython({ 93 | action: 'process', 94 | command: 'sample', 95 | message: sample 96 | }); 97 | } 98 | } 99 | }; 100 | 101 | // Subscribe to your functions 102 | ourBoard.on('sample', sampleFunc); 103 | 104 | // ZMQ 105 | socket.bind(portPub, function (err) { 106 | if (err) throw err; 107 | console.log(`bound to ${portPub}`); 108 | }); 109 | 110 | /** 111 | * Used to send a message to the Python process. 112 | * @param {Object} interProcessObject The standard inter-process object. 113 | * @param {Boolean} verbose Should we do a verbose print out 114 | * @return {None} 115 | */ 116 | const sendToPython = (interProcessObject, verbose) => { 117 | if (verbose) { 118 | console.log(`<- out ${JSON.stringify(interProcessObject)}`); 119 | } 120 | if (socket) { 121 | socket.send(JSON.stringify(interProcessObject)); 122 | } 123 | }; 124 | 125 | function exitHandler (options, err) { 126 | if (options.cleanup) { 127 | if (verbose) console.log('clean'); 128 | /** Do additional clean up here */ 129 | } 130 | if (err) console.log(err.stack); 131 | if (options.exit) { 132 | if (verbose) console.log('exit'); 133 | ourBoard 134 | .disconnect() 135 | .then(() => { 136 | process.exit(0); 137 | }) 138 | .catch(err => { 139 | console.log(err); 140 | process.exit(0); 141 | }); 142 | } 143 | } 144 | 145 | if (process.platform === 'win32') { 146 | const rl = require('readline').createInterface({ 147 | input: process.stdin, 148 | output: process.stdout 149 | }); 150 | 151 | rl.on('SIGINT', function () { 152 | process.emit('SIGINT'); 153 | }); 154 | } 155 | 156 | // do something when app is closing 157 | process.on( 158 | 'exit', 159 | exitHandler.bind(null, { 160 | cleanup: true 161 | }) 162 | ); 163 | 164 | // catches ctrl+c event 165 | process.on( 166 | 'SIGINT', 167 | exitHandler.bind(null, { 168 | exit: true 169 | }) 170 | ); 171 | 172 | // catches uncaught exceptions 173 | process.on( 174 | 'uncaughtException', 175 | exitHandler.bind(null, { 176 | exit: true 177 | }) 178 | ); 179 | -------------------------------------------------------------------------------- /examples/python/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * This is an example from the readme.md 4 | * On windows you should run with PowerShell not git bash. 5 | * Install 6 | * [nodejs](https://nodejs.org/en/) 7 | * 8 | * To run: 9 | * change directory to this file `cd examples/debug` 10 | * do `npm install` 11 | * then `npm start` 12 | */ 13 | const portPub = 'tcp://127.0.0.1:3004'; 14 | const zmq = require('zeromq'); 15 | const socket = zmq.socket('pair'); 16 | const simulate = true; // Sends synthetic data 17 | const debug = false; // Pretty print any bytes in and out... it's amazing... 18 | const verbose = true; // Adds verbosity to functions 19 | 20 | const Cyton = require('../..'); 21 | let ourBoard = new Cyton({ 22 | simulate: simulate, // Uncomment to see how it works with simulator! 23 | simulatorFirmwareVersion: 'v2', 24 | debug: debug, 25 | verbose: verbose 26 | }); 27 | 28 | let timeSyncPossible = false; 29 | let resyncPeriodMin = 1; 30 | let secondsInMinute = 60; 31 | let resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; 32 | 33 | ourBoard.autoFindOpenBCIBoard().then(portName => { 34 | if (portName) { 35 | /** 36 | * Connect to the board with portName 37 | * i.e. ourBoard.connect(portName)..... 38 | */ 39 | // Call to connect 40 | ourBoard.connect(portName) 41 | .then(() => { 42 | // 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. 43 | timeSyncPossible = ourBoard.usingAtLeastVersionTwoFirmware(); 44 | console.log(`timeSyncPossible: ${timeSyncPossible}`); 45 | 46 | if (timeSyncPossible) { 47 | ourBoard.streamStart() 48 | .catch(err => { 49 | console.log(`stream start: ${err}`); 50 | }); 51 | } else { 52 | console.log('not able to time sync'); 53 | } 54 | }) 55 | .catch(err => { 56 | console.log(`connect: ${err}`); 57 | }); 58 | } else { 59 | /** Unable to auto find OpenBCI board */ 60 | console.log('Unable to auto find OpenBCI board'); 61 | } 62 | }); 63 | 64 | const sampleFunc = sample => { 65 | console.log(JSON.stringify(sample)); 66 | 67 | if (sample._count % resyncPeriod === 0) { 68 | ourBoard.syncClocksFull() 69 | .then(syncObj => { 70 | // Sync was successful 71 | if (syncObj.valid) { 72 | // Log the object to check it out! 73 | console.log(`timeOffset`, syncObj.timeOffsetMaster); 74 | } else { 75 | // Retry it 76 | console.log(`Was not able to sync... retry!`); 77 | } 78 | }); 79 | } 80 | 81 | if (sample.timestamp) { // true after the first successful sync 82 | if (sample.timestamp < 10 * 60 * 60 * 1000) { // Less than 10 hours 83 | console.log(`Bad time sync ${sample.timestamp}`); 84 | } else { 85 | sendToPython({ 86 | action: 'process', 87 | command: 'sample', 88 | message: sample 89 | }); 90 | } 91 | } 92 | }; 93 | 94 | // Subscribe to your functions 95 | ourBoard.on('sample', sampleFunc); 96 | 97 | // ZMQ fun 98 | 99 | socket.bind(portPub, function (err) { 100 | if (err) throw err; 101 | console.log(`bound to ${portPub}`); 102 | }); 103 | 104 | /** 105 | * Used to send a message to the Python process. 106 | * @param {Object} interProcessObject The standard inter-process object. 107 | * @return {None} 108 | */ 109 | var sendToPython = (interProcessObject, verbose) => { 110 | if (verbose) { 111 | console.log(`<- out ${JSON.stringify(interProcessObject)}`); 112 | } 113 | if (socket) { 114 | socket.send(JSON.stringify(interProcessObject)); 115 | } 116 | }; 117 | 118 | var receiveFromPython = (rawData) => { 119 | try { 120 | let body = JSON.parse(rawData); // five because `resp ` 121 | processInterfaceObject(body); 122 | } catch (err) { 123 | console.log('in -> ' + 'bad json'); 124 | } 125 | }; 126 | 127 | socket.on('message', receiveFromPython); 128 | 129 | const sendStatus = () => { 130 | sendToPython({ 'action': 'active', 'message': 'ready', 'command': 'status' }, true); 131 | }; 132 | 133 | sendStatus(); 134 | 135 | /** 136 | * Process an incoming message 137 | * @param {String} body A stringify JSON object that shall be parsed. 138 | * @return {None} 139 | */ 140 | const processInterfaceObject = (body) => { 141 | switch (body.command) { 142 | case 'status': 143 | processStatus(body); 144 | break; 145 | default: 146 | unrecognizedCommand(body); 147 | break; 148 | } 149 | }; 150 | 151 | /** 152 | * Used to process a status related command from TCP IPC. 153 | * @param {Object} body 154 | * @return {None} 155 | */ 156 | const processStatus = (body) => { 157 | switch (body.action) { 158 | case 'started': 159 | console.log(`python started @ ${body.message}ms`); 160 | break; 161 | case 'alive': 162 | console.log(`python duplex communication test completed @ ${body.message}ms`); 163 | break; 164 | default: 165 | unrecognizedCommand(body); 166 | break; 167 | } 168 | }; 169 | 170 | function unrecognizedCommand (body) { 171 | console.log(`unrecognizedCommand ${body}`); 172 | } 173 | 174 | function exitHandler (options, err) { 175 | if (options.cleanup) { 176 | if (verbose) console.log('clean'); 177 | /** Do additional clean up here */ 178 | } 179 | if (err) console.log(err.stack); 180 | if (options.exit) { 181 | if (verbose) console.log('exit'); 182 | ourBoard.disconnect() 183 | .then(() => { 184 | process.exit(0); 185 | }) 186 | .catch((err) => { 187 | console.log(err); 188 | process.exit(0); 189 | }); 190 | } 191 | } 192 | 193 | if (process.platform === 'win32') { 194 | const rl = require('readline').createInterface({ 195 | input: process.stdin, 196 | output: process.stdout 197 | }); 198 | 199 | rl.on('SIGINT', function () { 200 | process.emit('SIGINT'); 201 | }); 202 | } 203 | 204 | // do something when app is closing 205 | process.on('exit', exitHandler.bind(null, { 206 | cleanup: true 207 | })); 208 | 209 | // catches ctrl+c event 210 | process.on('SIGINT', exitHandler.bind(null, { 211 | exit: true 212 | })); 213 | 214 | // catches uncaught exceptions 215 | process.on('uncaughtException', exitHandler.bind(null, { 216 | exit: true 217 | })); 218 | -------------------------------------------------------------------------------- /openBCISimulator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EventEmitter = require('events').EventEmitter; 3 | const util = require('util'); 4 | const stream = require('stream'); 5 | 6 | const obciUtilities = require('@openbci/utilities/dist/utilities'); 7 | const k = require('@openbci/utilities/dist/constants'); 8 | const now = require('performance-now'); 9 | const Buffer = require('safe-buffer').Buffer; 10 | 11 | const _options = { 12 | accel: true, 13 | alpha: true, 14 | boardFailure: false, 15 | daisy: false, 16 | daisyCanBeAttached: true, 17 | drift: 0, 18 | firmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2, k.OBCIFirmwareV3], 19 | fragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne], 20 | latencyTime: 16, 21 | bufferSize: 4096, 22 | lineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone], 23 | sampleRate: 250, 24 | serialPortFailure: false, 25 | verbose: false 26 | }; 27 | 28 | function Simulator (portName, options) { 29 | if (!(this instanceof Simulator)) { 30 | return new Simulator(portName, options); 31 | } 32 | options = options || {}; 33 | let opts = {}; 34 | 35 | stream.Stream.call(this); 36 | 37 | /** Configuring Options */ 38 | let o; 39 | for (o in _options) { 40 | let userValue = options[o]; 41 | delete options[o]; 42 | 43 | if (typeof _options[o] === 'object') { 44 | // an array specifying a list of choices 45 | // if the choice is not in the list, the first one is defaulted to 46 | 47 | if (_options[o].indexOf(userValue) !== -1) { 48 | opts[o] = userValue; 49 | } else { 50 | opts[o] = _options[o][0]; 51 | } 52 | } else { 53 | // anything else takes the user value if provided, otherwise is a default 54 | 55 | if (userValue !== undefined) { 56 | opts[o] = userValue; 57 | } else { 58 | opts[o] = _options[o]; 59 | } 60 | } 61 | } 62 | 63 | for (o in options) throw new Error('"' + o + '" is not a valid option'); 64 | 65 | this.options = opts; 66 | 67 | // Bools 68 | this.isOpen = false; 69 | this.sd = { 70 | active: false, 71 | startTime: 0 72 | }; 73 | this.streaming = false; 74 | this.synced = false; 75 | this.sendSyncSetPacket = false; 76 | // Buffers 77 | this.outputBuffer = new Buffer(this.options.bufferSize); 78 | this.outputBuffered = 0; 79 | // Numbers 80 | this.channelNumber = 1; 81 | this.hostChannelNumber = this.channelNumber; 82 | this.pollTime = 80; 83 | this.sampleNumber = -1; // So the first sample is 0 84 | // Objects 85 | this.sampleGenerator = obciUtilities.randomSample(k.OBCINumberOfChannelsDefault, this.options.sampleRate, this.options.alpha, this.options.lineNoise); 86 | this.time = { 87 | current: 0, 88 | start: now(), 89 | loop: null 90 | }; 91 | // Strings 92 | this.portName = portName || k.OBCISimulatorPortName; 93 | 94 | // Call 'open' 95 | if (this.options.verbose) console.log(`Port name: ${portName}`); 96 | setTimeout(() => { 97 | this.isOpen = true; 98 | this.emit('open'); 99 | }, 200); 100 | } 101 | 102 | // This allows us to use the emitter class freely outside of the module 103 | util.inherits(Simulator, EventEmitter); 104 | 105 | Simulator.prototype.flush = function (callback) { 106 | this.outputBuffered = 0; 107 | 108 | clearTimeout(this.outputLoopHandle); 109 | this.outputLoopHandle = null; 110 | 111 | if (callback) callback(); 112 | }; 113 | 114 | // output only size bytes of the output buffer 115 | Simulator.prototype._partialDrain = function (size) { 116 | if (!this.isOpen) throw new Error('not connected'); 117 | 118 | if (size > this.outputBuffered) size = this.outputBuffered; 119 | 120 | // buffer is copied because presently openBCICyton.js reuses it 121 | let outBuffer = new Buffer(this.outputBuffer.slice(0, size)); 122 | 123 | this.outputBuffer.copy(this.outputBuffer, 0, size, this.outputBuffered); 124 | this.outputBuffered -= size; 125 | 126 | this.emit('data', outBuffer); 127 | }; 128 | 129 | // queue some data for output and send it out depending on options.fragmentation 130 | Simulator.prototype._output = function (dataBuffer) { 131 | // drain full buffers until there is no overflow 132 | while (this.outputBuffered + dataBuffer.length > this.outputBuffer.length) { 133 | let len = dataBuffer.copy(this.outputBuffer, this.outputBuffered); 134 | dataBuffer = dataBuffer.slice(len); 135 | this.outputBuffered += len; 136 | 137 | this._partialDrain(this.outputBuffered); 138 | this.flush(); 139 | } 140 | 141 | dataBuffer.copy(this.outputBuffer, this.outputBuffered); 142 | this.outputBuffered += dataBuffer.length; 143 | 144 | if (!this.outputLoopHandle) { 145 | let latencyTime = this.options.latencyTime; 146 | if (this.options.fragmentation === k.OBCISimulatorFragmentationOneByOne || 147 | this.options.fragmentation === k.OBCISimulatorFragmentationNone) { 148 | // no need to wait for latencyTime 149 | // note that this is the only difference between 'none' and 'fullBuffers' 150 | latencyTime = 0; 151 | } 152 | let outputLoop = () => { 153 | let size; 154 | switch (this.options.fragmentation) { 155 | case k.OBCISimulatorFragmentationRandom: 156 | if (Math.random() < 0.5) { 157 | // randomly picked to send out a fragment 158 | size = Math.ceil(Math.random() * Math.max(this.outputBuffered, 62)); 159 | break; 160 | } // else, randomly picked to send a complete buffer in next block 161 | /* falls through */ 162 | case k.OBCISimulatorFragmentationFullBuffers: 163 | case k.OBCISimulatorFragmentationNone: 164 | case false: 165 | size = this.outputBuffered; 166 | break; 167 | case k.OBCISimulatorFragmentationOneByOne: 168 | size = 1; 169 | break; 170 | } 171 | this._partialDrain(size); 172 | if (this.outputBuffered) { 173 | this.outputLoopHandle = setTimeout(outputLoop, latencyTime); 174 | } else { 175 | this.outputLoopHandle = null; 176 | } 177 | }; 178 | if (latencyTime === 0) { 179 | outputLoop(); 180 | } else { 181 | this.outputLoopHandle = setTimeout(outputLoop, latencyTime); 182 | } 183 | } 184 | }; 185 | 186 | Simulator.prototype.write = function (data, callback) { 187 | if (!this.isOpen) { 188 | /* istanbul ignore else */ 189 | if (callback) callback(Error('Not connected')); 190 | else throw new Error('Not connected!'); 191 | return; 192 | } 193 | 194 | // TODO: this function assumes a type of Buffer for radio, and a type of String otherwise 195 | // FIX THIS it makes it unusable outside the api code 196 | switch (data[0]) { 197 | case k.OBCIRadioKey: 198 | this._processPrivateRadioMessage(data); 199 | break; 200 | case k.OBCIStreamStart: 201 | if (!this.stream) this._startStream(); 202 | this.streaming = true; 203 | break; 204 | case k.OBCIStreamStop: 205 | if (this.stream) clearInterval(this.stream); // Stops the stream 206 | this.streaming = false; 207 | break; 208 | case k.OBCIMiscSoftReset: 209 | if (this.stream) clearInterval(this.stream); 210 | this.streaming = false; 211 | this._output(new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x3E ${this.options.daisy ? `On Daisy ADS1299 Device ID: 0x3E\n` : ``} LIS3DH Device ID: 0x38422 ${this.options.firmwareVersion === k.OBCIFirmwareV2 ? `Firmware: v2.0.0\n` : ``}$$$`)); 212 | break; 213 | case k.OBCISDLogForHour1: 214 | case k.OBCISDLogForHour2: 215 | case k.OBCISDLogForHour4: 216 | case k.OBCISDLogForHour12: 217 | case k.OBCISDLogForHour24: 218 | case k.OBCISDLogForMin5: 219 | case k.OBCISDLogForMin15: 220 | case k.OBCISDLogForMin30: 221 | case k.OBCISDLogForSec14: 222 | // If we are not streaming, then do verbose output 223 | if (!this.streaming) { 224 | this._output(new Buffer('Wiring is correct and a card is present.\nCorresponding SD file OBCI_69.TXT\n$$$')); 225 | } 226 | this.sd.active = true; 227 | this.sd.startTime = now(); 228 | break; 229 | case k.OBCISDLogStop: 230 | if (!this.streaming) { 231 | if (this.SDLogActive) { 232 | this._output(new Buffer(`Total Elapsed Time: ${now() - this.sd.startTime} ms`)); 233 | this._output(new Buffer(`Max write time: ${Math.random() * 500} us`)); 234 | this._output(new Buffer(`Min write time: ${Math.random() * 200} us`)); 235 | this._output(new Buffer(`Overruns: 0`)); 236 | this._printEOT(); 237 | } else { 238 | this._output(new Buffer('No open file to close\n')); 239 | this._printEOT(); 240 | } 241 | } 242 | this.SDLogActive = false; 243 | break; 244 | case k.OBCISyncTimeSet: 245 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 246 | this.synced = true; 247 | setTimeout(() => { 248 | this._output(new Buffer(k.OBCISyncTimeSent)); 249 | this._syncUp(); 250 | }, 10); 251 | } 252 | break; 253 | case k.OBCIChannelMaxNumber8: 254 | if (this.options.daisy) { 255 | this.options.daisy = false; 256 | this._output(new Buffer(k.OBCIChannelMaxNumber8SuccessDaisyRemoved)); 257 | this._printEOT(); 258 | } else { 259 | this._printEOT(); 260 | } 261 | break; 262 | case k.OBCIChannelMaxNumber16: 263 | if (this.options.daisy) { 264 | this._output(new Buffer(k.OBCIChannelMaxNumber16DaisyAlreadyAttached)); 265 | this._printEOT(); 266 | } else { 267 | if (this.options.daisyCanBeAttached) { 268 | this.options.daisy = true; 269 | this._output(new Buffer(k.OBCIChannelMaxNumber16DaisyAttached)); 270 | this._printEOT(); 271 | } else { 272 | this._output(new Buffer(k.OBCIChannelMaxNumber16NoDaisyAttached)); 273 | this._printEOT(); 274 | } 275 | } 276 | break; 277 | case k.OBCIMiscQueryRegisterSettings: 278 | let outputString = k.OBCIRegisterQueryCyton; 279 | if (this.options.daisy) { 280 | outputString += k.OBCIRegisterQueryCytonDaisy; 281 | } 282 | if (this.options.firmwareVersion === k.OBCIFirmwareV3) { 283 | outputString += k.OBCIRegisterQueryAccelerometerFirmwareV3; 284 | } else { 285 | outputString += k.OBCIRegisterQueryAccelerometerFirmwareV1; 286 | } 287 | this._output(Buffer.from(outputString)); 288 | this._printEOT(); 289 | break; 290 | default: 291 | break; 292 | } 293 | 294 | /** Handle Callback */ 295 | if (callback) { 296 | callback(null, 'Success!'); 297 | } 298 | }; 299 | 300 | Simulator.prototype.drain = function (callback) { 301 | if (callback) callback(); 302 | }; 303 | 304 | Simulator.prototype.close = function (callback) { 305 | if (this.isOpen) { 306 | this.flush(); 307 | 308 | if (this.stream) clearInterval(this.stream); 309 | 310 | this.isOpen = false; 311 | this.emit('close'); 312 | if (callback) callback(); 313 | } else { 314 | if (callback) callback(Error('Not connected')); 315 | } 316 | }; 317 | 318 | Simulator.prototype._startStream = function () { 319 | let intervalInMS = 1000 / this.options.sampleRate; 320 | 321 | if (intervalInMS < 2) intervalInMS = 2; 322 | 323 | let getNewPacket = sampNumber => { 324 | if (this.options.accel) { 325 | if (this.synced) { 326 | if (this.sendSyncSetPacket) { 327 | this.sendSyncSetPacket = false; 328 | return obciUtilities.convertSampleToPacketAccelTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0)); 329 | } else { 330 | return obciUtilities.convertSampleToPacketAccelTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0)); 331 | } 332 | } else { 333 | return obciUtilities.convertSampleToPacketStandard(this.sampleGenerator(sampNumber)); 334 | } 335 | } else { 336 | if (this.synced) { 337 | if (this.sendSyncSetPacket) { 338 | this.sendSyncSetPacket = false; 339 | return obciUtilities.convertSampleToPacketRawAuxTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); 340 | } else { 341 | return obciUtilities.convertSampleToPacketRawAuxTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0])); 342 | } 343 | } else { 344 | return obciUtilities.convertSampleToPacketRawAux(this.sampleGenerator(sampNumber), new Buffer([0, 0, 0, 0, 0, 0])); 345 | } 346 | } 347 | }; 348 | 349 | this.stream = setInterval(() => { 350 | this._output(getNewPacket(this.sampleNumber)); 351 | this.sampleNumber++; 352 | }, intervalInMS); 353 | }; 354 | 355 | Simulator.prototype._syncUp = function () { 356 | setTimeout(() => { 357 | this.sendSyncSetPacket = true; 358 | }, 12); // 3 packets later 359 | }; 360 | 361 | Simulator.prototype._printEOT = function () { 362 | this._output(new Buffer('$$$')); 363 | }; 364 | 365 | Simulator.prototype._printFailure = function () { 366 | this._output(new Buffer('Failure: ')); 367 | }; 368 | 369 | Simulator.prototype._printSuccess = function () { 370 | this._output(new Buffer('Success: ')); 371 | }; 372 | 373 | Simulator.prototype._printValidatedCommsTimeout = function () { 374 | this._printFailure(); 375 | this._output(new Buffer('Communications timeout - Device failed to poll Host')); 376 | this._printEOT(); 377 | }; 378 | 379 | Simulator.prototype._processPrivateRadioMessage = function (dataBuffer) { 380 | switch (dataBuffer[1]) { 381 | case k.OBCIRadioCmdChannelGet: 382 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 383 | if (!this.options.boardFailure) { 384 | this._printSuccess(); 385 | this._output(new Buffer(`Host and Device on Channel Number ${this.channelNumber}`)); 386 | this._output(new Buffer([this.channelNumber])); 387 | this._printEOT(); 388 | } else if (!this.serialPortFailure) { 389 | this._printFailure(); 390 | this._output(new Buffer(`Host on Channel Number ${this.channelNumber}`)); 391 | this._output(new Buffer([this.channelNumber])); 392 | this._printEOT(); 393 | } 394 | } 395 | break; 396 | case k.OBCIRadioCmdChannelSet: 397 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 398 | if (!this.options.boardFailure) { 399 | if (dataBuffer[2] <= k.OBCIRadioChannelMax) { 400 | this.channelNumber = dataBuffer[2]; 401 | this.hostChannelNumber = this.channelNumber; 402 | this._printSuccess(); 403 | this._output(new Buffer(`Channel Number ${this.channelNumber}`)); 404 | this._output(new Buffer([this.channelNumber])); 405 | this._printEOT(); 406 | } else { 407 | this._printFailure(); 408 | this._output(new Buffer('Verify channel number is less than 25')); 409 | this._printEOT(); 410 | } 411 | } else if (!this.serialPortFailure) { 412 | this._printValidatedCommsTimeout(); 413 | } 414 | } 415 | break; 416 | case k.OBCIRadioCmdChannelSetOverride: 417 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 418 | if (dataBuffer[2] <= k.OBCIRadioChannelMax) { 419 | if (dataBuffer[2] === this.channelNumber) { 420 | this.options.boardFailure = false; 421 | } else { 422 | this.options.boardFailure = true; 423 | } 424 | this.hostChannelNumber = dataBuffer[2]; 425 | this._printSuccess(); 426 | this._output(new Buffer(`Host override - Channel Number ${this.hostChannelNumber}`)); 427 | this._output(new Buffer([this.hostChannelNumber])); 428 | this._printEOT(); 429 | } else { 430 | this._printFailure(); 431 | this._output(new Buffer('Verify channel number is less than 25')); 432 | this._printEOT(); 433 | } 434 | } 435 | break; 436 | case k.OBCIRadioCmdPollTimeGet: 437 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 438 | if (!this.options.boardFailure) { 439 | this._printSuccess(); 440 | this._output(new Buffer(`Poll Time ${this.pollTime}`)); 441 | this._output(new Buffer([this.pollTime])); 442 | this._printEOT(); 443 | } else { 444 | this._printValidatedCommsTimeout(); 445 | } 446 | } 447 | break; 448 | case k.OBCIRadioCmdPollTimeSet: 449 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 450 | if (!this.options.boardFailure) { 451 | this.pollTime = dataBuffer[2]; 452 | this._printSuccess(); 453 | this._output(new Buffer(`Poll Time ${this.pollTime}`)); 454 | this._output(new Buffer([this.pollTime])); 455 | this._printEOT(); 456 | } else { 457 | this._printValidatedCommsTimeout(); 458 | } 459 | } 460 | break; 461 | case k.OBCIRadioCmdBaudRateSetDefault: 462 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 463 | this._printSuccess(); 464 | this._output(new Buffer('Switch your baud rate to 115200')); 465 | this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this 466 | } 467 | break; 468 | case k.OBCIRadioCmdBaudRateSetFast: 469 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 470 | this._printSuccess(); 471 | this._output(new Buffer('Switch your baud rate to 230400')); 472 | this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this 473 | } 474 | break; 475 | case k.OBCIRadioCmdSystemStatus: 476 | if (this.options.firmwareVersion === k.OBCIFirmwareV2) { 477 | if (!this.options.boardFailure) { 478 | this._printSuccess(); 479 | this._output(new Buffer('System is Up')); 480 | this._printEOT(); 481 | } else { 482 | this._printFailure(); 483 | this._output(new Buffer('System is Down')); 484 | this._printEOT(); 485 | } 486 | } 487 | break; 488 | default: 489 | break; 490 | } 491 | }; 492 | 493 | module.exports = Simulator; 494 | -------------------------------------------------------------------------------- /test/openBCICyton-Impedance-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const bluebirdChecks = require('./bluebirdChecks'); 3 | const chai = require('chai'); 4 | const should = chai.should(); // eslint-disable-line no-unused-vars 5 | const OpenBCICyton = require('../openBCICyton'); 6 | const openBCIUtilities = require('@openbci/utilities/dist/utilities'); 7 | const k = require('@openbci/utilities/dist/constants'); 8 | 9 | const chaiAsPromised = require('chai-as-promised'); 10 | const sinonChai = require('sinon-chai'); 11 | chai.use(chaiAsPromised); 12 | chai.use(sinonChai); 13 | 14 | describe('#impedanceTesting', function () { 15 | let ourBoard; 16 | this.timeout(20000); 17 | 18 | before(function (done) { 19 | ourBoard = new OpenBCICyton({ 20 | verbose: true, 21 | simulatorFragmentation: k.OBCISimulatorFragmentationRandom 22 | }); 23 | var useSim = () => { 24 | return ourBoard.simulatorEnable().then(() => { 25 | return ourBoard.connect(k.OBCISimulatorPortName); 26 | }); 27 | }; 28 | ourBoard.autoFindOpenBCIBoard() 29 | .then(portName => { 30 | return ourBoard.connect(portName); 31 | }) 32 | .catch(() => { 33 | return useSim(); 34 | }) 35 | .then(() => { 36 | console.log('connected'); 37 | }) 38 | .catch(err => { 39 | console.log('Error: ' + err); 40 | }); 41 | 42 | ourBoard.once('ready', () => { 43 | ourBoard.streamStart() 44 | .then(() => { 45 | setTimeout(() => { 46 | done(); 47 | }, 100); // give some time for the stream command to be sent 48 | }) 49 | .catch(err => { 50 | console.log(err); 51 | done(err); 52 | }); 53 | }); 54 | }); 55 | after(done => { 56 | if (ourBoard.isConnected()) { 57 | ourBoard.disconnect() 58 | .then(() => { 59 | done(); 60 | }) 61 | .catch(err => { 62 | done(err); 63 | }); 64 | } else { 65 | done(); 66 | } 67 | }); 68 | after(() => bluebirdChecks.noPendingPromises()); 69 | 70 | describe('#impedanceTestAllChannels', function () { 71 | var impedanceArray = []; 72 | 73 | before(function (done) { 74 | ourBoard.once('impedanceArray', arr => { 75 | impedanceArray = arr; 76 | console.log(impedanceArray); 77 | done(); 78 | }); 79 | ourBoard.impedanceTestAllChannels(); 80 | }); 81 | describe('#channel1', function () { 82 | it('has valid channel number', function () { 83 | impedanceArray[0].channel.should.be.equal(1); 84 | }); 85 | describe('#inputP', function () { 86 | it('got raw impedance value', function () { 87 | impedanceArray[0].P.should.have.property('raw').above(-1); 88 | }); 89 | it("text is not 'init'", function () { 90 | impedanceArray[0].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 91 | }); 92 | }); 93 | describe('#inputN', function () { 94 | it('got raw impedance value', function () { 95 | impedanceArray[0].N.should.have.property('raw').above(-1); 96 | }); 97 | it("text is not 'init'", function () { 98 | impedanceArray[0].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 99 | }); 100 | }); 101 | }); 102 | describe('#channel2', function () { 103 | it('has valid channel number', function () { 104 | impedanceArray[1].channel.should.be.equal(2); 105 | }); 106 | describe('#inputP', function () { 107 | it('got raw impedance value', function () { 108 | impedanceArray[1].P.should.have.property('raw').above(-1); 109 | }); 110 | it("text is not 'init'", function () { 111 | impedanceArray[1].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 112 | }); 113 | }); 114 | describe('#inputN', function () { 115 | it('got raw impedance value', function () { 116 | impedanceArray[1].N.should.have.property('raw').above(-1); 117 | }); 118 | it("text is not 'init'", function () { 119 | impedanceArray[1].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 120 | }); 121 | }); 122 | }); 123 | describe('#channel3', function () { 124 | it('has valid channel number', function () { 125 | impedanceArray[2].channel.should.be.equal(3); 126 | }); 127 | describe('#inputP', function () { 128 | it('got raw impedance value', function () { 129 | impedanceArray[2].P.should.have.property('raw').above(-1); 130 | }); 131 | it("text is not 'init'", function () { 132 | impedanceArray[2].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 133 | }); 134 | }); 135 | describe('#inputN', function () { 136 | it('got raw impedance value', function () { 137 | impedanceArray[2].N.should.have.property('raw').above(-1); 138 | }); 139 | it("text is not 'init'", function () { 140 | impedanceArray[2].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 141 | }); 142 | }); 143 | }); 144 | describe('#channel4', function () { 145 | it('has valid channel number', function () { 146 | impedanceArray[3].channel.should.be.equal(4); 147 | }); 148 | describe('#inputP', function () { 149 | it('got raw impedance value', function () { 150 | impedanceArray[3].P.should.have.property('raw').above(-1); 151 | }); 152 | it("text is not 'init'", function () { 153 | impedanceArray[3].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 154 | }); 155 | }); 156 | describe('#inputN', function () { 157 | it('got raw impedance value', function () { 158 | impedanceArray[3].N.should.have.property('raw').above(-1); 159 | }); 160 | it("text is not 'init'", function () { 161 | impedanceArray[3].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 162 | }); 163 | }); 164 | }); 165 | describe('#channel5', function () { 166 | it('has valid channel number', function () { 167 | impedanceArray[4].channel.should.be.equal(5); 168 | }); 169 | describe('#inputP', function () { 170 | it('got raw impedance value', function () { 171 | impedanceArray[4].P.should.have.property('raw').above(-1); 172 | }); 173 | it("text is not 'init'", function () { 174 | impedanceArray[4].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 175 | }); 176 | }); 177 | describe('#inputN', function () { 178 | it('got raw impedance value', function () { 179 | impedanceArray[4].N.should.have.property('raw').above(-1); 180 | }); 181 | it("text is not 'init'", function () { 182 | impedanceArray[4].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 183 | }); 184 | }); 185 | }); 186 | describe('#channel6', function () { 187 | it('has valid channel number', function () { 188 | impedanceArray[5].channel.should.be.equal(6); 189 | }); 190 | describe('#inputP', function () { 191 | it('got raw impedance value', function () { 192 | impedanceArray[5].P.should.have.property('raw').above(-1); 193 | }); 194 | it("text is not 'init'", function () { 195 | impedanceArray[5].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 196 | }); 197 | }); 198 | describe('#inputN', function () { 199 | it('got raw impedance value', function () { 200 | impedanceArray[5].N.should.have.property('raw').above(-1); 201 | }); 202 | it("text is not 'init'", function () { 203 | impedanceArray[5].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 204 | }); 205 | }); 206 | }); 207 | describe('#channel7', function () { 208 | it('has valid channel number', function () { 209 | impedanceArray[6].channel.should.be.equal(7); 210 | }); 211 | describe('#inputP', function () { 212 | it('got raw impedance value', function () { 213 | impedanceArray[6].P.should.have.property('raw').above(-1); 214 | }); 215 | it("text is not 'init'", function () { 216 | impedanceArray[6].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 217 | }); 218 | }); 219 | describe('#inputN', function () { 220 | it('got raw impedance value', function () { 221 | impedanceArray[6].N.should.have.property('raw').above(-1); 222 | }); 223 | it("text is not 'init'", function () { 224 | impedanceArray[6].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 225 | }); 226 | }); 227 | }); 228 | describe('#channel8', function () { 229 | it('has valid channel number', function () { 230 | impedanceArray[7].channel.should.be.equal(8); 231 | }); 232 | describe('#inputP', function () { 233 | it('got raw impedance value', function () { 234 | impedanceArray[7].P.should.have.property('raw').above(-1); 235 | }); 236 | it("text is not 'init'", function () { 237 | impedanceArray[7].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 238 | }); 239 | }); 240 | describe('#inputN', function () { 241 | it('got raw impedance value', function () { 242 | impedanceArray[7].N.should.have.property('raw').above(-1); 243 | }); 244 | it("text is not 'init'", function () { 245 | impedanceArray[7].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 246 | }); 247 | }); 248 | }); 249 | }); 250 | describe('#impedanceTestChannelsRejects', function () { 251 | it('rejects when it does not get an array', function (done) { 252 | ourBoard.impedanceTestChannels('taco').should.be.rejected.and.notify(done); 253 | }); 254 | 255 | it('rejects when it array length does not match number of channels', function (done) { 256 | ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', 'p', 'b']).should.be.rejected.and.notify(done); 257 | }); 258 | }); 259 | describe('#impedanceTestChannels', function () { 260 | var impedanceArray = []; 261 | 262 | before(function (done) { 263 | ourBoard.once('impedanceArray', arr => { 264 | impedanceArray = arr; 265 | done(); 266 | }); 267 | ourBoard.impedanceArray[0] = openBCIUtilities.impedanceObject(1); 268 | ourBoard.impedanceTestChannels(['-', 'N', 'n', 'p', 'P', 'p', 'b', 'B']).catch(err => done(err)); 269 | }); 270 | describe('#channel1', function () { 271 | it('has valid channel number', function () { 272 | impedanceArray[0].channel.should.be.equal(1); 273 | }); 274 | describe('#inputP', function () { 275 | it('got raw impedance value', function () { 276 | impedanceArray[0].P.should.have.property('raw').equal(-1); 277 | }); 278 | it("text is not 'init'", function () { 279 | impedanceArray[0].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); 280 | }); 281 | }); 282 | describe('#inputN', function () { 283 | it('got raw impedance value', function () { 284 | impedanceArray[0].N.should.have.property('raw').equal(-1); 285 | }); 286 | it("text is not 'init'", function () { 287 | impedanceArray[0].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); 288 | }); 289 | }); 290 | }); 291 | describe('#channel2', function () { 292 | it('has valid channel number', function () { 293 | impedanceArray[1].channel.should.be.equal(2); 294 | }); 295 | describe('#inputP', function () { 296 | it('got raw impedance value', function () { 297 | impedanceArray[1].P.should.have.property('raw').equal(-1); 298 | }); 299 | it("text is not 'init'", function () { 300 | impedanceArray[1].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); 301 | }); 302 | }); 303 | describe('#inputN', function () { 304 | it('got raw impedance value', function () { 305 | impedanceArray[1].N.should.have.property('raw').above(-1); 306 | }); 307 | it("text is not 'init'", function () { 308 | impedanceArray[1].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 309 | }); 310 | }); 311 | }); 312 | describe('#channel3', function () { 313 | it('has valid channel number', function () { 314 | impedanceArray[2].channel.should.be.equal(3); 315 | }); 316 | describe('#inputP', function () { 317 | it('got raw impedance value', function () { 318 | impedanceArray[2].P.should.have.property('raw').equal(-1); 319 | }); 320 | it("text is not 'init'", function () { 321 | impedanceArray[2].P.should.have.property('text').equal(k.OBCIImpedanceTextInit); 322 | }); 323 | }); 324 | describe('#inputN', function () { 325 | it('got raw impedance value', function () { 326 | impedanceArray[2].N.should.have.property('raw').above(-1); 327 | }); 328 | it("text is not 'init'", function () { 329 | impedanceArray[2].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 330 | }); 331 | }); 332 | }); 333 | describe('#channel4', function () { 334 | it('has valid channel number', function () { 335 | impedanceArray[3].channel.should.be.equal(4); 336 | }); 337 | describe('#inputP', function () { 338 | it('got raw impedance value', function () { 339 | impedanceArray[3].P.should.have.property('raw').above(-1); 340 | }); 341 | it("text is not 'init'", function () { 342 | impedanceArray[3].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 343 | }); 344 | }); 345 | describe('#inputN', function () { 346 | it('got raw impedance value', function () { 347 | impedanceArray[3].N.should.have.property('raw').equal(-1); 348 | }); 349 | it("text is not 'init'", function () { 350 | impedanceArray[3].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); 351 | }); 352 | }); 353 | }); 354 | describe('#channel5', function () { 355 | it('has valid channel number', function () { 356 | impedanceArray[4].channel.should.be.equal(5); 357 | }); 358 | describe('#inputP', function () { 359 | it('got raw impedance value', function () { 360 | impedanceArray[4].P.should.have.property('raw').above(-1); 361 | }); 362 | it("text is not 'init'", function () { 363 | impedanceArray[4].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 364 | }); 365 | }); 366 | describe('#inputN', function () { 367 | it('got raw impedance value', function () { 368 | impedanceArray[4].N.should.have.property('raw').equal(-1); 369 | }); 370 | it("text is not 'init'", function () { 371 | impedanceArray[4].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); 372 | }); 373 | }); 374 | }); 375 | describe('#channel6', function () { 376 | it('has valid channel number', function () { 377 | impedanceArray[5].channel.should.be.equal(6); 378 | }); 379 | describe('#inputP', function () { 380 | it('got raw impedance value', function () { 381 | impedanceArray[5].P.should.have.property('raw').above(-1); 382 | }); 383 | it("text is not 'init'", function () { 384 | impedanceArray[5].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 385 | }); 386 | }); 387 | describe('#inputN', function () { 388 | it('got raw impedance value', function () { 389 | impedanceArray[5].N.should.have.property('raw').equal(-1); 390 | }); 391 | it("text is not 'init'", function () { 392 | impedanceArray[5].N.should.have.property('text').equal(k.OBCIImpedanceTextInit); 393 | }); 394 | }); 395 | }); 396 | describe('#channel7', function () { 397 | it('has valid channel number', function () { 398 | impedanceArray[6].channel.should.be.equal(7); 399 | }); 400 | describe('#inputP', function () { 401 | it('got raw impedance value', function () { 402 | impedanceArray[6].P.should.have.property('raw').above(-1); 403 | }); 404 | it("text is not 'init'", function () { 405 | impedanceArray[6].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 406 | }); 407 | }); 408 | describe('#inputN', function () { 409 | it('got raw impedance value', function () { 410 | impedanceArray[6].N.should.have.property('raw').above(-1); 411 | }); 412 | it("text is not 'init'", function () { 413 | impedanceArray[6].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 414 | }); 415 | }); 416 | }); 417 | describe('#channel8', function () { 418 | it('has valid channel number', function () { 419 | impedanceArray[7].channel.should.be.equal(8); 420 | }); 421 | describe('#inputP', function () { 422 | it('got raw impedance value', function () { 423 | impedanceArray[7].P.should.have.property('raw').above(-1); 424 | }); 425 | it("text is not 'init'", function () { 426 | impedanceArray[7].P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 427 | }); 428 | }); 429 | describe('#inputN', function () { 430 | it('got raw impedance value', function () { 431 | impedanceArray[7].N.should.have.property('raw').above(-1); 432 | }); 433 | it("text is not 'init'", function () { 434 | impedanceArray[7].N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 435 | }); 436 | }); 437 | }); 438 | }); 439 | describe('#impedanceTestChannel', function () { 440 | var impedanceObject = {}; 441 | 442 | before(function (done) { 443 | ourBoard.impedanceTestChannel(1) 444 | .then(impdObj => { 445 | impedanceObject = impdObj; 446 | done(); 447 | }) 448 | .catch(err => done(err)); 449 | }); 450 | describe('#channel1', function () { 451 | it('has valid channel number', function () { 452 | impedanceObject.channel.should.be.equal(1); 453 | }); 454 | describe('#inputP', function () { 455 | it('got raw impedance value', function () { 456 | impedanceObject.P.should.have.property('raw').above(-1); 457 | }); 458 | it("text is not 'init'", function () { 459 | impedanceObject.P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 460 | }); 461 | }); 462 | describe('#inputN', function () { 463 | it('got raw impedance value', function () { 464 | impedanceObject.N.should.have.property('raw').above(-1); 465 | }); 466 | it("text is not 'init'", function () { 467 | impedanceObject.N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 468 | }); 469 | }); 470 | }); 471 | }); 472 | describe('#impedanceTestChannelInputP', function () { 473 | var impedanceObject = {}; 474 | 475 | before(function (done) { 476 | ourBoard.impedanceTestChannelInputP(1) 477 | .then(impdObj => { 478 | impedanceObject = impdObj; 479 | done(); 480 | }) 481 | .catch(err => done(err)); 482 | }); 483 | describe('#channel1', function () { 484 | it('has valid channel number', function () { 485 | impedanceObject.channel.should.be.equal(1); 486 | }); 487 | describe('#inputP', function () { 488 | it('got raw impedance value', function () { 489 | impedanceObject.P.should.have.property('raw').above(-1); 490 | }); 491 | it("text is not 'init'", function () { 492 | impedanceObject.P.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 493 | }); 494 | }); 495 | describe('#inputN', function () { 496 | it('got raw impedance value', function () { 497 | impedanceObject.N.should.have.property('raw').equal(-1); 498 | }); 499 | it("text is not 'init'", function () { 500 | impedanceObject.N.should.have.property('text').equal(k.OBCIImpedanceTextInit); 501 | }); 502 | }); 503 | }); 504 | }); 505 | describe('#impedanceTestChannelInputN', function () { 506 | var impedanceObject = {}; 507 | // wstream = fs.createWriteStream('hardwareVoltageOutputAll.txt') 508 | 509 | before(function (done) { 510 | console.log('7'); 511 | 512 | ourBoard.on('sample', sample => { 513 | // console.log('8') 514 | // OpenBCISample.debugPrettyPrint(sample) 515 | // good to start impedance testing.. 516 | }); 517 | 518 | ourBoard.impedanceTestChannelInputN(1) 519 | .then(impdObj => { 520 | impedanceObject = impdObj; 521 | setTimeout(() => { 522 | done(); 523 | }, 1000); 524 | }) 525 | .catch(err => done(err)); 526 | }); 527 | describe('#channel1', function () { 528 | it('has valid channel number', function () { 529 | impedanceObject.channel.should.be.equal(1); 530 | }); 531 | describe('#inputP', function () { 532 | it('got raw impedance value', function () { 533 | impedanceObject.P.should.have.property('raw').equal(-1); 534 | }); 535 | it("text is not 'init'", function () { 536 | impedanceObject.P.should.have.property('text').equal(k.OBCIImpedanceTextInit); 537 | }); 538 | }); 539 | describe('#inputN', function () { 540 | it('got raw impedance value', function () { 541 | impedanceObject.N.should.have.property('raw').above(-1); 542 | }); 543 | it("text is not 'init'", function () { 544 | impedanceObject.N.should.have.property('text').not.be.equal(k.OBCIImpedanceTextInit); 545 | }); 546 | }); 547 | }); 548 | }); 549 | describe('#impedanceTestContinuousStXX', function () { 550 | before(function (done) { 551 | ourBoard.impedanceTestContinuousStart() 552 | .then(done).catch(err => done(err)); 553 | }); 554 | 555 | after(function (done) { 556 | ourBoard.impedanceTestContinuousStop() 557 | .then(done); 558 | }); 559 | 560 | it('prints 10 impedance arrays', function (done) { 561 | var count = 1; 562 | 563 | var listener = impedanceArray => { 564 | // console.log('\nImpedance Array: ' + count) 565 | // console.log(impedanceArray) 566 | count++; 567 | if (count > 10) { 568 | ourBoard.removeListener('impedanceArray', listener); 569 | done(); 570 | } 571 | }; 572 | ourBoard.on('impedanceArray', listener); 573 | }); 574 | }); 575 | describe('#_impedanceTestSetChannel', function () { 576 | it('reject with invalid channel', function (done) { 577 | ourBoard._impedanceTestSetChannel(0, false, false).should.be.rejected.and.notify(done); 578 | }); 579 | }); 580 | describe('#_impedanceTestCalculateChannel', function () { 581 | it('reject with low invalid channel', function (done) { 582 | ourBoard._impedanceTestCalculateChannel(0, false, false).should.be.rejected.and.notify(done); 583 | }); 584 | it('reject with high invalid channel', function (done) { 585 | ourBoard._impedanceTestCalculateChannel(69, false, false).should.be.rejected.and.notify(done); 586 | }); 587 | it('reject with invalid data type pInput', function (done) { 588 | ourBoard._impedanceTestCalculateChannel(1, 'taco', false).should.be.rejected.and.notify(done); 589 | }); 590 | it('reject with invalid data type nInput', function (done) { 591 | ourBoard._impedanceTestCalculateChannel(1, false, 'taco').should.be.rejected.and.notify(done); 592 | }); 593 | }); 594 | describe('#_impedanceTestFinalizeChannel', function () { 595 | it('reject with low invalid channel', function (done) { 596 | ourBoard._impedanceTestFinalizeChannel(0, false, false).should.be.rejected.and.notify(done); 597 | }); 598 | it('reject with high invalid channel', function (done) { 599 | ourBoard._impedanceTestFinalizeChannel(69, false, false).should.be.rejected.and.notify(done); 600 | }); 601 | it('reject with invalid data type pInput', function (done) { 602 | ourBoard._impedanceTestFinalizeChannel(1, 'taco', false).should.be.rejected.and.notify(done); 603 | }); 604 | it('reject with invalid data type nInput', function (done) { 605 | ourBoard._impedanceTestFinalizeChannel(1, false, 'taco').should.be.rejected.and.notify(done); 606 | }); 607 | }); 608 | }); 609 | -------------------------------------------------------------------------------- /test/openBCICyton-radio-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const bluebirdChecks = require('./bluebirdChecks'); 3 | const sinon = require('sinon'); // eslint-disable-line no-unused-vars 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const should = chai.should(); // eslint-disable-line no-unused-vars 7 | const OpenBCICyton = require('../openBCICyton'); 8 | const k = require('@openbci/utilities/dist/constants'); 9 | const chaiAsPromised = require('chai-as-promised'); 10 | const sinonChai = require('sinon-chai'); 11 | const dirtyChai = require('dirty-chai'); 12 | 13 | chai.use(chaiAsPromised); 14 | chai.use(sinonChai); 15 | chai.use(dirtyChai); 16 | 17 | describe('openbci-radios', function () { 18 | this.timeout(2000); 19 | var ourBoard, masterPortName; 20 | 21 | before(function (done) { 22 | ourBoard = new OpenBCICyton(); 23 | ourBoard.autoFindOpenBCIBoard() 24 | .then(portName => { 25 | ourBoard = null; 26 | masterPortName = portName; 27 | done(); 28 | }) 29 | .catch(() => { 30 | ourBoard = null; 31 | masterPortName = k.OBCISimulatorPortName; 32 | done(); 33 | }); 34 | }); 35 | after(done => { 36 | if (ourBoard) { 37 | if (ourBoard['connected']) { 38 | ourBoard.disconnect() 39 | .then(() => { 40 | done(); 41 | }) 42 | .catch(err => { 43 | done(err); 44 | }); 45 | } else { 46 | done(); 47 | } 48 | } else { 49 | done(); 50 | } 51 | }); 52 | 53 | describe('#radioChannelSet', function () { 54 | afterEach(function (done) { 55 | if (ourBoard.isConnected()) { 56 | ourBoard.disconnect().then(() => { 57 | done(); 58 | }).catch(() => done); 59 | } else { 60 | done(); 61 | } 62 | }); 63 | afterEach(() => bluebirdChecks.noPendingPromises()); 64 | 65 | it('should not change the channel number if not connected', function (done) { 66 | ourBoard = new OpenBCICyton({ 67 | verbose: true, 68 | simulate: true, 69 | simulatorFirmwareVersion: 'v2' 70 | }); 71 | ourBoard.radioChannelGet().should.be.rejected.and.notify(done); 72 | }); 73 | 74 | it('should reject if streaming', function (done) { 75 | ourBoard = new OpenBCICyton({ 76 | verbose: true, 77 | simulate: true, 78 | simulatorFirmwareVersion: 'v2' 79 | }); 80 | ourBoard.connect(k.OBCISimulatorPortName) 81 | .then(() => { 82 | ourBoard.streamStart() 83 | .then(() => { 84 | ourBoard.radioChannelSet(1).then(() => { 85 | done('should have rejected'); 86 | }).catch(() => { 87 | done(); // Test pass 88 | }); 89 | }).catch(err => done(err)); 90 | }).catch(err => done(err)); 91 | }); 92 | it('should reject if not firmware version 2', function (done) { 93 | ourBoard = new OpenBCICyton({ 94 | verbose: true, 95 | simulate: true 96 | }); 97 | ourBoard.connect(k.OBCISimulatorPortName) 98 | .then(() => { 99 | ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); 100 | }).catch(err => done(err)); 101 | }); 102 | it('should reject if a number is not sent as input', function (done) { 103 | ourBoard = new OpenBCICyton({ 104 | verbose: true, 105 | simulate: true, 106 | simulatorFirmwareVersion: 'v2' 107 | }); 108 | ourBoard.connect(k.OBCISimulatorPortName) 109 | .then(() => { 110 | ourBoard.radioChannelSet('1').should.be.rejected.and.notify(done); 111 | }).catch(err => done(err)); 112 | }); 113 | 114 | it('should reject if no channel number is presented as arg', function (done) { 115 | ourBoard = new OpenBCICyton({ 116 | verbose: true, 117 | simulate: true, 118 | simulatorFirmwareVersion: 'v2' 119 | }); 120 | ourBoard.connect(k.OBCISimulatorPortName) 121 | .then(() => { 122 | ourBoard.radioChannelSet().should.be.rejected.and.notify(done); 123 | }).catch(err => done(err)); 124 | }); 125 | 126 | it('should reject if the requested new channel number is lower than 0', function (done) { 127 | ourBoard = new OpenBCICyton({ 128 | verbose: true, 129 | simulate: true, 130 | simulatorFirmwareVersion: 'v2' 131 | }); 132 | ourBoard.connect(k.OBCISimulatorPortName) 133 | .then(() => { 134 | ourBoard.radioChannelSet(-1).should.be.rejected.and.notify(done); 135 | }).catch(err => done(err)); 136 | }); 137 | 138 | it('should reject if the requested new channel number is higher than 25', function (done) { 139 | ourBoard = new OpenBCICyton({ 140 | verbose: true, 141 | simulate: true, 142 | simulatorFirmwareVersion: 'v2' 143 | }); 144 | ourBoard.connect(k.OBCISimulatorPortName) 145 | .then(() => { 146 | ourBoard.radioChannelSet(26).should.be.rejected.and.notify(done); 147 | }).catch(err => done(err)); 148 | }); 149 | 150 | it('should not change the channel if the board is not communicating with the host', function (done) { 151 | ourBoard = new OpenBCICyton({ 152 | verbose: true, 153 | simulate: true, 154 | simulatorBoardFailure: true, 155 | simulatorFirmwareVersion: 'v2' 156 | }); 157 | ourBoard.connect(k.OBCISimulatorPortName) 158 | .then(() => { 159 | ourBoard.radioChannelSet(1).should.be.rejected.and.notify(done); 160 | }).catch(err => done(err)); 161 | }); 162 | 163 | it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { 164 | var newChannelNumber = 2; 165 | ourBoard = new OpenBCICyton({ 166 | verbose: true, 167 | simulate: true, 168 | simulatorFirmwareVersion: 'v2' 169 | }); 170 | ourBoard.connect(k.OBCISimulatorPortName) 171 | .then(() => { 172 | ourBoard.radioChannelSet(newChannelNumber).then(channelNumber => { 173 | expect(channelNumber).to.be.equal(newChannelNumber); 174 | done(); 175 | }).catch(err => done(err)); 176 | }).catch(err => done(err)); 177 | }); 178 | }); 179 | 180 | describe('#radioChannelSetHostOverride', function () { 181 | afterEach(function (done) { 182 | if (ourBoard.isConnected()) { 183 | ourBoard.disconnect().then(() => { 184 | done(); 185 | }).catch(() => done); 186 | } else { 187 | done(); 188 | } 189 | }); 190 | afterEach(() => bluebirdChecks.noPendingPromises()); 191 | it('should not change the channel number if not connected', function (done) { 192 | ourBoard = new OpenBCICyton({ 193 | verbose: true, 194 | simulate: true, 195 | simulatorFirmwareVersion: 'v2' 196 | }); 197 | ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); 198 | }); 199 | it('should reject if streaming', function (done) { 200 | ourBoard = new OpenBCICyton({ 201 | verbose: true, 202 | simulate: true, 203 | simulatorFirmwareVersion: 'v2' 204 | }); 205 | ourBoard.connect(k.OBCISimulatorPortName) 206 | .then(() => { 207 | ourBoard.streamStart() 208 | .then(() => { 209 | ourBoard.radioChannelSetHostOverride(1).then(() => { 210 | done('should have rejected'); 211 | }).catch(() => { 212 | done(); // Test pass 213 | }); 214 | }).catch(err => done(err)); 215 | }).catch(err => done(err)); 216 | }); 217 | it('should reject if a number is not sent as input', function (done) { 218 | ourBoard = new OpenBCICyton({ 219 | verbose: true, 220 | simulate: true, 221 | simulatorFirmwareVersion: 'v2' 222 | }); 223 | ourBoard.connect(k.OBCISimulatorPortName) 224 | .then(() => { 225 | ourBoard.radioChannelSetHostOverride('1').should.be.rejected.and.notify(done); 226 | }).catch(err => done(err)); 227 | }); 228 | it('should reject if no channel number is presented as arg', function (done) { 229 | ourBoard = new OpenBCICyton({ 230 | verbose: true, 231 | simulate: true, 232 | simulatorFirmwareVersion: 'v2' 233 | }); 234 | ourBoard.connect(k.OBCISimulatorPortName) 235 | .then(() => { 236 | ourBoard.radioChannelSetHostOverride().should.be.rejected.and.notify(done); 237 | }).catch(err => done(err)); 238 | }); 239 | it('should reject if the requested new channel number is lower than 0', function (done) { 240 | ourBoard = new OpenBCICyton({ 241 | verbose: true, 242 | simulate: true, 243 | simulatorFirmwareVersion: 'v2' 244 | }); 245 | ourBoard.connect(k.OBCISimulatorPortName) 246 | .then(() => { 247 | ourBoard.radioChannelSetHostOverride(-1).should.be.rejected.and.notify(done); 248 | }).catch(err => done(err)); 249 | }); 250 | it('should reject if the requested new channel number is higher than 25', function (done) { 251 | ourBoard = new OpenBCICyton({ 252 | verbose: true, 253 | simulate: true, 254 | simulatorFirmwareVersion: 'v2' 255 | }); 256 | ourBoard.connect(k.OBCISimulatorPortName) 257 | .then(() => { 258 | ourBoard.radioChannelSetHostOverride(26).should.be.rejected.and.notify(done); 259 | }).catch(err => done(err)); 260 | }); 261 | it('should change the channel if connected, not steaming, and using firmware version 2+', function (done) { 262 | var newChannelNumber = 2; 263 | ourBoard = new OpenBCICyton({ 264 | verbose: true, 265 | simulate: true, 266 | simulatorFirmwareVersion: 'v2' 267 | }); 268 | ourBoard.connect(k.OBCISimulatorPortName) 269 | .then(() => { 270 | ourBoard.radioChannelSetHostOverride(newChannelNumber).then(channelNumber => { 271 | expect(channelNumber).to.be.equal(newChannelNumber); 272 | done(); 273 | }).catch(err => done(err)); 274 | }).catch(err => done(err)); 275 | }); 276 | }); 277 | 278 | describe('#radioChannelGet', function () { 279 | afterEach(function (done) { 280 | if (ourBoard.isConnected()) { 281 | ourBoard.disconnect().then(() => { 282 | done(); 283 | }).catch(() => done); 284 | } else { 285 | done(); 286 | } 287 | }); 288 | afterEach(() => bluebirdChecks.noPendingPromises()); 289 | 290 | it('should not query if not connected', function (done) { 291 | ourBoard = new OpenBCICyton({ 292 | verbose: true, 293 | simulate: true 294 | }); 295 | ourBoard.radioChannelGet().should.be.rejected.and.notify(done); 296 | }); 297 | it('should not query if streaming', function (done) { 298 | ourBoard = new OpenBCICyton({ 299 | verbose: true, 300 | simulate: true 301 | }); 302 | ourBoard.connect(k.OBCISimulatorPortName) 303 | .then(() => { 304 | ourBoard.streamStart() 305 | .then(() => { 306 | ourBoard.radioChannelGet().then(() => { 307 | done('should have rejected'); 308 | }).catch(() => { 309 | done(); // Test pass 310 | }); 311 | }).catch(err => done(err)); 312 | }).catch(err => done(err)); 313 | }); 314 | it('should not query if not firmware version 2', function (done) { 315 | ourBoard = new OpenBCICyton({ 316 | verbose: true, 317 | simulate: true 318 | }); 319 | ourBoard.connect(k.OBCISimulatorPortName) 320 | .then(() => { 321 | ourBoard.radioChannelGet().should.be.rejected.and.notify(done); 322 | }).catch(err => done(err)); 323 | }); 324 | it('should query if firmware version 2', function (done) { 325 | ourBoard = new OpenBCICyton({ 326 | verbose: true, 327 | simulate: true, 328 | simulatorFirmwareVersion: 'v2' 329 | }); 330 | ourBoard.connect(k.OBCISimulatorPortName) 331 | .then(() => { 332 | ourBoard.radioChannelGet().then(res => { 333 | expect(res.channelNumber).to.be.within(k.OBCIRadioChannelMin, k.OBCIRadioChannelMax); 334 | done(); 335 | }).catch(err => done(err)); 336 | }).catch(err => done(err)); 337 | }); 338 | it('should get message even if the board is not communicating with dongle', function (done) { 339 | ourBoard = new OpenBCICyton({ 340 | verbose: true, 341 | simulate: true, 342 | simulatorBoardFailure: true, 343 | simulatorFirmwareVersion: 'v2' 344 | }); 345 | ourBoard.connect(k.OBCISimulatorPortName) 346 | .then(() => { 347 | ourBoard.radioChannelGet().should.be.rejected.and.notify(done); 348 | }).catch(err => done(err)); 349 | }); 350 | }); 351 | 352 | describe('#radioPollTimeSet', function () { 353 | afterEach(function (done) { 354 | if (ourBoard.isConnected()) { 355 | ourBoard.disconnect().then(() => { 356 | done(); 357 | }).catch(() => done); 358 | } else { 359 | done(); 360 | } 361 | }); 362 | afterEach(() => bluebirdChecks.noPendingPromises()); 363 | it('should not change the channel number if not connected', function (done) { 364 | ourBoard = new OpenBCICyton({ 365 | verbose: true, 366 | simulate: true, 367 | simulatorFirmwareVersion: 'v2' 368 | }); 369 | ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); 370 | }); 371 | 372 | it('should reject if streaming', function (done) { 373 | ourBoard = new OpenBCICyton({ 374 | verbose: true, 375 | simulate: true, 376 | simulatorFirmwareVersion: 'v2' 377 | }); 378 | ourBoard.connect(k.OBCISimulatorPortName) 379 | .then(() => { 380 | ourBoard.streamStart() 381 | .then(() => { 382 | ourBoard.radioPollTimeSet(1).then(() => { 383 | done('should have rejected'); 384 | }).catch(() => { 385 | done(); // Test pass 386 | }); 387 | }).catch(err => done(err)); 388 | }).catch(err => done(err)); 389 | }); 390 | 391 | it('should reject if not firmware version 2', function (done) { 392 | ourBoard = new OpenBCICyton({ 393 | verbose: true, 394 | simulate: true 395 | }); 396 | ourBoard.connect(k.OBCISimulatorPortName) 397 | .then(() => { 398 | ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); 399 | }).catch(err => done(err)); 400 | }); 401 | 402 | it('should reject if a number is not sent as input', function (done) { 403 | ourBoard = new OpenBCICyton({ 404 | verbose: true, 405 | simulate: true, 406 | simulatorFirmwareVersion: 'v2' 407 | }); 408 | ourBoard.connect(k.OBCISimulatorPortName) 409 | .then(() => { 410 | ourBoard.radioPollTimeSet('1').should.be.rejected.and.notify(done); 411 | }).catch(err => done(err)); 412 | }); 413 | 414 | it('should reject if no poll time is presented as arg', function (done) { 415 | ourBoard = new OpenBCICyton({ 416 | verbose: true, 417 | simulate: true, 418 | simulatorFirmwareVersion: 'v2' 419 | }); 420 | ourBoard.connect(k.OBCISimulatorPortName) 421 | .then(() => { 422 | ourBoard.radioPollTimeSet().should.be.rejected.and.notify(done); 423 | }).catch(err => done(err)); 424 | }); 425 | 426 | it('should reject if the requested new poll time is lower than 0', function (done) { 427 | ourBoard = new OpenBCICyton({ 428 | verbose: true, 429 | simulate: true, 430 | simulatorFirmwareVersion: 'v2' 431 | }); 432 | ourBoard.connect(k.OBCISimulatorPortName) 433 | .then(() => { 434 | ourBoard.radioPollTimeSet(-1).should.be.rejected.and.notify(done); 435 | }).catch(err => done(err)); 436 | }); 437 | 438 | it('should reject if the requested new poll time is higher than 255', function (done) { 439 | ourBoard = new OpenBCICyton({ 440 | verbose: true, 441 | simulate: true, 442 | simulatorFirmwareVersion: 'v2' 443 | }); 444 | ourBoard.connect(k.OBCISimulatorPortName) 445 | .then(() => { 446 | ourBoard.radioPollTimeSet(256).should.be.rejected.and.notify(done); 447 | }).catch(err => done(err)); 448 | }); 449 | 450 | it('should not change the poll time if the board is not communicating with the host', function (done) { 451 | ourBoard = new OpenBCICyton({ 452 | verbose: true, 453 | simulate: true, 454 | simulatorBoardFailure: true, 455 | simulatorFirmwareVersion: 'v2' 456 | }); 457 | ourBoard.connect(k.OBCISimulatorPortName) 458 | .then(() => { 459 | ourBoard.radioPollTimeSet(1).should.be.rejected.and.notify(done); 460 | }).catch(err => done(err)); 461 | }); 462 | 463 | it('should change the poll time if connected, not steaming, and using firmware version 2+', function (done) { 464 | var newPollTime = 69; 465 | ourBoard = new OpenBCICyton({ 466 | verbose: true, 467 | simulate: true, 468 | simulatorFirmwareVersion: 'v2' 469 | }); 470 | ourBoard.connect(k.OBCISimulatorPortName) 471 | .then(() => { 472 | ourBoard.radioPollTimeSet(newPollTime).then(() => { 473 | done(); 474 | }).catch(err => { 475 | done(err); 476 | }); 477 | }).catch(err => done(err)); 478 | }); 479 | }); 480 | 481 | describe('#radioPollTimeGet', function () { 482 | afterEach(function (done) { 483 | if (ourBoard.isConnected()) { 484 | ourBoard.disconnect().then(() => { 485 | done(); 486 | }).catch(() => done); 487 | } else { 488 | done(); 489 | } 490 | }); 491 | afterEach(() => bluebirdChecks.noPendingPromises()); 492 | 493 | it('should not query if not connected', function (done) { 494 | ourBoard = new OpenBCICyton({ 495 | verbose: true, 496 | simulate: true 497 | }); 498 | ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); 499 | }); 500 | it('should not query if streaming', function (done) { 501 | ourBoard = new OpenBCICyton({ 502 | verbose: true, 503 | simulate: true 504 | }); 505 | ourBoard.connect(k.OBCISimulatorPortName) 506 | .then(() => { 507 | ourBoard.streamStart() 508 | .then(() => { 509 | ourBoard.radioPollTimeGet().then(() => { 510 | done('should have rejected'); 511 | }).catch(() => { 512 | done(); // Test pass 513 | }); 514 | }).catch(err => done(err)); 515 | }).catch(err => done(err)); 516 | }); 517 | it('should not query if not firmware version 2', function (done) { 518 | ourBoard = new OpenBCICyton({ 519 | verbose: true, 520 | simulate: true 521 | }); 522 | ourBoard.connect(k.OBCISimulatorPortName) 523 | .then(() => { 524 | ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); 525 | }).catch(err => done(err)); 526 | }); 527 | it('should query if firmware version 2', function (done) { 528 | ourBoard = new OpenBCICyton({ 529 | verbose: true, 530 | simulate: true, 531 | simulatorFirmwareVersion: 'v2' 532 | }); 533 | ourBoard.connect(k.OBCISimulatorPortName) 534 | .then(() => { 535 | ourBoard.radioPollTimeGet().then(pollTime => { 536 | expect(pollTime).to.be.greaterThan(0); 537 | done(); 538 | }).catch(err => done(err)); 539 | }).catch(err => done(err)); 540 | }); 541 | it('should get failure message if the board is not communicating with dongle', function (done) { 542 | ourBoard = new OpenBCICyton({ 543 | verbose: true, 544 | simulate: true, 545 | simulatorBoardFailure: true, 546 | simulatorFirmwareVersion: 'v2' 547 | }); 548 | ourBoard.connect(k.OBCISimulatorPortName) 549 | .then(() => { 550 | ourBoard.radioPollTimeGet().should.be.rejected.and.notify(done); 551 | }).catch(err => done(err)); 552 | }); 553 | }); 554 | 555 | describe('#radioBaudRateSet', function () { 556 | afterEach(function (done) { 557 | if (ourBoard.isConnected()) { 558 | ourBoard.disconnect().then(() => { 559 | done(); 560 | }).catch(() => done); 561 | } else { 562 | done(); 563 | } 564 | }); 565 | afterEach(() => bluebirdChecks.noPendingPromises()); 566 | 567 | it('should not try to set baud rate if not connected', function (done) { 568 | ourBoard = new OpenBCICyton({ 569 | verbose: true, 570 | simulate: true 571 | }); 572 | ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); 573 | }); 574 | it('should reject if no input', function (done) { 575 | ourBoard = new OpenBCICyton({ 576 | verbose: true, 577 | simulate: true 578 | }); 579 | ourBoard.radioBaudRateSet().should.be.rejected.and.notify(done); 580 | }); 581 | it('should be rejected if input type incorrect', function (done) { 582 | ourBoard = new OpenBCICyton({ 583 | verbose: true, 584 | simulate: true 585 | }); 586 | ourBoard.radioBaudRateSet(1).should.be.rejected.and.notify(done); 587 | }); 588 | it('should not try to change the baud rate if streaming', function (done) { 589 | ourBoard = new OpenBCICyton({ 590 | verbose: true, 591 | simulate: true 592 | }); 593 | ourBoard.connect(k.OBCISimulatorPortName) 594 | .then(() => { 595 | ourBoard.streamStart() 596 | .then(() => { 597 | ourBoard.radioBaudRateSet('default').then(() => { 598 | done('should have rejected'); 599 | }).catch(() => { 600 | done(); // Test pass 601 | }); 602 | }).catch(err => done(err)); 603 | }).catch(err => done(err)); 604 | }); 605 | it('should not try to change the baud rate if not firmware version 2', function (done) { 606 | ourBoard = new OpenBCICyton({ 607 | verbose: true, 608 | simulate: true 609 | }); 610 | ourBoard.connect(k.OBCISimulatorPortName) 611 | .then(() => { 612 | ourBoard.radioBaudRateSet('default').should.be.rejected.and.notify(done); 613 | }).catch(err => done(err)); 614 | }); 615 | it('should set the baud rate to default if firmware version 2', function (done) { 616 | ourBoard = new OpenBCICyton({ 617 | verbose: true, 618 | simulate: true, 619 | simulatorFirmwareVersion: 'v2' 620 | }); 621 | ourBoard.connect(k.OBCISimulatorPortName) 622 | .then(() => { 623 | ourBoard.radioBaudRateSet('default').then(baudrate => { 624 | expect(baudrate).to.be.equal(115200); 625 | done(); 626 | }).catch(err => done(err)); 627 | }).catch(err => done(err)); 628 | }); 629 | it('should set the baud rate to fast if firmware version 2', function (done) { 630 | ourBoard = new OpenBCICyton({ 631 | verbose: true, 632 | simulate: true, 633 | simulatorFirmwareVersion: 'v2' 634 | }); 635 | ourBoard.connect(k.OBCISimulatorPortName) 636 | .then(() => { 637 | ourBoard.radioBaudRateSet('fast').then(baudrate => { 638 | expect(baudrate).to.be.equal(230400); 639 | done(); 640 | }).catch(err => done(err)); 641 | }).catch(err => done(err)); 642 | }); 643 | }); 644 | 645 | describe('#radioSystemStatusGet', function () { 646 | afterEach(function (done) { 647 | if (ourBoard.isConnected()) { 648 | ourBoard.disconnect().then(() => { 649 | done(); 650 | }).catch(() => done); 651 | } else { 652 | done(); 653 | } 654 | }); 655 | afterEach(() => bluebirdChecks.noPendingPromises()); 656 | 657 | it('should not get system status if not connected', function (done) { 658 | ourBoard = new OpenBCICyton({ 659 | verbose: true, 660 | simulate: true 661 | }); 662 | ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); 663 | }); 664 | it('should not get system status if streaming', function (done) { 665 | ourBoard = new OpenBCICyton({ 666 | verbose: true, 667 | simulate: true 668 | }); 669 | ourBoard.connect(k.OBCISimulatorPortName) 670 | .then(() => { 671 | ourBoard.streamStart() 672 | .then(() => { 673 | ourBoard.radioSystemStatusGet().then(() => { 674 | done('should have rejected'); 675 | }).catch(() => { 676 | done(); // Test pass 677 | }); 678 | }).catch(err => done(err)); 679 | }).catch(err => done(err)); 680 | }); 681 | it('should not get system status if not firmware version 2', function (done) { 682 | ourBoard = new OpenBCICyton({ 683 | verbose: true, 684 | simulate: true 685 | }); 686 | ourBoard.connect(k.OBCISimulatorPortName) 687 | .then(() => { 688 | ourBoard.radioSystemStatusGet().should.be.rejected.and.notify(done); 689 | }).catch(err => done(err)); 690 | }); 691 | it('should get up system status if firmware version 2', function (done) { 692 | ourBoard = new OpenBCICyton({ 693 | verbose: true, 694 | simulate: true, 695 | simulatorFirmwareVersion: 'v2' 696 | }); 697 | ourBoard.connect(k.OBCISimulatorPortName) 698 | .then(() => { 699 | ourBoard.radioSystemStatusGet().then(isUp => { 700 | expect(isUp).to.be.true(); 701 | done(); 702 | }).catch(err => done(err)); 703 | }).catch(err => done(err)); 704 | }); 705 | it('should get down system status if firmware version 2', function (done) { 706 | ourBoard = new OpenBCICyton({ 707 | verbose: true, 708 | simulate: true, 709 | simulatorFirmwareVersion: 'v2', 710 | simulatorBoardFailure: true 711 | }); 712 | ourBoard.connect(k.OBCISimulatorPortName) 713 | .then(() => { 714 | ourBoard.radioSystemStatusGet().then(isUp => { 715 | expect(isUp).to.be.false(); 716 | done(); 717 | }).catch(err => done(err)); 718 | }).catch(err => done(err)); 719 | }); 720 | }); 721 | 722 | describe('#radioTests', function () { 723 | this.timeout(0); 724 | before(function (done) { 725 | ourBoard = new OpenBCICyton({ 726 | verbose: true, 727 | simulatorFirmwareVersion: 'v2', 728 | simulatorFragmentation: k.OBCISimulatorFragmentationRandom 729 | }); 730 | ourBoard.connect(masterPortName) 731 | .then(done) 732 | .catch(err => done(err)); 733 | }); 734 | after(function (done) { 735 | if (ourBoard.isConnected()) { 736 | ourBoard.disconnect().then(() => { 737 | done(); 738 | }); 739 | } else { 740 | done(); 741 | } 742 | }); 743 | after(() => bluebirdChecks.noPendingPromises()); 744 | it('should be able to get the channel number', function (done) { 745 | // Don't test if not using v2 746 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 747 | // The channel number should be between 0 and 25. Those are hard limits. 748 | ourBoard.radioChannelGet().then(res => { 749 | expect(res.channelNumber).to.be.within(0, 25); 750 | done(); 751 | }).catch(err => done(err)); 752 | }); 753 | it('should be able to set the channel to 1', function (done) { 754 | // Don't test if not using v2 755 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 756 | ourBoard.radioChannelSet(1).then(channelNumber => { 757 | expect(channelNumber).to.equal(1); 758 | done(); 759 | }).catch(err => done(err)); 760 | }); 761 | it('should be able to set the channel to 2', function (done) { 762 | // Don't test if not using v2 763 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 764 | ourBoard.radioChannelSet(2).then(channelNumber => { 765 | expect(channelNumber).to.equal(2); 766 | done(); 767 | }).catch(err => done(err)); 768 | }); 769 | it('should be able to set the channel to 25', function (done) { 770 | // Don't test if not using v2 771 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 772 | ourBoard.radioChannelSet(25).then(channelNumber => { 773 | expect(channelNumber).to.equal(25); 774 | done(); 775 | }).catch(err => done(err)); 776 | }); 777 | it('should be able to set the channel to 5', function (done) { 778 | // Don't test if not using v2 779 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 780 | ourBoard.radioChannelSet(5).then(channelNumber => { 781 | expect(channelNumber).to.equal(5); 782 | done(); 783 | }).catch(err => done(err)); 784 | }); 785 | it('should be able to override host channel number, verify system is down, set the host back and verify system is up', function (done) { 786 | // Don't test if not using v2 787 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 788 | var systemChanNumber = 0; 789 | var newChanNum = 0; 790 | // var timey = Date.now() 791 | // Get the current system channel 792 | ourBoard.radioChannelGet() 793 | .then(res => { 794 | // Store it 795 | systemChanNumber = res.channelNumber; 796 | // console.log(`system channel number: ${res.channelNumber}`) 797 | if (systemChanNumber === 25) { 798 | newChanNum = 24; 799 | } else { 800 | newChanNum = systemChanNumber + 1; 801 | } 802 | // Call to change just the host 803 | return ourBoard.radioChannelSetHostOverride(newChanNum); 804 | }) 805 | .then(newChanNumActual => { 806 | expect(newChanNumActual).to.equal(newChanNum); 807 | // timey = Date.now() 808 | // console.log(`new chan ${newChanNumActual} got`, timey, '0ms') 809 | return new Promise((resolve, reject) => { 810 | setTimeout(function () { 811 | // console.log(`get status`, Date.now(), `${Date.now() - timey}ms`) 812 | ourBoard.radioSystemStatusGet().then(isUp => { 813 | // console.log('resolving', Date.now(), `${Date.now() - timey}ms`) 814 | resolve(isUp); 815 | }) 816 | .catch(err => { 817 | reject(err); 818 | }); 819 | }, 270); // Should be accurate after 270 seconds 820 | }); 821 | }) 822 | .then(isUp => { 823 | // console.log(`isUp test`, Date.now(), `${Date.now() - timey}ms`) 824 | expect(isUp).to.be.false(); 825 | return ourBoard.radioChannelSetHostOverride(systemChanNumber); // Set back to good 826 | }) 827 | .then(newChanNumActual => { 828 | // Verify we set it back to normal 829 | expect(newChanNumActual).to.equal(systemChanNumber); 830 | return ourBoard.radioSystemStatusGet(); 831 | }) 832 | .then(isUp => { 833 | expect(isUp).to.be.true(); 834 | done(); 835 | }) 836 | .catch(err => done(err)); 837 | }); 838 | it('should be able to get the poll time', function (done) { 839 | // Don't test if not using v2 840 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 841 | ourBoard.radioPollTimeGet().should.eventually.be.greaterThan(0).and.notify(done); 842 | }); 843 | it('should be able to set the poll time', function (done) { 844 | // Don't test if not using v2 845 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 846 | ourBoard.radioPollTimeSet(80).should.become(80).and.notify(done); 847 | }); 848 | it('should be able to change to default baud rate', function (done) { 849 | // Don't test if not using v2 850 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 851 | ourBoard.radioBaudRateSet('default').should.become(115200).and.notify(done); 852 | }); 853 | it('should be able to change to fast baud rate', function (done) { 854 | // Don't test if not using v2 855 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 856 | ourBoard.radioBaudRateSet('fast').then(newBaudRate => { 857 | expect(newBaudRate).to.equal(230400); 858 | return ourBoard.radioBaudRateSet('default'); 859 | }).then(() => { 860 | done(); 861 | }).catch(err => done(err)); 862 | }); 863 | it('should be able to set the system status', function (done) { 864 | // Don't test if not using v2 865 | if (!ourBoard.usingVersionTwoFirmware()) return done(); 866 | ourBoard.radioSystemStatusGet().then(isUp => { 867 | expect(isUp).to.be.true(); 868 | done(); 869 | }).catch(err => done(err)); 870 | }); 871 | }); 872 | }); 873 | -------------------------------------------------------------------------------- /test/openBCISimulator-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global describe, it, after, afterEach, beforeEach */ 3 | const bluebirdChecks = require('./bluebirdChecks'); 4 | const bufferEqual = require('buffer-equal'); 5 | const chai = require('chai'); 6 | const chaiAsPromised = require(`chai-as-promised`); 7 | const dirtyChai = require('dirty-chai'); 8 | const expect = chai.expect; 9 | const should = chai.should(); // eslint-disable-line no-unused-vars 10 | const OpenBCISimulator = require('../openBCISimulator'); 11 | const openBCIUtilities = require('@openbci/utilities/dist/utilities'); 12 | const k = require('@openbci/utilities/dist/constants'); 13 | const _ = require('lodash'); 14 | 15 | chai.use(chaiAsPromised); 16 | chai.use(dirtyChai); 17 | 18 | describe('openBCISimulator', function () { 19 | this.timeout(4000); 20 | let portName = k.OBCISimulatorPortName; 21 | afterEach(() => bluebirdChecks.noPendingPromises(200)); 22 | describe('#constructor', function () { 23 | let simulator; 24 | afterEach(() => { 25 | simulator = null; 26 | }); 27 | after(done => { 28 | setTimeout(() => { 29 | // Since there is a conditional timeout, it's important to wait to start the next test till this ends for sure 30 | done(); 31 | }, 200); // The same amount of time in the simulator 32 | }); 33 | it('constructs with the correct default options', function () { 34 | simulator = new OpenBCISimulator(); 35 | expect(simulator.options.accel).to.be.true(); 36 | expect(simulator.options.alpha).to.be.true(); 37 | expect(simulator.options.boardFailure).to.be.false(); 38 | expect(simulator.options.daisy).to.be.false(); 39 | expect(simulator.options.daisyCanBeAttached).to.be.true(); 40 | expect(simulator.options.drift).to.equal(0); 41 | expect(simulator.options.firmwareVersion).to.equal(k.OBCIFirmwareV1); 42 | expect(simulator.options.lineNoise).to.equal(k.OBCISimulatorLineNoiseHz60); 43 | expect(simulator.options.sampleRate).to.equal(k.OBCISampleRate250); 44 | expect(simulator.options.serialPortFailure).to.be.false(); 45 | expect(simulator.options.verbose).to.be.false(); 46 | expect(simulator.options.fragmentation).to.equal(k.OBCISimulatorFragmentationNone); 47 | expect(simulator.options.latencyTime).to.equal(16); 48 | expect(simulator.options.bufferSize).to.equal(4096); 49 | }); 50 | it('should be able to get into daisy mode', function () { 51 | simulator = new OpenBCISimulator(portName, { 52 | daisy: true 53 | }); 54 | expect(simulator.options.daisy).to.be.true(); 55 | }); 56 | it('should set the correct sample rate in daisy mode, if no sampleRate is provided', function () { 57 | simulator = new OpenBCISimulator(portName, { 58 | daisy: true 59 | }); 60 | expect(simulator.options.sampleRate).to.equal(250); // produce samples at same rate 61 | }); 62 | it('should use provided sample rate even if daisy is true', function () { 63 | simulator = new OpenBCISimulator(portName, { 64 | daisy: true, 65 | sampleRate: 20 66 | }); 67 | expect(simulator.options.daisy).to.be.true(); 68 | expect(simulator.options.sampleRate).to.equal(20); 69 | }); 70 | it('should be able to put into firmware version 2', function () { 71 | simulator = new OpenBCISimulator(portName, { 72 | firmwareVersion: 'v2' 73 | }); 74 | expect(simulator.options.firmwareVersion).to.equal(k.OBCIFirmwareV2); 75 | }); 76 | it('should be able to simulate board failure', function () { 77 | simulator = new OpenBCISimulator(portName, { 78 | boardFailure: true 79 | }); 80 | expect(simulator.options.boardFailure).to.be.true(); 81 | }); 82 | it('should be able to simulate serial port failure', function () { 83 | simulator = new OpenBCISimulator(portName, { 84 | serialPortFailure: true 85 | }); 86 | expect(simulator.options.serialPortFailure).to.be.true(); 87 | }); 88 | it('can turn 50Hz line noise on', function () { 89 | simulator = new OpenBCISimulator(portName, { 90 | lineNoise: '50Hz' 91 | }); 92 | (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseHz50); 93 | }); 94 | it('can turn no line noise on', function () { 95 | simulator = new OpenBCISimulator(portName, { 96 | lineNoise: 'none' 97 | }); 98 | (simulator.options.lineNoise).should.equal(k.OBCISimulatorLineNoiseNone); 99 | }); 100 | it('should not inject alpha if desired', function () { 101 | simulator = new OpenBCISimulator(portName, { 102 | alpha: false 103 | }); 104 | expect(simulator.options.alpha).to.be.false(); 105 | }); 106 | it('should be able to not use the accel', function () { 107 | simulator = new OpenBCISimulator(portName, { 108 | accel: false 109 | }); 110 | expect(simulator.options.accel).to.be.false(); 111 | }); 112 | it('should be able to set positive drift', function () { 113 | simulator = new OpenBCISimulator(portName, { 114 | drift: 1 115 | }); 116 | expect(simulator.options.drift).to.be.greaterThan(0); 117 | }); 118 | it('should be able to set negative drift', function () { 119 | simulator = new OpenBCISimulator(portName, { 120 | drift: -1 121 | }); 122 | expect(simulator.options.drift).to.be.lessThan(0); 123 | }); 124 | it('should throw if passed an invalid option', function (done) { 125 | try { 126 | simulator = new OpenBCISimulator(portName, { 127 | foo: 'bar' 128 | }); 129 | done('did not throw'); 130 | } catch (e) { done(); } 131 | }); 132 | }); 133 | describe('#write', function () { 134 | it('should refuse to write when not connected', function (done) { 135 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); 136 | try { 137 | simulator.write('q'); 138 | done('did not throw on disconnected write'); 139 | } catch (e) { 140 | simulator.write('q', err => { 141 | if (err) { 142 | done(); 143 | } else { 144 | done('did not provide error on disconnected write'); 145 | } 146 | }); 147 | } 148 | }); 149 | }); 150 | describe('#close', function () { 151 | it('should provide an error closing when already closed', function (done) { 152 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); 153 | simulator.close(err => { 154 | if (err) { 155 | done(); 156 | } else { 157 | done('closed successfully but had no time to open'); 158 | } 159 | }); 160 | }); 161 | }); 162 | describe('query register settings', function () { 163 | it('should send back the register query settings firmware version 1 cyton', function (done) { 164 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 165 | firmwareVersion: 'v1' 166 | }); 167 | simulator.once('data', function (data) { 168 | console.log(data.toString()); 169 | expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryAccelerometerFirmwareV1); 170 | done(); 171 | }); 172 | 173 | simulator.once('open', () => { 174 | simulator.write('?'); 175 | }); 176 | }); 177 | it('should send back the register query settings firmware version 3 cyton', function (done) { 178 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 179 | firmwareVersion: 'v3' 180 | }); 181 | simulator.once('data', function (data) { 182 | console.log(data.toString()); 183 | expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryAccelerometerFirmwareV3); 184 | done(); 185 | }); 186 | 187 | simulator.once('open', () => { 188 | simulator.write('?'); 189 | }); 190 | }); 191 | it('should send back the register query settings firmware version 1 cyton daisy', function (done) { 192 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 193 | firmwareVersion: 'v1', 194 | daisy: true 195 | }); 196 | simulator.once('data', function (data) { 197 | console.log(data.toString()); 198 | expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryCytonDaisy + k.OBCIRegisterQueryAccelerometerFirmwareV1); 199 | done(); 200 | }); 201 | 202 | simulator.once('open', () => { 203 | simulator.write('?'); 204 | }); 205 | }); 206 | it('should send back the register query settings firmware version 3 cyton daisy', function (done) { 207 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 208 | firmwareVersion: 'v3', 209 | daisy: true 210 | }); 211 | simulator.once('data', function (data) { 212 | console.log(data.toString()); 213 | expect(data.toString()).to.equal(k.OBCIRegisterQueryCyton + k.OBCIRegisterQueryCytonDaisy + k.OBCIRegisterQueryAccelerometerFirmwareV3); 214 | done(); 215 | }); 216 | 217 | simulator.once('open', () => { 218 | simulator.write('?'); 219 | }); 220 | }); 221 | }); 222 | describe(`_startStream`, function () { 223 | it('should return a packet with sample data in it', function (done) { 224 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); 225 | let sampleCounter = 0; 226 | let sampleTestSize = 5; 227 | 228 | let newDataFunc = data => { 229 | if (sampleCounter > sampleTestSize) { 230 | simulator.write(k.OBCIStreamStop); 231 | simulator.removeListener('data', newDataFunc); 232 | const inputObj = { 233 | rawDataPacket: data, 234 | channelSettings: k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton), 235 | scale: true 236 | }; 237 | let sample = openBCIUtilities.parsePacketStandardAccel(inputObj); 238 | expect(sample.channelData).to.not.all.equal(0); 239 | done(); 240 | simulator = null; 241 | } else { 242 | sampleCounter++; 243 | } 244 | }; 245 | simulator.on('data', newDataFunc); 246 | simulator.once('open', () => simulator._startStream()); 247 | }); 248 | it('should return a sync set packet with accel', function (done) { 249 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName); 250 | let sampleCounter = 0; 251 | let sampleTestSize = 5; 252 | 253 | let newDataFunc = data => { 254 | if (sampleCounter === 0) { 255 | // Ensure everything is switched on for this test 256 | simulator.options.accel = true; 257 | simulator.synced = true; 258 | simulator.sendSyncSetPacket = true; 259 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketStandardAccel)); 260 | } else if (sampleCounter === 1) { 261 | // Now this data should be the time sync up packet 262 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSyncSet)); 263 | // Expect flag to be down 264 | expect(simulator.sendSyncSetPacket).to.be.false(); 265 | } else if (sampleCounter >= sampleTestSize) { 266 | simulator.write(k.OBCIStreamStop); 267 | simulator.removeListener('data', newDataFunc); 268 | simulator = null; 269 | done(); 270 | } else { 271 | // Now this data should be the time sync up packet 272 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketAccelTimeSynced)); 273 | } 274 | sampleCounter++; 275 | }; 276 | 277 | simulator.on('data', newDataFunc); 278 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 279 | }); 280 | it('should return a sync set packet with raw aux', function (done) { 281 | let simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 282 | accel: false 283 | }); 284 | let sampleCounter = 0; 285 | let sampleTestSize = 5; 286 | 287 | let newDataFunc = data => { 288 | if (sampleCounter === 0) { 289 | // Ensure everything is switched on for this test 290 | simulator.synced = true; 291 | simulator.sendSyncSetPacket = true; 292 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketStandardRawAux)); 293 | } else if (sampleCounter === 1) { 294 | // Now this data should be the time sync up packet 295 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSyncSet)); 296 | // Expect flag to be down 297 | expect(simulator.sendSyncSetPacket).to.be.false(); 298 | } else if (sampleCounter >= sampleTestSize) { 299 | simulator.write(k.OBCIStreamStop); 300 | simulator.removeListener('data', newDataFunc); 301 | simulator = null; 302 | done(); 303 | } else { 304 | // Now this data should be the time sync up packet 305 | expect(data[k.OBCIPacketPositionStopByte]).to.equal(openBCIUtilities.makeTailByteFromPacketType(k.OBCIStreamPacketRawAuxTimeSynced)); 306 | } 307 | sampleCounter++; 308 | }; 309 | 310 | simulator.on('data', newDataFunc); 311 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 312 | }); 313 | }); 314 | describe(`firmwareVersion1`, function () { 315 | let simulator; 316 | beforeEach((done) => { 317 | simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 318 | firmwareVersion: 'v1' 319 | }); 320 | simulator.once('open', done); 321 | }); 322 | afterEach(() => { 323 | simulator = null; 324 | }); 325 | describe('reset', function () { 326 | it('should not be v2', function (done) { 327 | simulator.on('data', function (data) { 328 | console.log(data.toString()); 329 | expect(data.toString().match('v2')).to.equal(null); 330 | done(); 331 | }); 332 | simulator.write(k.OBCIMiscSoftReset); 333 | }); 334 | }); 335 | }); 336 | describe(`firmwareVersion2`, function () { 337 | let simulator; 338 | beforeEach((done) => { 339 | simulator = new OpenBCISimulator(k.OBCISimulatorPortName, { 340 | firmwareVersion: 'v2' 341 | }); 342 | simulator.once('open', done); 343 | }); 344 | afterEach(() => { 345 | simulator.removeAllListeners('data'); 346 | simulator = null; 347 | }); 348 | describe('set max channels', function () { 349 | this.timeout(100); 350 | it('should send nothing if no daisy attached', function (done) { 351 | simulator.options.daisy = false; 352 | simulator.on('data', function (data) { 353 | expect(data.toString().match(k.OBCIParseEOT)).to.not.equal(null); 354 | if (data.toString().match(k.OBCIParseEOT)) { 355 | simulator.removeAllListeners('data'); 356 | done(); 357 | } 358 | }); 359 | simulator.write(k.OBCIChannelMaxNumber8); 360 | }); 361 | it('should send daisy removed if daisy attached', function (done) { 362 | simulator.options.daisy = true; 363 | simulator.on('data', function (data) { 364 | expect(_.eq(data.toString(), 'daisy removed')).to.not.equal(null); 365 | if (data.toString().match(k.OBCIParseEOT)) { 366 | expect(simulator.options.daisy).to.equal(false); 367 | simulator.removeAllListeners('data'); 368 | done(); 369 | } 370 | }); 371 | simulator.write(k.OBCIChannelMaxNumber8); 372 | }); 373 | it('should send just 16 if daisy already attached', function (done) { 374 | simulator.options.daisy = true; 375 | simulator.on('data', function (data) { 376 | expect(data.toString().match(`16${k.OBCIParseEOT}`)).to.not.equal(null); 377 | if (data.toString().match(k.OBCIParseEOT)) { 378 | simulator.removeAllListeners('data'); 379 | done(); 380 | } 381 | }); 382 | simulator.write(k.OBCIChannelMaxNumber16); 383 | }); 384 | it('should send daisy attached if able to attach', function (done) { 385 | simulator.options.daisy = false; 386 | simulator.options.daisyCanBeAttached = true; 387 | simulator.on('data', function (data) { 388 | expect(data.toString().match(/daisy attached16/)).to.not.equal(null); 389 | if (data.toString().match(k.OBCIParseEOT)) { 390 | expect(simulator.options.daisy).to.equal(true); 391 | simulator.removeAllListeners('data'); 392 | done(); 393 | } 394 | }); 395 | simulator.write(k.OBCIChannelMaxNumber16); 396 | }); 397 | it('should send daisy attached if not able to attach', function (done) { 398 | simulator.options.daisy = false; 399 | simulator.options.daisyCanBeAttached = false; 400 | simulator.on('data', function (data) { 401 | expect(data.toString().match(/no daisy to attach!/)).to.not.equal(null); 402 | if (data.toString().match(k.OBCIParseEOT)) { 403 | simulator.removeAllListeners('data'); 404 | done(); 405 | } 406 | }); 407 | simulator.write(k.OBCIChannelMaxNumber16); 408 | }); 409 | }); 410 | describe('reset', function () { 411 | it('should be v2', function (done) { 412 | simulator.on('data', function (data) { 413 | expect(data.toString().match(/v2/)).to.not.equal(null); 414 | done(); 415 | }); 416 | simulator.write(k.OBCIMiscSoftReset); 417 | }); 418 | }); 419 | describe('_processPrivateRadioMessage', function () { 420 | describe('OBCIRadioCmdChannelGet', function () { 421 | it('should emit success if firmware version 2', done => { 422 | simulator.channelNumber = 0; 423 | let buf = Buffer.alloc(0); 424 | let dataEmit = data => { 425 | buf = Buffer.concat([buf, data]); 426 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 427 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 428 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 429 | expect(buf[buf.length - 4]).to.equal(0); 430 | simulator.removeListener('data', dataEmit); 431 | done(); 432 | } 433 | }; 434 | 435 | simulator.on('data', dataEmit); 436 | 437 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])); 438 | }); 439 | it('should emit failure if board failure and host channel number', done => { 440 | // Turn board failure mode 441 | simulator.options.boardFailure = true; 442 | simulator.channelNumber = 9; 443 | let buf = Buffer.alloc(0); 444 | let dataEmit = data => { 445 | buf = Buffer.concat([buf, data]); 446 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 447 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 448 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 449 | expect(buf[buf.length - 4]).to.equal(9); 450 | simulator.removeListener('data', dataEmit); 451 | done(); 452 | } 453 | }; 454 | 455 | simulator.on('data', dataEmit); 456 | 457 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelGet])); 458 | }); 459 | }); 460 | describe('OBCIRadioCmdChannelSet', function () { 461 | it('should set the channel number if in bounds', done => { 462 | let newChanNum = 20; 463 | let buf = Buffer.alloc(0); 464 | let dataEmit = data => { 465 | buf = Buffer.concat([buf, data]); 466 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 467 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 468 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 469 | expect(buf[buf.length - 4]).to.equal(newChanNum); 470 | expect(simulator.channelNumber).to.equal(newChanNum); 471 | expect(simulator.hostChannelNumber).to.equal(newChanNum); 472 | simulator.removeListener('data', dataEmit); 473 | done(); 474 | } 475 | }; 476 | 477 | simulator.on('data', dataEmit); 478 | 479 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, newChanNum])); 480 | }); 481 | it('should not set the channel number if out of bounds', done => { 482 | let newChanNum = 26; 483 | let buf = Buffer.alloc(0); 484 | let dataEmit = data => { 485 | buf = Buffer.concat([buf, data]); 486 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 487 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 488 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 489 | simulator.removeListener('data', dataEmit); 490 | done(); 491 | } 492 | }; 493 | 494 | simulator.on('data', dataEmit); 495 | 496 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, newChanNum])); 497 | }); 498 | it('should emit failure if board failure', done => { 499 | // Turn board failure mode 500 | simulator.options.boardFailure = true; 501 | let buf = Buffer.alloc(0); 502 | let dataEmit = data => { 503 | buf = Buffer.concat([buf, data]); 504 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 505 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 506 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 507 | simulator.removeListener('data', dataEmit); 508 | done(); 509 | } 510 | }; 511 | 512 | simulator.on('data', dataEmit); 513 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSet, 7])); 514 | }); 515 | }); 516 | describe('OBCIRadioCmdChannelSetOverride', function () { 517 | it('should change just the hosts channel number and not the systems channel number and force a board comms failure', done => { 518 | let systemChannelNumber = 0; 519 | let newHostChannelNumber = 1; 520 | simulator.channelNumber = systemChannelNumber; 521 | let buf = Buffer.alloc(0); 522 | let dataEmit = data => { 523 | buf = Buffer.concat([buf, data]); 524 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 525 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 526 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 527 | expect(buf[buf.length - 4]).to.equal(newHostChannelNumber); 528 | expect(simulator.options.boardFailure).to.be.true(); 529 | expect(simulator.channelNumber).to.equal(systemChannelNumber); 530 | expect(simulator.hostChannelNumber).to.equal(newHostChannelNumber); 531 | simulator.removeListener('data', dataEmit); 532 | done(); 533 | } 534 | }; 535 | 536 | simulator.on('data', dataEmit); 537 | 538 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, newHostChannelNumber])); 539 | }); 540 | it('should change just the hosts channel number and not the systems channel number and fix a board failure', done => { 541 | let systemChannelNumber = 0; 542 | let oldHostChannelNumber = 1; 543 | simulator.channelNumber = systemChannelNumber; 544 | simulator.hostChannelNumber = oldHostChannelNumber; 545 | let buf = Buffer.alloc(0); 546 | let dataEmit = data => { 547 | buf = Buffer.concat([buf, data]); 548 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 549 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 550 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 551 | expect(buf[buf.length - 4]).to.equal(systemChannelNumber); 552 | expect(simulator.options.boardFailure).to.be.false(); 553 | expect(simulator.channelNumber).to.equal(systemChannelNumber); 554 | expect(simulator.hostChannelNumber).to.equal(systemChannelNumber); 555 | simulator.removeListener('data', dataEmit); 556 | done(); 557 | } 558 | }; 559 | 560 | simulator.on('data', dataEmit); 561 | 562 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, systemChannelNumber])); 563 | }); 564 | it('should not set the channel number if out of bounds', done => { 565 | let newChanNum = 26; 566 | let buf = Buffer.alloc(0); 567 | let dataEmit = data => { 568 | buf = Buffer.concat([buf, data]); 569 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 570 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 571 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 572 | simulator.removeListener('data', dataEmit); 573 | done(); 574 | } 575 | }; 576 | 577 | simulator.on('data', dataEmit); 578 | 579 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdChannelSetOverride, newChanNum])); 580 | }); 581 | }); 582 | describe('OBCIRadioCmdPollTimeGet', function () { 583 | it('should emit success if firmware version 2 with poll time', done => { 584 | let expectedPollTime = 80; 585 | simulator.pollTime = expectedPollTime; 586 | let buf = Buffer.alloc(0); 587 | let dataEmit = data => { 588 | buf = Buffer.concat([buf, data]); 589 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 590 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 591 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 592 | expect(buf[buf.length - 4]).to.equal(expectedPollTime); 593 | simulator.removeListener('data', dataEmit); 594 | done(); 595 | } 596 | }; 597 | 598 | simulator.on('data', dataEmit); 599 | 600 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])); 601 | }); 602 | it('should emit failure if board failure', done => { 603 | // Turn board failure mode 604 | simulator.options.boardFailure = true; 605 | let buf = Buffer.alloc(0); 606 | let dataEmit = data => { 607 | buf = Buffer.concat([buf, data]); 608 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 609 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 610 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 611 | simulator.removeListener('data', dataEmit); 612 | done(); 613 | } 614 | }; 615 | 616 | simulator.on('data', dataEmit); 617 | 618 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeGet])); 619 | }); 620 | }); 621 | describe('OBCIRadioCmdPollTimeSet', function () { 622 | it('should set the poll time if in bounds', done => { 623 | let newPollTime = 20; 624 | let buf = Buffer.alloc(0); 625 | let dataEmit = data => { 626 | buf = Buffer.concat([buf, data]); 627 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 628 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 629 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 630 | expect(buf[buf.length - 4]).to.equal(newPollTime); 631 | simulator.removeListener('data', dataEmit); 632 | done(); 633 | } 634 | }; 635 | 636 | simulator.on('data', dataEmit); 637 | 638 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, newPollTime])); 639 | }); 640 | it('should emit failure if board failure', done => { 641 | // Turn board failure mode 642 | simulator.options.boardFailure = true; 643 | let buf = Buffer.alloc(0); 644 | let dataEmit = data => { 645 | buf = Buffer.concat([buf, data]); 646 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 647 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 648 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 649 | simulator.removeListener('data', dataEmit); 650 | done(); 651 | } 652 | }; 653 | 654 | simulator.on('data', dataEmit); 655 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdPollTimeSet, 7])); 656 | }); 657 | }); 658 | describe('OBCIRadioCmdBaudRateSetDefault', function () { 659 | it('should emit success if firmware version 2 with proper baud rate', done => { 660 | let buf = Buffer.alloc(0); 661 | let dataEmit = data => { 662 | buf = Buffer.concat([buf, data]); 663 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 664 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 665 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 666 | let eotBuf = Buffer.from('$$$'); 667 | let newBaudRateBuf; 668 | for (let i = buf.length; i > 3; i--) { 669 | if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { 670 | newBaudRateBuf = buf.slice(i - 9, i - 3); 671 | break; 672 | } 673 | } 674 | let newBaudRateNum = Number(newBaudRateBuf.toString()); 675 | expect(newBaudRateNum).to.equal(k.OBCIRadioBaudRateDefault); 676 | simulator.removeListener('data', dataEmit); 677 | done(); 678 | } 679 | }; 680 | 681 | simulator.on('data', dataEmit); 682 | 683 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])); 684 | }); 685 | it('should emit success if board failure', done => { 686 | // Turn board failure mode 687 | simulator.options.boardFailure = true; 688 | let buf = Buffer.alloc(0); 689 | let dataEmit = data => { 690 | buf = Buffer.concat([buf, data]); 691 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 692 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 693 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 694 | simulator.removeListener('data', dataEmit); 695 | done(); 696 | } 697 | }; 698 | 699 | simulator.on('data', dataEmit); 700 | 701 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetDefault])); 702 | }); 703 | }); 704 | describe('OBCIRadioCmdBaudRateSetFast', function () { 705 | it('should emit success if firmware version 2 with proper baud rate', done => { 706 | let buf = Buffer.alloc(0); 707 | let dataEmit = data => { 708 | buf = Buffer.concat([buf, data]); 709 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 710 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 711 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 712 | let eotBuf = Buffer.from(`$$$`); 713 | let newBaudRateBuf; 714 | for (let i = buf.length; i > 3; i--) { 715 | if (bufferEqual(buf.slice(i - 3, i), eotBuf)) { 716 | newBaudRateBuf = buf.slice(i - 9, i - 3); 717 | break; 718 | } 719 | } 720 | let newBaudRateNum = Number(newBaudRateBuf.toString()); 721 | expect(newBaudRateNum).to.equal(k.OBCIRadioBaudRateFast); 722 | simulator.removeListener('data', dataEmit); 723 | done(); 724 | } 725 | }; 726 | 727 | simulator.on('data', dataEmit); 728 | 729 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])); 730 | }); 731 | it('should emit success if board failure', done => { 732 | // Turn board failure mode 733 | simulator.options.boardFailure = true; 734 | let buf = Buffer.alloc(0); 735 | let dataEmit = data => { 736 | buf = Buffer.concat([buf, data]); 737 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 738 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 739 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 740 | simulator.removeListener('data', dataEmit); 741 | done(); 742 | } 743 | }; 744 | 745 | simulator.on('data', dataEmit); 746 | 747 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdBaudRateSetFast])); 748 | }); 749 | }); 750 | describe('OBCIRadioCmdSystemStatus', function () { 751 | it('should emit success if firmware version 2', done => { 752 | let buf = Buffer.alloc(0); 753 | let dataEmit = data => { 754 | buf = Buffer.concat([buf, data]); 755 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 756 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.true(); 757 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.false(); 758 | simulator.removeListener('data', dataEmit); 759 | done(); 760 | } 761 | }; 762 | 763 | simulator.on('data', dataEmit); 764 | 765 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])); 766 | }); 767 | it('should emit failure if board failure', done => { 768 | // Turn board failure mode 769 | simulator.options.boardFailure = true; 770 | let buf = Buffer.alloc(0); 771 | let dataEmit = data => { 772 | buf = Buffer.concat([buf, data]); 773 | if (openBCIUtilities.doesBufferHaveEOT(buf)) { 774 | expect(openBCIUtilities.isSuccessInBuffer(buf)).to.be.false(); 775 | expect(openBCIUtilities.isFailureInBuffer(buf)).to.be.true(); 776 | simulator.removeListener('data', dataEmit); 777 | done(); 778 | } 779 | }; 780 | 781 | simulator.on('data', dataEmit); 782 | 783 | simulator._processPrivateRadioMessage(Buffer.from([k.OBCIRadioKey, k.OBCIRadioCmdSystemStatus])); 784 | }); 785 | }); 786 | }); 787 | }); 788 | describe('fragmentation', function () { 789 | let simulator; 790 | afterEach(done => { 791 | simulator.removeAllListeners(); 792 | simulator.write(k.OBCIStreamStop); 793 | simulator.close(done); 794 | }); 795 | it('Should accumulate packets if set to FullBuffers', function (done) { 796 | let bufferSize = 64; 797 | simulator = new OpenBCISimulator(portName, { 798 | fragmentation: k.OBCISimulatorFragmentationFullBuffers, 799 | bufferSize: bufferSize, 800 | latencyTime: 1000 801 | }); 802 | simulator.once('data', function (buffer) { 803 | expect(buffer.length).to.equal(bufferSize); 804 | done(); 805 | }); 806 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 807 | }); 808 | it('Should emit partial packets after latencyTime', function (done) { 809 | let bufferSize = 4096; 810 | simulator = new OpenBCISimulator(portName, { 811 | fragmentation: k.OBCISimulatorFragmentationFullBuffers, 812 | bufferSize: 4096, 813 | latencyTime: 0 814 | }); 815 | simulator.once('data', function (buffer) { 816 | expect(buffer.length).to.be.lessThan(bufferSize); 817 | done(); 818 | }); 819 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 820 | }); 821 | it('Should emit single bytes if set to OneByOne', function (done) { 822 | simulator = new OpenBCISimulator(portName, { 823 | fragmentation: k.OBCISimulatorFragmentationOneByOne 824 | }); 825 | let counter = 0; 826 | simulator.on('data', function (buffer) { 827 | expect(buffer.length).to.equal(1); 828 | ++counter; 829 | 830 | if (counter === 5) done(); 831 | }); 832 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 833 | }); 834 | it('should properly split packets, retaining valid packets', function (done) { 835 | simulator = new OpenBCISimulator(portName, { 836 | fragmentation: k.OBCISimulatorFragmentationRandom 837 | }); 838 | let buffer = Buffer.alloc(0); 839 | let counter = 0; 840 | simulator.on('data', function (data) { 841 | buffer = Buffer.concat([buffer, data], buffer.length + data.length); 842 | if (buffer.length >= 33) { 843 | const inputObj = { 844 | rawDataPacket: buffer.slice(0, 33), 845 | channelSettings: k.channelSettingsArrayInit(k.OBCINumberOfChannelsCyton), 846 | scale: true 847 | }; 848 | openBCIUtilities.parsePacketStandardAccel(inputObj); 849 | buffer = buffer.slice(33); 850 | ++counter; 851 | if (counter === 5) done(); 852 | } 853 | }); 854 | simulator.once('open', () => simulator.write(k.OBCIStreamStart)); 855 | }); 856 | }); 857 | describe(`boardFailure`, function () {}); 858 | 859 | describe(`#sync`, function () { 860 | this.timeout(2000); 861 | let simulator; 862 | beforeEach(function (done) { 863 | simulator = new OpenBCISimulator(portName, { 864 | firmwareVersion: 'v2' 865 | }); 866 | simulator.once('open', () => { 867 | done(); 868 | }); 869 | }); 870 | afterEach(function () { 871 | simulator = null; 872 | }); 873 | it(`should emit the time sync sent command`, function (done) { 874 | simulator.once('data', data => { 875 | expect(openBCIUtilities.isTimeSyncSetConfirmationInBuffer(data)).to.be.true(); 876 | done(); 877 | }); 878 | simulator.write(k.OBCISyncTimeSet, (err, msg) => { 879 | if (err) { 880 | done(err); 881 | } 882 | }); 883 | }); 884 | it(`should set synced to true`, function (done) { 885 | simulator.synced = false; 886 | let newData = data => { 887 | expect(simulator.synced).to.be.true(); 888 | simulator.removeListener('data', newData); 889 | done(); 890 | }; 891 | 892 | simulator.on('data', data => { 893 | newData(data); 894 | }); 895 | 896 | simulator.write(k.OBCISyncTimeSet, (err, msg) => { 897 | if (err) { 898 | done(err); 899 | } 900 | }); 901 | }); 902 | it(`should emit a time sync set packet followed by a time synced accel packet after sync up call`, function (done) { 903 | simulator.synced = false; 904 | let emitCounter = 0; 905 | let maxPacketsBetweenSetPacket = 5; 906 | let newData = data => { 907 | if (emitCounter === 0) { // the time sync packet is emitted here 908 | // Make a call to start streaming 909 | simulator.write(k.OBCIStreamStart, err => { 910 | if (err) done(err); 911 | }); 912 | } else if (emitCounter < maxPacketsBetweenSetPacket) { 913 | if (openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketAccelTimeSyncSet) { 914 | simulator.removeListener('data', newData); 915 | simulator.write(k.OBCIStreamStop, err => { 916 | if (err) done(err); 917 | done(); 918 | }); 919 | } else { 920 | expect(openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketAccelTimeSynced); 921 | } 922 | } else { 923 | done(`Failed to get set packet in time`); 924 | } 925 | emitCounter++; 926 | }; 927 | 928 | simulator.on('data', newData); 929 | 930 | simulator.write(k.OBCISyncTimeSet, (err, msg) => { 931 | if (err) { 932 | done(err); 933 | } 934 | }); 935 | }); 936 | it(`should emit a time sync set raw aux, then time synced raw aux packet after sync up call`, function (done) { 937 | simulator.synced = false; 938 | simulator.options.accel = false; 939 | let emitCounter = 0; 940 | let maxPacketsBetweenSetPacket = 5; 941 | let newData = data => { 942 | if (emitCounter === 0) { // the time sync packet is emitted here 943 | // Make a call to start streaming 944 | simulator.write(k.OBCIStreamStart, err => { 945 | if (err) done(err); 946 | }); 947 | } else if (emitCounter < maxPacketsBetweenSetPacket) { 948 | if (openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte]) === k.OBCIStreamPacketRawAuxTimeSyncSet) { 949 | simulator.removeListener('data', newData); 950 | simulator.write(k.OBCIStreamStop, err => { 951 | if (err) done(err); 952 | done(); 953 | }); 954 | } else { 955 | expect(openBCIUtilities.getRawPacketType(data[k.OBCIPacketPositionStopByte])).to.equal(k.OBCIStreamPacketRawAuxTimeSynced); 956 | } 957 | } else { 958 | done(`Failed to get set packet in time`); 959 | } 960 | 961 | emitCounter++; 962 | }; 963 | 964 | simulator.on('data', newData); 965 | 966 | simulator.write(k.OBCISyncTimeSet, (err, msg) => { 967 | if (err) { 968 | done(err); 969 | } 970 | }); 971 | }); 972 | }); 973 | }); 974 | --------------------------------------------------------------------------------