Comparar commits
81 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| f96cdd94ec | |||
| 4ce630dc4c | |||
| 8557444c55 | |||
| c2c75fe9d8 | |||
| 13d4f57003 | |||
| b9d0a466f8 | |||
| 4428040a06 | |||
| f7e5c4988e | |||
| d4799dd45a | |||
| 212db205f2 | |||
| 47b2df5802 | |||
| d06b6101e3 | |||
| eb0510be6f | |||
| c696d4f5ca | |||
| 677517715a | |||
| 600b1b2b28 | |||
| 32d011d1f9 | |||
| ee6c50294c | |||
| 34be4c9fe3 | |||
| 57dc399742 | |||
| 5d989f6ea4 | |||
| 2eebde6053 | |||
| f7f8517c28 | |||
| d4f62ef382 | |||
| c4e29bbaa7 | |||
| f553cdea35 | |||
| 25c7871fec | |||
| c2097670e7 | |||
| cfb8071a9d | |||
| 22aca53dac | |||
| 9467d8a052 | |||
| b6ef09c220 | |||
| 238d4cc119 | |||
| c59cd18cf4 | |||
| 69cd84f446 | |||
| 9a17e41c0b | |||
| edff39504c | |||
| 2d1285a887 | |||
| 2100aaee5c | |||
| eba46f8e6c | |||
| de57563f51 | |||
| f59b41109d | |||
| 0193031d20 | |||
| 2904830309 | |||
| 08ce44d4e9 | |||
| 62a69e01cc | |||
| e9e6dbd624 | |||
| 1ade656876 | |||
| 83d32bc9b9 | |||
| 4b5e4ab0a6 | |||
| 2cc0d42fdf | |||
| 94e0a13d71 | |||
| 6b11910fa7 | |||
| 83ac939d4e | |||
| 38687d42bb | |||
| 1a43a06786 | |||
| 47c6918194 | |||
| c351f3afd1 | |||
| e65707cb31 | |||
| 97e44171b8 | |||
| 0f7bb33434 | |||
| 884f2f14ee | |||
| 5fde5b09bf | |||
| 23776e4b9c | |||
| 5b70268390 | |||
| 37db876f11 | |||
| 864469d3d8 | |||
| 7d78748532 | |||
| d6fbce3e4c | |||
| 7ecff0a08d | |||
| ab891d7565 | |||
| 3e99a77e88 | |||
| e3061c03ce | |||
| d6335257e1 | |||
| aea599a7dd | |||
| 031861d2ed | |||
| 6fd9ddf01b | |||
| fe2abac07b | |||
| 2528d56ac9 | |||
| 18b137b498 | |||
| 470ee81928 |
@@ -0,0 +1 @@
|
||||
{"extends": ["standard"], "parser": "babel-eslint"}
|
||||
@@ -40,3 +40,6 @@ hardwareVoltageOutputAll.txt
|
||||
|
||||
# For git
|
||||
*.orig
|
||||
|
||||
# Text editor temporary files
|
||||
.*.sw* # vi/vim
|
||||
|
||||
+2
-1
@@ -17,4 +17,5 @@ node_modules
|
||||
.DS_Store
|
||||
public
|
||||
myOutput.txt
|
||||
*.tgz
|
||||
*.tgz
|
||||
openBCISerialFormat
|
||||
|
||||
@@ -3,11 +3,20 @@ node_js:
|
||||
- "4.0"
|
||||
- "4.1"
|
||||
- "4.2"
|
||||
- "4.3"
|
||||
- "4.4"
|
||||
- "4.5"
|
||||
- "4.6"
|
||||
- "5.11.0"
|
||||
- "6.0"
|
||||
- "6.1"
|
||||
- "6.2"
|
||||
- "6.3"
|
||||
- "6.4"
|
||||
- "6.5"
|
||||
- "6.6"
|
||||
- "6.7"
|
||||
- "6.8"
|
||||
install:
|
||||
- npm install --all
|
||||
script:
|
||||
|
||||
+46
-21
@@ -4,6 +4,7 @@
|
||||
[](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS)
|
||||
[](https://david-dm.org/OpenBCI/OpenBCI_NodeJS)
|
||||
[](http://npmjs.com/package/openbci)
|
||||
[](https://github.com/Flet/semistandard)
|
||||
|
||||
# OpenBCI Node.js SDK
|
||||
|
||||
@@ -24,11 +25,10 @@ The purpose of this module is to **get connected** and **start streaming** as fa
|
||||
1. [Constructor](#constructor)
|
||||
2. [Methods](#method)
|
||||
3. [Events](#event)
|
||||
4. [Properties](#property)
|
||||
5. [Constants](#constants)
|
||||
4. [Constants](#constants)
|
||||
6. [Interfacing With Other Tools](#interfacing-with-other-tools)
|
||||
7. [Developing](#developing)
|
||||
8. [Testing](#testing)
|
||||
8. [Testing](#developing-testing)
|
||||
9. [Contribute](#contribute)
|
||||
10. [License](#license)
|
||||
11. [Roadmap](#roadmap)
|
||||
@@ -38,7 +38,7 @@ The purpose of this module is to **get connected** and **start streaming** as fa
|
||||
npm install openbci
|
||||
```
|
||||
### <a name="tldr"></a> TL;DR:
|
||||
Get connected and start streaming right now
|
||||
Get connected and [start streaming right now with the example code](examples/getStreaming/getStreaming.js).
|
||||
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
@@ -71,7 +71,7 @@ Want to know if the module really works? Check out some projects and organizatio
|
||||
|
||||
Still not satisfied it works?? Check out this [detailed report](http://s132342840.onlinehome.us/pushtheworld/files/voltageVerificationTestPlanAndResults.pdf) that scientifically validates the output voltages of this module.
|
||||
|
||||
How are you still doubting and not using this already? Fine, go look at some of the [700 **_automatic_** tests](https://codecov.io/github/OpenBCI/openbci-js-sdk?branch=master) written for it!
|
||||
How are you still doubting and not using this already? Fine, go look at some of the [700 **_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS) written for it!
|
||||
|
||||
### <a name="general-overview"></a> General Overview:
|
||||
|
||||
@@ -118,6 +118,15 @@ ourBoard.connect(k.OBCISimulatorPortName) // This will set `simulate` to true
|
||||
});
|
||||
```
|
||||
|
||||
To debug, it's amazing, do:
|
||||
|
||||
```js
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
simulate: true
|
||||
});
|
||||
```
|
||||
ps: go [checkout out the example](examples/debug/debug.js) to do it right now!
|
||||
|
||||
'ready' event
|
||||
------------
|
||||
@@ -388,18 +397,26 @@ Board optional configurations.
|
||||
* `simulatorFirmwareVersion` {String} - Allows the simulator to use firmware version 2 features. (2 Possible Options)
|
||||
* `v1` - Firmware Version 1 (Default)
|
||||
* `v2` - Firmware Version 2
|
||||
* `simulatorFragmentation` {String} - Specifies how to break packets to simulate fragmentation, which occurs commonly in real devices. It is recommended to test code with this enabled. (4 Possible Options)
|
||||
* `none` - do not fragment packets; output complete chunks immediately when produced (Default)
|
||||
* `random` - output random small chunks of data interspersed with full buffers
|
||||
* `fullBuffers` - allow buffers to fill up until latency timer has expired
|
||||
* `oneByOne` - output each byte separately
|
||||
* `simulatorLatencyTime` {Number} - The time in milliseconds to wait before sending partially full buffers of data, if `simulatorFragmentation` is specified. (Default `16`)
|
||||
* `simulatorBufferSize` {Number} - The size of a full buffer of data, if `simulatorFragmentation` is specified. (Default `4096`)
|
||||
* `simulatorHasAccelerometer` - {Boolean} - Sets simulator to send packets with accelerometer data. (Default `true`)
|
||||
* `simulatorInjectAlpha` - {Boolean} - Inject a 10Hz alpha wave in Channels 1 and 2 (Default `true`)
|
||||
* `simulatorInjectLineNoise` {String} - Injects line noise on channels. (3 Possible Options)
|
||||
* `60Hz` - 60Hz line noise (Default) [America]
|
||||
* `50Hz` - 50Hz line noise [Europe]
|
||||
* `None` - Do not inject line noise.
|
||||
* `none` - Do not inject line noise.
|
||||
* `simulatorSampleRate` {Number} - The sample rate to use for the simulator. Simulator will set to 125 if `simulatorDaisyModuleAttached` is set `true`. However, setting this option overrides that setting and this sample rate will be used. (Default is `250`)
|
||||
* `simulatorSerialPortFailure` {Boolean} - Simulates not being able to open a serial connection. Most likely due to a OpenBCI dongle not being plugged in.
|
||||
* `sntpTimeSync` - {Boolean} Syncs the module up with an SNTP time server and uses that as single source of truth instead of local computer time. If you are running experiments on your local computer, keep this `false`. (Default `false`)
|
||||
* `sntpTimeSyncHost` - {String} The sntp server to use, can be either sntp or ntp (Defaults `pool.ntp.org`).
|
||||
* `sntpTimeSyncPort` - {Number} The port to access the sntp server (Defaults `123`)
|
||||
* `verbose` {Boolean} - Print out useful debugging events (Default `false`)
|
||||
* `debug` {Boolean} - Print out a raw dump of bytes sent and received (Default `false`)
|
||||
|
||||
**Note, we have added support for either all lowercase OR camel case for the options, use whichever style you prefer.**
|
||||
|
||||
@@ -670,6 +687,16 @@ Sends command to turn off impedances for all channels and stop continuously calc
|
||||
|
||||
**_Returns_** a promise, that fulfills when all the commands are sent to the internal write buffer
|
||||
|
||||
### <a name="method-is-connected"></a> .isConnected()
|
||||
|
||||
Checks if the driver is connected to a board.
|
||||
**_Returns_** a boolean, true if connected
|
||||
|
||||
### <a name="method-is-streaming"></a> .isStreaming()
|
||||
|
||||
Checks if the board is currently sending samples.
|
||||
**_Returns_** a boolean, true if streaming
|
||||
|
||||
### <a name="method-list-ports"></a> .listPorts()
|
||||
|
||||
List available ports so the user can choose a device when not automatically found.
|
||||
@@ -828,7 +855,7 @@ To leave simulate mode.
|
||||
|
||||
### <a name="method-sntp"></a> .sntp
|
||||
|
||||
Extends the popular STNP package on [npmjs](https://www.npmjs.com/package/sntp)
|
||||
Extends the popular SNTP package on [npmjs](https://www.npmjs.com/package/sntp)
|
||||
|
||||
### <a name="method-sntp-get-offset"></a> .sntpGetOffset()
|
||||
|
||||
@@ -895,6 +922,7 @@ Send the command to tell the board to start the syncing protocol. Must be connec
|
||||
timeRoundTrip: 0, // Simply timeSyncSetPacket - timeSyncSent.
|
||||
timeTransmission: 0, // Estimated time it took for time sync set packet to be sent from Board to Driver.
|
||||
timeOffset: 0, // The map (or translation) from boardTime to module time.
|
||||
timeOffsetMaster: 0, // The map (or translation) from boardTime to module time averaged over time syncs.
|
||||
valid: false // If there was an error in the process, valid will be false and no time sync was done. It's important to resolve this so we can perform multiple promise syncs as show in the example below.
|
||||
}
|
||||
```
|
||||
@@ -1016,13 +1044,13 @@ Either a single character or an Array of characters
|
||||
|
||||
Sends a single character command to the board.
|
||||
```js
|
||||
// ourBoard has fulfilled the promise on .connected() and 'ready' has been observed previously
|
||||
// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously
|
||||
ourBoard.write('a');
|
||||
```
|
||||
|
||||
Sends an array of bytes
|
||||
```js
|
||||
// ourBoard has fulfilled the promise on .connected() and 'ready' has been observed previously
|
||||
// ourBoard has fulfilled the promise on .connect() and 'ready' has been observed previously
|
||||
ourBoard.write(['x','0','1','0','0','0','0','0','0','X']);
|
||||
```
|
||||
|
||||
@@ -1068,15 +1096,9 @@ Emitted when the board is in a ready to start streaming state.
|
||||
|
||||
Emitted when there is a new sample available.
|
||||
|
||||
## <a name="property"></a> Properties:
|
||||
### <a name="event-sample"></a> .on('synced', callback)
|
||||
|
||||
### <a name="property-connected"></a> connected
|
||||
|
||||
A bool, true if connected to an OpenBCI board, false if not.
|
||||
|
||||
### <a name="property-streaming"></a> streaming
|
||||
|
||||
A bool, true if streaming data from an OpenBCI board, false if not.
|
||||
Emitted when there is a new sample available.
|
||||
|
||||
## <a name="constants"></a> Constants:
|
||||
|
||||
@@ -1153,11 +1175,14 @@ npm test
|
||||
## <a name="contribute"></a> Contribute:
|
||||
|
||||
1. Fork it!
|
||||
2. Branch off of `development`: `git checkout development`
|
||||
2. Create your feature branch: `git checkout -b my-new-feature`
|
||||
3. Make changes and ensure tests all pass. (`npm test`)
|
||||
4. Commit your changes: `git commit -m 'Add some feature'`
|
||||
5. Push to the branch: `git push origin my-new-feature`
|
||||
6. Submit a pull request :D
|
||||
3. Make changes
|
||||
4. If adding a feature, please add test coverage.
|
||||
5. Ensure tests all pass. (`npm test`)
|
||||
6. Commit your changes: `git commit -m 'Add some feature'`
|
||||
7. Push to the branch: `git push origin my-new-feature`
|
||||
8. Submit a pull request. Make sure it is based off of the `development` branch when submitting! :D
|
||||
|
||||
## <a name="license"></a> License:
|
||||
|
||||
|
||||
@@ -1,3 +1,75 @@
|
||||
# 1.4.2
|
||||
|
||||
### New examples
|
||||
* Add example of debug
|
||||
* Add example of get streaming
|
||||
|
||||
# 1.4.1
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes bug where extra data after EOT (`$$$`) was dumped by preserving the poriton after the EOT for further decomposition.
|
||||
* Fixes bug where any calls to channel set would actually break the openBCISample code as the channelSettingsArray contained an undefined.
|
||||
* Writes promises resolve when they are actually sent over the serial port.
|
||||
|
||||
# 1.4.0
|
||||
|
||||
### New Features
|
||||
|
||||
* Three new initialization options: `simulatorFragmentation`, `simulatorBufferSize`, and `simulatorLatencyTimer`. Together, these enable a more _realistic_ serial port simulation, mimicking different potential user computer systems.
|
||||
* New option `debug` gives a live dump of serial traffic on the console if enabled
|
||||
* New API function `.isConnected()` to check if communications are active.
|
||||
* New API function `.isStreaming()` to check if samples are coming from the board.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Implement and adapt semi-standard code style. Closes #83
|
||||
* autoFindOpenBCIBoard now notices and uses the stock dongle on Linux
|
||||
* 'synced' object now has `error` property, null on good syncs, error description on bad syncs.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The setting for simulatorInjectLineNoise has changed from `None` to `none`
|
||||
* connect() will now fail if already connected
|
||||
* The constructor will throw an error now if an invalid option is passed
|
||||
* The `.connected` property has been removed, replaced by `.isConnected()`. Removed from docs.
|
||||
* The `.streaming` property has been removed, replaced by `.isStreaming()`. Removed from docs.
|
||||
* An error event will be emitted if sntp fails to initialize on construction
|
||||
* The simulator will no longer communicate when disconnected
|
||||
* Promises returned by writes will now only resolve after the write has been sent
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed bug where early packet fragments were dropped after board reset
|
||||
* Fixed bug where time sync replies that began a buffered chunk were ignored
|
||||
* Fixed bug where simulator would output wrong version in its reset message
|
||||
* Fixed bug where resources were not cleaned up if connect was called twice
|
||||
* Fixed bug where serial data was written after disconnection
|
||||
* Fixed bug where unexpected disconnection was not detected
|
||||
* Fixed bug where promises could lead to out of order packet processing.
|
||||
|
||||
# 1.3.3
|
||||
|
||||
### New Features
|
||||
|
||||
* Add `timeOffsetMaster` to object emitted when bad time sync.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed log statement on impedance setting function
|
||||
* Remove event emitter with time sync on reject of sync clock full
|
||||
|
||||
# 1.3.2
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Added master time offset `timeOffsetMaster` to `syncObj` which is a running average across sync attempts.
|
||||
|
||||
# 1.3.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed bug where `connected` and `streaming` were not set in constructor
|
||||
|
||||
# 1.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* This is an example of debugging the board. Thanks to Karl @baffo32
|
||||
* On windows you should run with PowerShell not git bash.
|
||||
* Install
|
||||
* [nodejs](https://nodejs.org/en/)
|
||||
*
|
||||
* To run:
|
||||
* change directory to this file `cd examples/debug`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
|
||||
var stream = true;
|
||||
var debug = true; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* Only works if one board is plugged in
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
// Call to connect
|
||||
ourBoard.connect(portName).then(() => {
|
||||
console.log(`connected`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect: ${err}`);
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The board is ready to start streaming after the ready function is fired.
|
||||
*/
|
||||
var readyFunc = () => {
|
||||
// Get the sample rate after 'ready'
|
||||
sampleRate = ourBoard.sampleRate();
|
||||
if (stream) {
|
||||
ourBoard.streamStart()
|
||||
.catch(err => {
|
||||
console.log(`stream start: ${err}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var sampleFunc = sample => {
|
||||
/**
|
||||
* Checkout the README.md for all other API functions.
|
||||
* We support every feature.
|
||||
* */
|
||||
};
|
||||
|
||||
// Subscribe to your functions
|
||||
ourBoard.on('ready', readyFunc);
|
||||
ourBoard.on('sample', sampleFunc);
|
||||
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
if (options.exit) {
|
||||
if (verbose) console.log('exit');
|
||||
if (stream) {
|
||||
ourBoard.streamStop().catch(console.log);
|
||||
}
|
||||
ourBoard.disconnect().catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
});
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, {
|
||||
cleanup: true
|
||||
}));
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "debug",
|
||||
"version": "1.0.0",
|
||||
"description": "Debug example",
|
||||
"main": "debug.js",
|
||||
"scripts": {
|
||||
"start": "node debug.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"debug"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* This is an example from the readme.md
|
||||
* On windows you should run with PowerShell not git bash.
|
||||
* Install
|
||||
* [nodejs](https://nodejs.org/en/)
|
||||
*
|
||||
* To run:
|
||||
* change directory to this file `cd examples/debug`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
var debug = false; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* Only works if one board is plugged in
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
ourBoard.connect(portName) // Port name is a serial port name, see `.listPorts()`
|
||||
.then(() => {
|
||||
ourBoard.on('ready',() => {
|
||||
ourBoard.streamStart();
|
||||
ourBoard.on('sample',(sample) => {
|
||||
/** Work with sample */
|
||||
for (var i = 0; i < ourBoard.numberOfChannels(); i++) {
|
||||
console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts.");
|
||||
// prints to the console
|
||||
// "Channel 1: 0.00001987 Volts."
|
||||
// "Channel 2: 0.00002255 Volts."
|
||||
// ...
|
||||
// "Channel 8: -0.00001875 Volts."
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
if (options.exit) {
|
||||
if (verbose) console.log('exit');
|
||||
ourBoard.disconnect().catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
});
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, {
|
||||
cleanup: true
|
||||
}));
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "get-streaming",
|
||||
"version": "1.0.0",
|
||||
"description": "Get streaming example",
|
||||
"main": "getStreaming.js",
|
||||
"scripts": {
|
||||
"start": "node getStreaming.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"get"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import json
|
||||
import sys
|
||||
import numpy as np
|
||||
import time
|
||||
import zmq
|
||||
|
||||
|
||||
class Interface:
|
||||
def __init__(self, verbose=False):
|
||||
context = zmq.Context()
|
||||
self._socket = context.socket(zmq.PAIR)
|
||||
self._socket.connect("tcp://localhost:3004")
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
if self.verbose:
|
||||
print "Client Ready!"
|
||||
|
||||
# Send a quick message to tell node process we are up and running
|
||||
self.send(json.dumps({
|
||||
'action': 'started',
|
||||
'command': 'status',
|
||||
'message': time.time()*1000.0
|
||||
}))
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Sends a message to TCP server
|
||||
:param msg: str
|
||||
A string to send to node TCP server, could be a JSON dumps...
|
||||
:return: None
|
||||
"""
|
||||
if self.verbose:
|
||||
print '<- out ' + msg
|
||||
self._socket.send(msg)
|
||||
return
|
||||
|
||||
def recv(self):
|
||||
"""
|
||||
Checks the ZeroMQ for data
|
||||
:return: str
|
||||
String of data
|
||||
"""
|
||||
return self._socket.recv()
|
||||
|
||||
|
||||
class RingBuffer(np.ndarray):
|
||||
"""A multidimensional ring buffer."""
|
||||
|
||||
def __new__(cls, input_array):
|
||||
obj = np.asarray(input_array).view(cls)
|
||||
return obj
|
||||
|
||||
def __array_finalize__(self, obj):
|
||||
if obj is None:
|
||||
return
|
||||
|
||||
def __array_wrap__(self, out_arr, context=None):
|
||||
return np.ndarray.__array_wrap__(self, out_arr, context)
|
||||
|
||||
def append(self, x):
|
||||
"""Adds element x to the ring buffer."""
|
||||
x = np.asarray(x)
|
||||
self[:, :-1] = self[:, 1:]
|
||||
self[:, -1] = x
|
||||
|
||||
|
||||
def main(argv):
|
||||
nb_chan = 8
|
||||
verbose = True
|
||||
|
||||
# Create a new python interface.
|
||||
interface = Interface(verbose=verbose)
|
||||
# Signal buffer
|
||||
signal = RingBuffer(np.zeros((nb_chan + 1, 2500)))
|
||||
|
||||
while True:
|
||||
msg = interface.recv()
|
||||
try:
|
||||
dicty = json.loads(msg)
|
||||
action = dicty.get('action')
|
||||
command = dicty.get('command')
|
||||
message = dicty.get('message')
|
||||
|
||||
if command == 'sample':
|
||||
if action == 'process':
|
||||
# Do sample processing here
|
||||
try:
|
||||
if type(message) is not dict:
|
||||
print "sample is not a dict", message
|
||||
raise ValueError
|
||||
# Get keys of sample
|
||||
data = np.zeros(9)
|
||||
|
||||
data[:-1] = message.get('channelData')
|
||||
data[-1] = message.get('timeStamp')
|
||||
|
||||
# Add data to end of ring buffer
|
||||
signal.append(data)
|
||||
|
||||
print message.get('sampleNumber')
|
||||
except ValueError as e:
|
||||
print e
|
||||
elif command == 'status':
|
||||
if action == 'active':
|
||||
interface.send(json.dumps({
|
||||
'action': 'alive',
|
||||
'command': 'status',
|
||||
'message': time.time() * 1000.0
|
||||
}))
|
||||
|
||||
except BaseException as e:
|
||||
print e
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* This is an example from the readme.md
|
||||
* On windows you should run with PowerShell not git bash.
|
||||
* Install
|
||||
* [nodejs](https://nodejs.org/en/)
|
||||
*
|
||||
* To run:
|
||||
* change directory to this file `cd examples/debug`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
var port_pub = 'tcp://127.0.0.1:3004';
|
||||
var zmq = require('zmq-prebuilt');
|
||||
var socket = zmq.socket('pair');
|
||||
var simulate = true; // Sends synthetic data
|
||||
var debug = false; // Pretty print any bytes in and out... it's amazing...
|
||||
var verbose = true; // Adds verbosity to functions
|
||||
|
||||
var ourBoard = new OpenBCIBoard({
|
||||
simulate: simulate,
|
||||
simulatorFirmwareVersion: 'v2',
|
||||
debug: debug,
|
||||
verbose: verbose
|
||||
});
|
||||
|
||||
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
|
||||
var timeSyncPossible = false;
|
||||
var resyncPeriodMin = 1;
|
||||
var secondsInMinute = 60;
|
||||
var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute;
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
// Call to connect
|
||||
ourBoard.connect(portName)
|
||||
.then(() => {
|
||||
ourBoard.on('ready', () => {
|
||||
|
||||
// Get the sample rate after 'ready'
|
||||
sampleRate = ourBoard.sampleRate();
|
||||
// Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties.
|
||||
timeSyncPossible = ourBoard.usingVersionTwoFirmware();
|
||||
|
||||
if (timeSyncPossible) {
|
||||
ourBoard.streamStart()
|
||||
.catch(err => {
|
||||
console.log(`stream start: ${err}`);
|
||||
});
|
||||
} else {
|
||||
console.log('not able to time sync');
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect: ${err}`);
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
console.log('Unable to auto find OpenBCI board');
|
||||
}
|
||||
});
|
||||
|
||||
var sampleFunc = sample => {
|
||||
if (sample._count % resyncPeriod === 0) {
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
// Sync was successful
|
||||
if (syncObj.valid) {
|
||||
// Log the object to check it out!
|
||||
console.log(`timeOffset`, syncObj.timeOffsetMaster);
|
||||
} else {
|
||||
// Retry it
|
||||
console.log(`Was not able to sync... retry!`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sample.timeStamp) { // true after the first successful sync
|
||||
if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours
|
||||
console.log(`Bad time sync ${sample.timeStamp}`);
|
||||
} else {
|
||||
sendToPython({
|
||||
action: 'process',
|
||||
command: 'sample',
|
||||
message: sample
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to your functions
|
||||
ourBoard.on('sample', sampleFunc);
|
||||
|
||||
// ZMQ fun
|
||||
|
||||
socket.bind(port_pub, function (err) {
|
||||
if (err) throw err;
|
||||
console.log(`bound to ${port_pub}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Used to send a message to the Python process.
|
||||
* @param {Object} interProcessObject The standard inter-process object.
|
||||
* @return {None}
|
||||
*/
|
||||
var sendToPython = (interProcessObject, verbose) => {
|
||||
if (verbose) {
|
||||
console.log(`<- out ${JSON.stringify(interProcessObject)}`);
|
||||
}
|
||||
if (socket) {
|
||||
socket.send(JSON.stringify(interProcessObject));
|
||||
}
|
||||
};
|
||||
|
||||
var receiveFromPython = (raw_data) => {
|
||||
try {
|
||||
let body = JSON.parse(raw_data); // five because `resp `
|
||||
processInterfaceObject(body);
|
||||
} catch (err) {
|
||||
console.log('in -> ' + 'bad json');
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('message', receiveFromPython);
|
||||
|
||||
var sendStatus = () => {
|
||||
sendToPython({'action': 'active', 'message': 'ready', 'command': 'status'}, true);
|
||||
};
|
||||
|
||||
sendStatus();
|
||||
|
||||
/**
|
||||
* Process an incoming message
|
||||
* @param {String} body A stringify JSON object that shall be parsed.
|
||||
* @return {None}
|
||||
*/
|
||||
var processInterfaceObject = (body) => {
|
||||
switch (body.command) {
|
||||
case 'status':
|
||||
processStatus(body);
|
||||
break;
|
||||
default:
|
||||
unrecognizedCommand(body);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to process a status related command from TCP IPC.
|
||||
* @param {Object} body
|
||||
* @return {None}
|
||||
*/
|
||||
var processStatus = (body) => {
|
||||
switch (body.action) {
|
||||
case 'started':
|
||||
console.log(`python started @ ${body.message}ms`);
|
||||
break;
|
||||
case 'alive':
|
||||
console.log(`python duplex communication test completed @ ${body.message}ms`);
|
||||
break;
|
||||
default:
|
||||
unrecognizedCommand(body);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function unrecognizedCommand (body) {
|
||||
console.log(`unrecognizedCommand ${body}`);
|
||||
}
|
||||
|
||||
function exitHandler (options, err) {
|
||||
if (options.cleanup) {
|
||||
if (verbose) console.log('clean');
|
||||
/** Do additional clean up here */
|
||||
}
|
||||
if (err) console.log(err.stack);
|
||||
if (options.exit) {
|
||||
if (verbose) console.log('exit');
|
||||
ourBoard.disconnect().catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on("SIGINT", function () {
|
||||
process.emit("SIGINT");
|
||||
});
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, {
|
||||
cleanup: true
|
||||
}));
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {
|
||||
exit: true
|
||||
}));
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "python",
|
||||
"version": "1.0.0",
|
||||
"description": "node to python example",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"",
|
||||
"start-node": "node index.js",
|
||||
"start-verbose": "concurrently --kill-others \"python handoff.py -v\" \"node index.js\"",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"python",
|
||||
"openbci",
|
||||
"node"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.4.2",
|
||||
"zmq-prebuilt": "^2.1.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "<=6.x",
|
||||
"npm": ">=3.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# OpenBCI Node SDK to Python
|
||||
|
||||
## About
|
||||
|
||||
Written to end the struggles of python researchers and developers. ~ written with love by [Push The World!](http://www.pushtheworldllc.com)
|
||||
|
||||
This module has every feature available on the OpenBCI Board.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* [Python 2.7](https://www.python.org/downloads/)
|
||||
* [ZeroMQ](http://zeromq.org/bindings:python)
|
||||
```python
|
||||
pip install pyzmq
|
||||
```
|
||||
* [Node.js LTS](https://nodejs.org/en/)
|
||||
|
||||
|
||||
## Installation
|
||||
For Python 2.7 do:
|
||||
```bash
|
||||
python setup.py install
|
||||
```
|
||||
For Node:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running
|
||||
```
|
||||
npm start
|
||||
```
|
||||
Verbose:
|
||||
```
|
||||
npm run start-verbose
|
||||
```
|
||||
For running just the node, for example if you were running the python in a separate ide and debugging, it's useful.
|
||||
```python
|
||||
npm run start-node
|
||||
```
|
||||
|
||||
## Contributing
|
||||
Please PR if you have code to contribute!
|
||||
@@ -0,0 +1,12 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='openbci-node-python',
|
||||
version='0.0.1',
|
||||
description='Node to Python the right way',
|
||||
url='',
|
||||
author='AJ Keller',
|
||||
author_email='pushtheworldllc@gmail.com',
|
||||
license='MIT',
|
||||
packages=find_packages(),
|
||||
install_requires=['numpy', 'pyzmq'],
|
||||
zip_safe=False)
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "timesync",
|
||||
"version": "1.0.0",
|
||||
"description": "Time sync example",
|
||||
"main": "timeSync.js",
|
||||
"scripts": {
|
||||
"start": "node timeSync.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"time",
|
||||
"sync"
|
||||
],
|
||||
"author": "AJ Keller",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"openbci": "^1.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* This is an example of time syncing every second for one minute. Used with a
|
||||
* real board.
|
||||
* To run:
|
||||
* change directory to this file `cd examples/timeSync`
|
||||
* do `npm install`
|
||||
* then `npm start`
|
||||
*/
|
||||
|
||||
var OpenBCIBoard = require('openbci').OpenBCIBoard;
|
||||
|
||||
var ourBoard = new OpenBCIBoard({});
|
||||
|
||||
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
|
||||
var timeSyncPossible = false;
|
||||
|
||||
ourBoard.autoFindOpenBCIBoard().then(portName => {
|
||||
if (portName) {
|
||||
/**
|
||||
* Connect to the board with portName
|
||||
* i.e. ourBoard.connect(portName).....
|
||||
*/
|
||||
// Call to connect
|
||||
ourBoard.connect(portName).then(() => {
|
||||
console.log(`connected`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`connect: ${err}`);
|
||||
});
|
||||
} else {
|
||||
/** Unable to auto find OpenBCI board */
|
||||
}
|
||||
});
|
||||
|
||||
var readyFunc = () => {
|
||||
// Get the sample rate after 'ready'
|
||||
sampleRate = ourBoard.sampleRate();
|
||||
// Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties.
|
||||
timeSyncPossible = ourBoard.usingVersionTwoFirmware();
|
||||
|
||||
if (timeSyncPossible) {
|
||||
ourBoard.streamStart()
|
||||
.catch(err => {
|
||||
console.log(`stream start: ${err}`);
|
||||
});
|
||||
} else {
|
||||
killFunc();
|
||||
}
|
||||
};
|
||||
|
||||
var killFunc = () => {
|
||||
ourBoard.disconnect()
|
||||
.then(() => {
|
||||
process.kill();
|
||||
});
|
||||
};
|
||||
|
||||
var sampleFunc = sample => {
|
||||
// Resynchronize every every second
|
||||
if (sample._count % (sampleRate * 1) === 0) {
|
||||
ourBoard.syncClocksFull()
|
||||
.then(syncObj => {
|
||||
// Sync was successful
|
||||
if (syncObj.valid) {
|
||||
// Log the object to check it out!
|
||||
console.log(`timeOffset`, syncObj.timeOffsetMaster);
|
||||
} else {
|
||||
// Retry it
|
||||
console.log(`Was not able to sync... retry!`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sample.timeStamp) { // true after the first successful sync
|
||||
if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours
|
||||
console.log(`Bad time sync ${sample.timeStamp}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop after one minute
|
||||
if (sample._count > sampleRate * 60) {
|
||||
killFunc();
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to your functions
|
||||
ourBoard.on('ready', readyFunc);
|
||||
ourBoard.on('sample', sampleFunc);
|
||||
+2336
-2208
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+1082
-1038
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+1182
-1196
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+422
-323
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var stream = require('stream');
|
||||
@@ -8,355 +7,455 @@ var openBCISample = require('./openBCISample');
|
||||
var k = openBCISample.k;
|
||||
var now = require('performance-now');
|
||||
|
||||
function OpenBCISimulatorFactory () {
|
||||
var factory = this;
|
||||
|
||||
function OpenBCISimulatorFactory() {
|
||||
var factory = this;
|
||||
var _options = {
|
||||
accel: true,
|
||||
alpha: true,
|
||||
boardFailure: false,
|
||||
daisy: false,
|
||||
drift: 0,
|
||||
firmwareVersion: [k.OBCIFirmwareV1, k.OBCIFirmwareV2],
|
||||
fragmentation: [k.OBCISimulatorFragmentationNone, k.OBCISimulatorFragmentationRandom, k.OBCISimulatorFragmentationFullBuffers, k.OBCISimulatorFragmentationOneByOne],
|
||||
latencyTime: 16,
|
||||
bufferSize: 4096,
|
||||
lineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone],
|
||||
sampleRate: 250,
|
||||
serialPortFailure: false,
|
||||
verbose: false
|
||||
};
|
||||
|
||||
var _options = {
|
||||
accel: true,
|
||||
alpha: true,
|
||||
boardFailure:false,
|
||||
daisy: false,
|
||||
drift: 0,
|
||||
firmwareVersion: k.OBCIFirmwareV1,
|
||||
lineNoise: '60Hz',
|
||||
sampleRate: 250,
|
||||
serialPortFailure:false,
|
||||
verbose: false
|
||||
};
|
||||
function OpenBCISimulator (portName, options) {
|
||||
options = (typeof options !== 'function') && options || {};
|
||||
var opts = {};
|
||||
|
||||
function OpenBCISimulator(portName, options) {
|
||||
options = (typeof options !== 'function') && options || {};
|
||||
var opts = {};
|
||||
stream.Stream.call(this);
|
||||
|
||||
stream.Stream.call(this);
|
||||
/** Configuring Options */
|
||||
var o;
|
||||
for (o in _options) {
|
||||
var userValue = options[o];
|
||||
delete options[o];
|
||||
|
||||
/** Configuring Options */
|
||||
if (options.accel === false) {
|
||||
opts.accel = false;
|
||||
if (typeof _options[o] === 'object') {
|
||||
// an array specifying a list of choices
|
||||
// if the choice is not in the list, the first one is defaulted to
|
||||
|
||||
if (_options[o].indexOf(userValue) !== -1) {
|
||||
opts[o] = userValue;
|
||||
} else {
|
||||
opts.accel = _options.accel;
|
||||
opts[o] = _options[o][0];
|
||||
}
|
||||
if (options.alpha === false) {
|
||||
opts.alpha = false;
|
||||
} else {
|
||||
// anything else takes the user value if provided, otherwise is a default
|
||||
|
||||
if (userValue !== undefined) {
|
||||
opts[o] = userValue;
|
||||
} else {
|
||||
opts.alpha = _options.alpha;
|
||||
opts[o] = _options[o];
|
||||
}
|
||||
opts.boardFailure = options.boardFailure || _options.boardFailure;
|
||||
opts.daisy = options.daisy || _options.daisy;
|
||||
opts.drift = options.drift || _options.drift;
|
||||
opts.firmwareVersion = options.firmwareVersion || _options.firmwareVersion;
|
||||
opts.lineNoise = options.lineNoise || _options.lineNoise;
|
||||
if (options.sampleRate) {
|
||||
opts.sampleRate = options.sampleRate;
|
||||
} else {
|
||||
opts.sampleRate = k.OBCISampleRate250;
|
||||
}
|
||||
opts.serialPortFailure = options.serialPortFailure || _options.serialPortFailure;
|
||||
opts.verbose = options.verbose || _options.verbose;
|
||||
|
||||
this.options = opts;
|
||||
|
||||
// Bools
|
||||
this.connected = false;
|
||||
this.sd = {
|
||||
active:false,
|
||||
startTime: 0
|
||||
};
|
||||
this.streaming = false;
|
||||
this.synced = false;
|
||||
this.sendSyncSetPacket = false;
|
||||
// Buffers
|
||||
this.buffer = new Buffer(500);
|
||||
// Numbers
|
||||
this.channelNumber = 1;
|
||||
this.hostChannelNumber = this.channelNumber;
|
||||
this.pollTime = 80;
|
||||
this.sampleNumber = -1; // So the first sample is 0
|
||||
// Objects
|
||||
this.sampleGenerator = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, this.options.sampleRate, this.options.alpha, this.options.lineNoise);
|
||||
this.time = {
|
||||
current: 0,
|
||||
start: now(),
|
||||
loop: null
|
||||
};
|
||||
// Strings
|
||||
this.portName = portName || k.OBCISimulatorPortName;
|
||||
|
||||
// Call 'open'
|
||||
if (this.options.verbose) console.log(`Port name: ${portName}`);
|
||||
setTimeout(() => {
|
||||
this.emit('open');
|
||||
this.connected = true;
|
||||
}, 200);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to use the emitter class freely outside of the module
|
||||
util.inherits(OpenBCISimulator, stream.Stream);
|
||||
for (o in options) throw new Error('"' + o + '" is not a valid option');
|
||||
|
||||
OpenBCISimulator.prototype.flush = function() {
|
||||
this.buffer.fill(0);
|
||||
//if (this.options.verbose) console.log('flushed');
|
||||
this.options = opts;
|
||||
|
||||
// Bools
|
||||
this.connected = false;
|
||||
this.sd = {
|
||||
active: false,
|
||||
startTime: 0
|
||||
};
|
||||
this.streaming = false;
|
||||
this.synced = false;
|
||||
this.sendSyncSetPacket = false;
|
||||
// Buffers
|
||||
this.outputBuffer = new Buffer(this.options.bufferSize);
|
||||
this.outputBuffered = 0;
|
||||
// Numbers
|
||||
this.channelNumber = 1;
|
||||
this.hostChannelNumber = this.channelNumber;
|
||||
this.pollTime = 80;
|
||||
this.sampleNumber = -1; // So the first sample is 0
|
||||
// Objects
|
||||
this.sampleGenerator = openBCISample.randomSample(k.OBCINumberOfChannelsDefault, this.options.sampleRate, this.options.alpha, this.options.lineNoise);
|
||||
this.time = {
|
||||
current: 0,
|
||||
start: now(),
|
||||
loop: null
|
||||
};
|
||||
// Strings
|
||||
this.portName = portName || k.OBCISimulatorPortName;
|
||||
|
||||
OpenBCISimulator.prototype.write = function(data,callback) {
|
||||
switch (data[0]) {
|
||||
case k.OBCIRadioKey:
|
||||
this._processPrivateRadioMessage(data);
|
||||
break;
|
||||
case k.OBCIStreamStart:
|
||||
if (!this.stream) this._startStream();
|
||||
this.streaming = true;
|
||||
break;
|
||||
case k.OBCIStreamStop:
|
||||
if (this.stream) clearInterval(this.stream); // Stops the stream
|
||||
this.streaming = false;
|
||||
break;
|
||||
case k.OBCIMiscSoftReset:
|
||||
if (this.stream) clearInterval(this.stream);
|
||||
this.streaming = false;
|
||||
this.emit('data', new Buffer(`OpenBCI V3 Simulator\nOn Board ADS1299 Device ID: 0x12345\n${this.options.daisy ? "On Daisy ADS1299 Device ID: 0xFFFFF\n" : ""}LIS3DH Device ID: 0x38422\n${this.options.firmware === k.OBCIFirmwareV2 ? "Firmware: v2.0.0\n" : ""}$$$`));
|
||||
break;
|
||||
case k.OBCISDLogForHour1:
|
||||
case k.OBCISDLogForHour2:
|
||||
case k.OBCISDLogForHour4:
|
||||
case k.OBCISDLogForHour12:
|
||||
case k.OBCISDLogForHour24:
|
||||
case k.OBCISDLogForMin5:
|
||||
case k.OBCISDLogForMin15:
|
||||
case k.OBCISDLogForMin30:
|
||||
case k.OBCISDLogForSec14:
|
||||
// If we are not streaming, then do verbose output
|
||||
if (!this.streaming) {
|
||||
this.emit('data', new Buffer('Wiring is correct and a card is present.\nCorresponding SD file OBCI_69.TXT\n$$$'));
|
||||
}
|
||||
this.sd.active = true;
|
||||
this.sd.startTime = now();
|
||||
break;
|
||||
case k.OBCISDLogStop:
|
||||
if (!this.streaming) {
|
||||
if (this.SDLogActive) {
|
||||
this.emit('data', new Buffer(`Total Elapsed Time: ${now() - this.sd.startTime} ms\n`));
|
||||
this.emit('data', new Buffer(`Max write time: ${Math.random()*500} us\n`));
|
||||
this.emit('data', new Buffer(`Min write time: ${Math.random()*200} us\n`));
|
||||
this.emit('data', new Buffer(`Overruns: 0\n`));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this.emit('data', new Buffer('No open file to close\n'));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
this.SDLogActive = false;
|
||||
break;
|
||||
case k.OBCISyncTimeSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this.synced = true;
|
||||
setTimeout(() => {
|
||||
this.emit('data', new Buffer(k.OBCISyncTimeSent));
|
||||
this._syncUp();
|
||||
}, 10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// Call 'open'
|
||||
if (this.options.verbose) console.log(`Port name: ${portName}`);
|
||||
setTimeout(() => {
|
||||
this.connected = true;
|
||||
this.emit('open');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// This allows us to use the emitter class freely outside of the module
|
||||
// TODO: upgrade from old-style streams to stream.Duplex or stream.Transform
|
||||
util.inherits(OpenBCISimulator, stream.Stream);
|
||||
|
||||
OpenBCISimulator.prototype.flush = function (callback) {
|
||||
this.outputBuffered = 0;
|
||||
|
||||
clearTimeout(this.outputLoopHandle);
|
||||
this.outputLoopHandle = null;
|
||||
|
||||
if (callback) callback();
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype.isOpen = function () {
|
||||
return this.connected;
|
||||
};
|
||||
|
||||
// output only size bytes of the output buffer
|
||||
OpenBCISimulator.prototype._partialDrain = function (size) {
|
||||
if (!this.connected) throw new Error('not connected');
|
||||
|
||||
if (size > this.outputBuffered) size = this.outputBuffered;
|
||||
|
||||
// buffer is copied because presently openBCIBoard.js reuses it
|
||||
var outBuffer = new Buffer(this.outputBuffer.slice(0, size));
|
||||
|
||||
this.outputBuffer.copy(this.outputBuffer, 0, size, this.outputBuffered);
|
||||
this.outputBuffered -= size;
|
||||
|
||||
this.emit('data', outBuffer);
|
||||
};
|
||||
|
||||
// queue some data for output and send it out depending on options.fragmentation
|
||||
OpenBCISimulator.prototype._output = function (dataBuffer) {
|
||||
// drain full buffers until there is no overflow
|
||||
while (this.outputBuffered + dataBuffer.length > this.outputBuffer.length) {
|
||||
var len = dataBuffer.copy(this.outputBuffer, this.outputBuffered);
|
||||
dataBuffer = dataBuffer.slice(len);
|
||||
this.outputBuffered += len;
|
||||
|
||||
this._partialDrain(this.outputBuffered);
|
||||
this.flush();
|
||||
}
|
||||
|
||||
dataBuffer.copy(this.outputBuffer, this.outputBuffered);
|
||||
this.outputBuffered += dataBuffer.length;
|
||||
|
||||
if (!this.outputLoopHandle) {
|
||||
var latencyTime = this.options.latencyTime;
|
||||
if (this.options.fragmentation === k.OBCISimulatorFragmentationOneByOne ||
|
||||
this.options.fragmentation === k.OBCISimulatorFragmentationNone) {
|
||||
// no need to wait for latencyTime
|
||||
// note that this is the only difference between 'none' and 'fullBuffers'
|
||||
latencyTime = 0;
|
||||
}
|
||||
var outputLoop = () => {
|
||||
var size;
|
||||
switch (this.options.fragmentation) {
|
||||
case k.OBCISimulatorFragmentationRandom:
|
||||
if (Math.random() < 0.5) {
|
||||
// randomly picked to send out a fragment
|
||||
size = Math.ceil(Math.random() * Math.max(this.outputBuffered, 62));
|
||||
break;
|
||||
} // else, randomly picked to send a complete buffer in next block
|
||||
/* falls through */
|
||||
case k.OBCISimulatorFragmentationFullBuffers:
|
||||
case k.OBCISimulatorFragmentationNone:
|
||||
case false:
|
||||
size = this.outputBuffered;
|
||||
break;
|
||||
case k.OBCISimulatorFragmentationOneByOne:
|
||||
size = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/** Handle Callback */
|
||||
if (this.connected) {
|
||||
callback(null,'Success!');
|
||||
this._partialDrain(size);
|
||||
if (this.outputBuffered) {
|
||||
this.outputLoopHandle = setTimeout(outputLoop, latencyTime);
|
||||
} else {
|
||||
this.outputLoopHandle = null;
|
||||
}
|
||||
};
|
||||
};
|
||||
if (latencyTime === 0) {
|
||||
outputLoop();
|
||||
} else {
|
||||
this.outputLoopHandle = setTimeout(outputLoop, latencyTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype.drain = function(callback) {
|
||||
callback();
|
||||
};
|
||||
OpenBCISimulator.prototype.write = function (data, callback) {
|
||||
if (!this.connected) {
|
||||
if (callback) callback('Not connected');
|
||||
else throw new Error('Not connected!');
|
||||
return;
|
||||
}
|
||||
|
||||
OpenBCISimulator.prototype.close = function(callback) {
|
||||
if (this.connected) {
|
||||
this.emit('close');
|
||||
// TODO: this function assumes a type of Buffer for radio, and a type of String otherwise
|
||||
// FIX THIS it makes it unusable outside the api code
|
||||
switch (data[0]) {
|
||||
case k.OBCIRadioKey:
|
||||
this._processPrivateRadioMessage(data);
|
||||
break;
|
||||
case k.OBCIStreamStart:
|
||||
if (!this.stream) this._startStream();
|
||||
this.streaming = true;
|
||||
break;
|
||||
case k.OBCIStreamStop:
|
||||
if (this.stream) clearInterval(this.stream); // Stops the stream
|
||||
this.streaming = false;
|
||||
break;
|
||||
case k.OBCIMiscSoftReset:
|
||||
if (this.stream) clearInterval(this.stream);
|
||||
this.streaming = false;
|
||||
this._output(new Buffer(`OpenBCI V3 Simulator On Board ADS1299 Device ID: 0x12345 ${this.options.daisy ? `On Daisy ADS1299 Device ID: 0xFFFFF\n` : ``} LIS3DH Device ID: 0x38422 ${this.options.firmwareVersion === k.OBCIFirmwareV2 ? `Firmware: v2.0.0\n` : ``}$$$`));
|
||||
break;
|
||||
case k.OBCISDLogForHour1:
|
||||
case k.OBCISDLogForHour2:
|
||||
case k.OBCISDLogForHour4:
|
||||
case k.OBCISDLogForHour12:
|
||||
case k.OBCISDLogForHour24:
|
||||
case k.OBCISDLogForMin5:
|
||||
case k.OBCISDLogForMin15:
|
||||
case k.OBCISDLogForMin30:
|
||||
case k.OBCISDLogForSec14:
|
||||
// If we are not streaming, then do verbose output
|
||||
if (!this.streaming) {
|
||||
this._output(new Buffer('Wiring is correct and a card is present.\nCorresponding SD file OBCI_69.TXT\n$$$'));
|
||||
}
|
||||
this.connected = false;
|
||||
callback();
|
||||
this.sd.active = true;
|
||||
this.sd.startTime = now();
|
||||
break;
|
||||
case k.OBCISDLogStop:
|
||||
if (!this.streaming) {
|
||||
if (this.SDLogActive) {
|
||||
this._output(new Buffer(`Total Elapsed Time: ${now() - this.sd.startTime} ms`));
|
||||
this._output(new Buffer(`Max write time: ${Math.random() * 500} us`));
|
||||
this._output(new Buffer(`Min write time: ${Math.random() * 200} us`));
|
||||
this._output(new Buffer(`Overruns: 0`));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._output(new Buffer('No open file to close\n'));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
this.SDLogActive = false;
|
||||
break;
|
||||
case k.OBCISyncTimeSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this.synced = true;
|
||||
setTimeout(() => {
|
||||
this._output(new Buffer(k.OBCISyncTimeSent));
|
||||
this._syncUp();
|
||||
}, 10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** Handle Callback */
|
||||
if (callback) {
|
||||
callback(null, 'Success!');
|
||||
}
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype.drain = function (callback) {
|
||||
if (callback) callback();
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype.close = function (callback) {
|
||||
if (this.connected) {
|
||||
this.flush();
|
||||
|
||||
if (this.stream) clearInterval(this.stream);
|
||||
|
||||
this.connected = false;
|
||||
this.emit('close');
|
||||
if (callback) callback();
|
||||
} else {
|
||||
if (callback) callback('not connected');
|
||||
}
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._startStream = function () {
|
||||
var intervalInMS = 1000 / this.options.sampleRate;
|
||||
|
||||
if (intervalInMS < 2) intervalInMS = 2;
|
||||
|
||||
var getNewPacket = sampNumber => {
|
||||
if (this.options.accel) {
|
||||
if (this.synced) {
|
||||
if (this.sendSyncSetPacket) {
|
||||
this.sendSyncSetPacket = false;
|
||||
return openBCISample.convertSampleToPacketAccelTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0));
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketAccelTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0));
|
||||
}
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketStandard(this.sampleGenerator(sampNumber));
|
||||
}
|
||||
} else {
|
||||
if (this.synced) {
|
||||
if (this.sendSyncSetPacket) {
|
||||
this.sendSyncSetPacket = false;
|
||||
return openBCISample.convertSampleToPacketRawAuxTimeSyncSet(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0]));
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketRawAuxTimeSynced(this.sampleGenerator(sampNumber), now().toFixed(0), new Buffer([0, 0, 0, 0, 0, 0]));
|
||||
}
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketRawAux(this.sampleGenerator(sampNumber), new Buffer([0, 0, 0, 0, 0, 0]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._startStream = function() {
|
||||
var intervalInMS = 1000 / this.options.sampleRate;
|
||||
this.stream = setInterval(() => {
|
||||
this._output(getNewPacket(this.sampleNumber));
|
||||
this.sampleNumber++;
|
||||
}, intervalInMS);
|
||||
};
|
||||
|
||||
if (intervalInMS < 2) intervalInMS = 2;
|
||||
OpenBCISimulator.prototype._syncUp = function () {
|
||||
setTimeout(() => {
|
||||
this.sendSyncSetPacket = true;
|
||||
}, 12); // 3 packets later
|
||||
};
|
||||
|
||||
var getNewPacket = sampNumber => {
|
||||
if (this.options.accel) {
|
||||
if (this.synced) {
|
||||
if (this.sendSyncSetPacket) {
|
||||
this.sendSyncSetPacket = false;
|
||||
return openBCISample.convertSampleToPacketAccelTimeSyncSet(this.sampleGenerator(sampNumber),now().toFixed(0));
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketAccelTimeSynced(this.sampleGenerator(sampNumber),now().toFixed(0));
|
||||
}
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketStandard(this.sampleGenerator(sampNumber));
|
||||
}
|
||||
OpenBCISimulator.prototype._printEOT = function () {
|
||||
this._output(new Buffer('$$$'));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printFailure = function () {
|
||||
this._output(new Buffer('Failure: '));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printSuccess = function () {
|
||||
this._output(new Buffer('Success: '));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printValidatedCommsTimeout = function () {
|
||||
this._printFailure();
|
||||
this._output(new Buffer('Communications timeout - Device failed to poll Host'));
|
||||
this._printEOT();
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._processPrivateRadioMessage = function (dataBuffer) {
|
||||
switch (dataBuffer[1]) {
|
||||
case k.OBCIRadioCmdChannelGet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this._output(new Buffer(`Host and Device on Channel Number ${this.channelNumber}`));
|
||||
this._output(new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
} else if (!this.serialPortFailure) {
|
||||
this._printFailure();
|
||||
this._output(new Buffer(`Host on Channel Number ${this.channelNumber}`));
|
||||
this._output(new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdChannelSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
if (dataBuffer[2] <= k.OBCIRadioChannelMax) {
|
||||
this.channelNumber = dataBuffer[2];
|
||||
this.hostChannelNumber = this.channelNumber;
|
||||
this._printSuccess();
|
||||
this._output(new Buffer(`Channel Number ${this.channelNumber}`));
|
||||
this._output(new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
if (this.synced) {
|
||||
if (this.sendSyncSetPacket) {
|
||||
this.sendSyncSetPacket = false;
|
||||
return openBCISample.convertSampleToPacketRawAuxTimeSyncSet(this.sampleGenerator(sampNumber),now().toFixed(0),new Buffer([0,0,0,0,0,0]));
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketRawAuxTimeSynced(this.sampleGenerator(sampNumber),now().toFixed(0),new Buffer([0,0,0,0,0,0]));
|
||||
}
|
||||
} else {
|
||||
return openBCISample.convertSampleToPacketRawAux(this.sampleGenerator(sampNumber),new Buffer([0,0,0,0,0,0]));
|
||||
}
|
||||
|
||||
this._printFailure();
|
||||
this._output(new Buffer('Verify channel number is less than 25'));
|
||||
this._printEOT();
|
||||
}
|
||||
};
|
||||
|
||||
this.stream = setInterval(() => {
|
||||
this.emit('data', getNewPacket(this.sampleNumber));
|
||||
this.sampleNumber++;
|
||||
}, intervalInMS);
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._syncUp = function() {
|
||||
setTimeout(() => {
|
||||
this.sendSyncSetPacket = true;
|
||||
}, 12); // 3 packets later
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printEOT = function () {
|
||||
this.emit('data', new Buffer("$$$"));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printFailure = function () {
|
||||
this.emit('data', new Buffer("Failure: "));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printSuccess = function () {
|
||||
this.emit('data', new Buffer("Success: "));
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._printValidatedCommsTimeout = function () {
|
||||
this._printFailure();
|
||||
this.emit('data', new Buffer("Communications timeout - Device failed to poll Host"));
|
||||
this._printEOT();
|
||||
};
|
||||
|
||||
OpenBCISimulator.prototype._processPrivateRadioMessage = function(dataBuffer) {
|
||||
switch (dataBuffer[1]) {
|
||||
case k.OBCIRadioCmdChannelGet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer(`Host and Device on Channel Number ${this.channelNumber}`));
|
||||
this.emit('data', new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
} else if (!this.serialPortFailure) {
|
||||
this._printFailure();
|
||||
this.emit('data', new Buffer(`Host on Channel Number ${this.channelNumber}`));
|
||||
this.emit('data', new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdChannelSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
if (dataBuffer[2] < k.OBCIRadioChannelMax) {
|
||||
this.channelNumber = dataBuffer[2];
|
||||
this.hostChannelNumber = this.channelNumber;
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer(`Channel Number ${this.channelNumber}`));
|
||||
this.emit('data', new Buffer([this.channelNumber]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printFailure();
|
||||
this.emit('data', new Buffer("Verify channel number is less than 25"));
|
||||
this._printEOT();
|
||||
}
|
||||
} else if (!this.serialPortFailure) {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdChannelSetOverride:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (dataBuffer[2] < k.OBCIRadioChannelMax) {
|
||||
if (dataBuffer[2] === this.channelNumber) {
|
||||
this.options.boardFailure = false;
|
||||
} else {
|
||||
this.options.boardFailure = true;
|
||||
}
|
||||
this.hostChannelNumber = dataBuffer[2];
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer(`Host override - Channel Number ${this.hostChannelNumber}`));
|
||||
this.emit('data', new Buffer([this.hostChannelNumber]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printFailure();
|
||||
this.emit('data', new Buffer("Verify channel number is less than 25"));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdPollTimeGet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer(`Poll Time ${this.pollTime}`));
|
||||
this.emit('data', new Buffer([this.pollTime]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdPollTimeSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this.pollTime = dataBuffer[2];
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer(`Poll Time ${this.pollTime}`));
|
||||
this.emit('data', new Buffer([this.pollTime]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdBaudRateSetDefault:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer("Switch your baud rate to 115200"));
|
||||
this.emit('data', new Buffer([0x24,0x24,0x24,0xFF])); // The board really does this
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdBaudRateSetFast:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer("Switch your baud rate to 230400"));
|
||||
this.emit('data', new Buffer([0x24,0x24,0x24,0xFF])); // The board really does this
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdSystemStatus:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this.emit('data', new Buffer("System is Up"));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printFailure();
|
||||
this.emit('data', new Buffer("System is Down"));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} else if (!this.serialPortFailure) {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case k.OBCIRadioCmdChannelSetOverride:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (dataBuffer[2] <= k.OBCIRadioChannelMax) {
|
||||
if (dataBuffer[2] === this.channelNumber) {
|
||||
this.options.boardFailure = false;
|
||||
} else {
|
||||
this.options.boardFailure = true;
|
||||
}
|
||||
this.hostChannelNumber = dataBuffer[2];
|
||||
this._printSuccess();
|
||||
this._output(new Buffer(`Host override - Channel Number ${this.hostChannelNumber}`));
|
||||
this._output(new Buffer([this.hostChannelNumber]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printFailure();
|
||||
this._output(new Buffer('Verify channel number is less than 25'));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdPollTimeGet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this._output(new Buffer(`Poll Time ${this.pollTime}`));
|
||||
this._output(new Buffer([this.pollTime]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdPollTimeSet:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this.pollTime = dataBuffer[2];
|
||||
this._printSuccess();
|
||||
this._output(new Buffer(`Poll Time ${this.pollTime}`));
|
||||
this._output(new Buffer([this.pollTime]));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printValidatedCommsTimeout();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdBaudRateSetDefault:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this._printSuccess();
|
||||
this._output(new Buffer('Switch your baud rate to 115200'));
|
||||
this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdBaudRateSetFast:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
this._printSuccess();
|
||||
this._output(new Buffer('Switch your baud rate to 230400'));
|
||||
this._output(new Buffer([0x24, 0x24, 0x24, 0xFF])); // The board really does this
|
||||
}
|
||||
break;
|
||||
case k.OBCIRadioCmdSystemStatus:
|
||||
if (this.options.firmwareVersion === k.OBCIFirmwareV2) {
|
||||
if (!this.options.boardFailure) {
|
||||
this._printSuccess();
|
||||
this._output(new Buffer('System is Up'));
|
||||
this._printEOT();
|
||||
} else {
|
||||
this._printFailure();
|
||||
this._output(new Buffer('System is Down'));
|
||||
this._printEOT();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
factory.OpenBCISimulator = OpenBCISimulator;
|
||||
factory.OpenBCISimulator = OpenBCISimulator;
|
||||
}
|
||||
|
||||
util.inherits(OpenBCISimulatorFactory, EventEmitter);
|
||||
|
||||
+19
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "openbci",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.2",
|
||||
"description": "The official Node.js SDK for the OpenBCI Biosensor Board.",
|
||||
"main": "openBCIBoard",
|
||||
"scripts": {
|
||||
"start": "node index",
|
||||
"test": "mocha test",
|
||||
"test": "semistandard | snazzy && mocha test",
|
||||
"test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov"
|
||||
},
|
||||
"keywords": [
|
||||
@@ -27,14 +27,17 @@
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bluebird": "3.4.6",
|
||||
"chai": "^3.4.1",
|
||||
"chai-as-promised": "^5.2.0",
|
||||
"codecov": "^1.0.1",
|
||||
"istanbul": "^0.4.4",
|
||||
"mocha": "^3.0.2",
|
||||
"sandboxed-module": "^2.0.3",
|
||||
"semistandard": "^9.0.0",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0"
|
||||
"sinon-chai": "^2.8.0",
|
||||
"snazzy": "^5.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -46,5 +49,18 @@
|
||||
"homepage": "https://github.com/openbci/openbci_nodejs#readme",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"semistandard": {
|
||||
"globals": [
|
||||
"describe",
|
||||
"context",
|
||||
"before",
|
||||
"beforeEach",
|
||||
"after",
|
||||
"afterEach",
|
||||
"it",
|
||||
"expect",
|
||||
"should"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+1426
-1160
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+1221
-1375
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,142 @@
|
||||
var timingEventsAsPromises = require('./timingEventsAsPromises');
|
||||
exports.BluebirdPromise = require('bluebird');
|
||||
exports.PromiseIgnored = global.Promise;
|
||||
|
||||
// Enable bluebird for all promise usage during tests only
|
||||
// Fails tests for issues bluebird finds
|
||||
// Exports a function to list all promises (getPendingPromises)
|
||||
// Exports a function to verify no promises pending within a timeout (noPendingPromises)
|
||||
|
||||
exports.BluebirdPromise.config({
|
||||
// TODO: wForgottenReturn is disabled because timingEventsAsPromises triggers it; find a workaround
|
||||
warnings: { wForgottenReturn: false },
|
||||
longStackTraces: true,
|
||||
monitoring: true,
|
||||
cancellation: true
|
||||
});
|
||||
|
||||
// nextTick conveniently not instrumented by timingEventsAsPromises
|
||||
exports.BluebirdPromise.setScheduler(process.nextTick);
|
||||
|
||||
// unhandled rejections become test failures
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
if (!(reason instanceof Error)) {
|
||||
reason = new Error('unhandled promise rejection: ' + reason);
|
||||
} else {
|
||||
reason.message = 'unhandled promise rejection: ' + reason.message;
|
||||
}
|
||||
process.nextTick(() => { throw reason; });
|
||||
});
|
||||
|
||||
// // warnings become test failures
|
||||
// process.on('warning', (warning) => {
|
||||
// var error = new Error(warning);
|
||||
// process.nextTick(() => { throw error; });
|
||||
// });
|
||||
|
||||
// provide access to all currently pending promises
|
||||
var pendingPromises = {};
|
||||
var promiseId = 0;
|
||||
var nested = 0;
|
||||
|
||||
function promiseCreationHandler (promise) {
|
||||
// promise created already resolved; ignore
|
||||
if (!promise.isPending()) return;
|
||||
|
||||
// need to create another promise to get access to the extended stack trace
|
||||
// nested detects if we are inside our own dummy promise
|
||||
++nested;
|
||||
if (nested === 1) {
|
||||
// not the dummy promise
|
||||
promise.___id = ++promiseId;
|
||||
// store promise details
|
||||
var error = new Error('Promise ' + promise.___id + ' is still pending');
|
||||
var entry = {
|
||||
promise: promise,
|
||||
id: promise.___id,
|
||||
error: error
|
||||
};
|
||||
pendingPromises[promise.___id] = entry;
|
||||
// extract stack trace by rejecting an error; bluebird fills in expanded stack
|
||||
exports.BluebirdPromise.reject(error).catch(error => {
|
||||
entry.error = error;
|
||||
entry.stack = error.stack;
|
||||
});
|
||||
} else {
|
||||
promise.___nested = nested;
|
||||
}
|
||||
--nested;
|
||||
}
|
||||
process.on('promiseCreated', promiseCreationHandler);
|
||||
|
||||
function promiseDoneHandler (promise) {
|
||||
if (promise.___nested) return;
|
||||
delete pendingPromises[promise.___id];
|
||||
}
|
||||
process.on('promiseFulfilled', promiseDoneHandler);
|
||||
process.on('promiseRejected', promiseDoneHandler);
|
||||
process.on('promiseResolved', promiseDoneHandler);
|
||||
process.on('promiseCancelled', promiseDoneHandler);
|
||||
|
||||
exports.getPendingPromises = function () {
|
||||
var ret = [];
|
||||
for (var promise in pendingPromises) {
|
||||
ret.push(pendingPromises[promise]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
exports.noPendingPromises = function (milliseconds) {
|
||||
if (!milliseconds) milliseconds = 0;
|
||||
|
||||
return new exports.PromiseIgnored((resolve, reject) => {
|
||||
function waited100 () {
|
||||
var promises = exports.getPendingPromises();
|
||||
|
||||
if (promises.length === 0) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (milliseconds > 0) {
|
||||
milliseconds -= 100;
|
||||
return timingEventsAsPromises.setTimeoutIgnored(waited100, 100);
|
||||
}
|
||||
|
||||
// timed out, but promises remaining: cancel all
|
||||
|
||||
console.log(promises.length + ' promises still pending');
|
||||
|
||||
promises.forEach(promise => {
|
||||
promise.promise.cancel();
|
||||
});
|
||||
|
||||
// report one
|
||||
reject(promises[0].error);
|
||||
}
|
||||
|
||||
timingEventsAsPromises.setTimeoutIgnored(waited100, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// now instrument the Promise object itself to always use a simplified version of bluebird
|
||||
// bluebird is composed inside a bare-bones Promise object providing only the official calls
|
||||
|
||||
global.Promise = function (handler) {
|
||||
this._promise = new exports.BluebirdPromise(handler);
|
||||
};
|
||||
|
||||
// compose class methods
|
||||
['all', 'race', 'reject', 'resolve'].forEach(classMethod => {
|
||||
global.Promise[classMethod] = function () {
|
||||
return exports.BluebirdPromise[classMethod].apply(exports.BluebirdPromise, [].slice.call(arguments));
|
||||
};
|
||||
Object.defineProperty(global.Promise[classMethod], 'name', { value: 'Promise.' + classMethod });
|
||||
});
|
||||
|
||||
// compose object methods
|
||||
['then', 'catch'].forEach(objectMethod => {
|
||||
global.Promise.prototype[objectMethod] = function () {
|
||||
return this._promise[objectMethod].apply(this._promise, [].slice.call(arguments));
|
||||
};
|
||||
Object.defineProperty(global.Promise.prototype[objectMethod], 'name', { value: 'Promise.' + objectMethod });
|
||||
});
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+3285
-2916
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+813
-687
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,108 @@
|
||||
// Converts timing events to use promises, to gain bluebird's checks
|
||||
|
||||
function instrumentTimingEvents (module, type) {
|
||||
// replaces module[type] with a function that does the same thing but uses a promise
|
||||
// assumes the first argument will be to a callback function
|
||||
|
||||
// if this a setSomething function with a clearSomething partner, the partner will also be instrumented
|
||||
var setName = type;
|
||||
var clearName = null;
|
||||
if (type.substring(0, 3) === 'set') {
|
||||
clearName = 'clear' + type.substring(3);
|
||||
}
|
||||
|
||||
if (!module[setName]) return;
|
||||
|
||||
// store original functions
|
||||
var originalSet = module[setName];
|
||||
var originalClear = module[clearName];
|
||||
exports[setName + 'Ignored'] = originalSet;
|
||||
|
||||
// dictionary to store promise details for each call
|
||||
var events = {};
|
||||
|
||||
// setAsPromise() is the brunt of the function. It replaces the previous global function,
|
||||
// and sets up a promise to resolve when the callback is called
|
||||
var eventCount = 0;
|
||||
var setAsPromise = function (callback) {
|
||||
var args = [].slice.call(arguments);
|
||||
|
||||
var eventNum = ++eventCount;
|
||||
var eventHandle;
|
||||
|
||||
if (setAsPromise._ignoreCount > 0) {
|
||||
--setAsPromise._ignoreCount;
|
||||
|
||||
return originalSet.apply(this, args);
|
||||
}
|
||||
|
||||
// actual callback is replaced by promise resolve
|
||||
args[0] = function () {
|
||||
if (!events[eventNum]) throw new Error(setName + ' ' + eventNum + ' disappeared');
|
||||
events[eventNum].resolve([].slice.call(arguments));
|
||||
};
|
||||
|
||||
eventHandle = originalSet.apply(this, args) || eventNum;
|
||||
|
||||
// this portion is a function so that setInterval may be handled via recursion
|
||||
function dispatch () {
|
||||
var handlerDetails = { handle: eventHandle };
|
||||
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
handlerDetails.resolve = resolve;
|
||||
handlerDetails.reject = reject;
|
||||
});
|
||||
|
||||
handlerDetails.promise = promise;
|
||||
events[eventNum] = handlerDetails;
|
||||
|
||||
promise.then(function (argumentArray) {
|
||||
if (type !== 'setInterval') {
|
||||
delete events[eventNum];
|
||||
} else {
|
||||
dispatch();
|
||||
}
|
||||
|
||||
// call original handler
|
||||
callback.apply(this, argumentArray);
|
||||
}, () => {
|
||||
originalClear(eventHandle);
|
||||
delete events[eventNum];
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
dispatch();
|
||||
|
||||
return eventNum;
|
||||
};
|
||||
|
||||
// actually replace functions with instrumented ones
|
||||
setAsPromise._ignoreCount = 0;
|
||||
module[setName] = setAsPromise;
|
||||
Object.defineProperty(setAsPromise, 'name', { value: setName + 'AsPromise' });
|
||||
|
||||
if (clearName) {
|
||||
module[clearName] = (eventNum) => {
|
||||
if (!events[eventNum]) {
|
||||
originalClear(eventNum);
|
||||
} else {
|
||||
events[eventNum].reject(new Error('cleared'));
|
||||
}
|
||||
};
|
||||
Object.defineProperty(module[clearName], 'name', { value: clearName + 'AsPromise' });
|
||||
}
|
||||
}
|
||||
|
||||
instrumentTimingEvents(global, 'setTimeout');
|
||||
instrumentTimingEvents(global, 'setInterval');
|
||||
instrumentTimingEvents(global, 'setImmediate');
|
||||
// // Possible TODO: nextTick needs some exceptions included to prevent infinite recursion
|
||||
// instrumentTimingEvents(process, 'nextTick');
|
||||
|
||||
// the next call to the passed function should not be promisified
|
||||
// may be queued multiple times
|
||||
exports.ignoreOnce = (ignored) => {
|
||||
ignored._ignoreCount ++;
|
||||
};
|
||||
@@ -1,135 +0,0 @@
|
||||
// This takes a openbci-sdk factory and mocks the shit out of it in complete isolation per require of this file
|
||||
|
||||
"use strict";
|
||||
|
||||
var Hardware = function () {
|
||||
this.bciBoard = {};
|
||||
//this.mockBinding = {
|
||||
// connect: this.connect.bind(this),
|
||||
// disconnect: this.disconnect.bind(this),
|
||||
// streamStart: this.streamStart.bind(this),
|
||||
// streamStop: this.streamStop.bind(this),
|
||||
// write: this.write.bind(this),
|
||||
// softReset: this.softReset.bind(this),
|
||||
// autoFindOpenBCIBoard: this.autoFindOpenBCIBoard.bind(this),
|
||||
// simulatorEnable: this.simulatorEnable.bind(this),
|
||||
// simulatorDisable: this.simulatorDisable.bind(this)
|
||||
//};
|
||||
};
|
||||
|
||||
Hardware.prototype.reset = function () {
|
||||
this.bciBoard = {};
|
||||
};
|
||||
|
||||
Hardware.prototype.createBoard = function () {
|
||||
this.bciBoard = {
|
||||
serial: null,
|
||||
connected: false,
|
||||
streaming: false,
|
||||
isSimulating: false,
|
||||
badPackets: 0,
|
||||
bytesIn: 0,
|
||||
commandsToWrite: 0,
|
||||
sampleCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
Hardware.prototype.connect = function (portName) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.connected = true;
|
||||
this.bciBoard.serial = {
|
||||
portName:portName,
|
||||
baudRate:115200
|
||||
};
|
||||
resolve(this.bciBoard.serial);
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.disconnect = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.connected = false;
|
||||
this.bciBoard.serial = null;
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.streamStart = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.streaming = true;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.streamStop = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.streaming = false;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.simulatorEnable = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.isSimulating = true;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.simulatorDisable = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.isSimulating = false;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.autoFindOpenBCIBoard = function () {
|
||||
return new Promise((resolve,reject) => {
|
||||
resolve('/dev/tty.usbserial-D069XXX');
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.emitSample = function (data) {
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!this.bciBoard) reject('Board does not exist - call hardware.createBoard() first');
|
||||
|
||||
this.bciBoard.isSimulating = false;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
Hardware.prototype.fakeData = function (data) {
|
||||
//do something, not quite sure what yet...
|
||||
};
|
||||
|
||||
var hardware = new Hardware();
|
||||
|
||||
var SandboxedModule = require('sandboxed-module');
|
||||
|
||||
var openBCIBoard = SandboxedModule.require('../openBCIBoard', {
|
||||
requires: {
|
||||
fs: {
|
||||
read: hardware.fakeData.bind(hardware)
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
process: {
|
||||
platform: 'darwin',
|
||||
nextTick: process.nextTick
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openBCIBoard.hardware = hardware;
|
||||
|
||||
module.exports = openBCIBoard;
|
||||
Referência em uma Nova Issue
Bloquear um usuário