Inital Commit

Esse commit está contido em:
AJ Keller
2016-11-23 12:50:37 -05:00
commit e91ded9b2a
20 arquivos alterados com 5458 adições e 0 exclusões
+1
Ver Arquivo
@@ -0,0 +1 @@
{"extends": ["standard"], "parser": "babel-eslint"}
+45
Ver Arquivo
@@ -0,0 +1,45 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# Adapted from OpenEXP
.idea
.DS_Store
public
# Test output files
myOutput.txt
hardwareVoltageOutputAll.txt
# Local npm builds for testing end in .tgz
*.tgz
# For git
*.orig
# Text editor temporary files
.*.sw* # vi/vim
+21
Ver Arquivo
@@ -0,0 +1,21 @@
test/
test_mocks/
# MIRRORED FROM .gitignore PLEASE MAINTAIN
logs
*.log
pids
*.pid
*.seed
lib-cov
coverage
.grunt
.lock-wscript
build/Release
node_modules
.idea
.DS_Store
public
myOutput.txt
*.tgz
openBCISerialFormat
+33
Ver Arquivo
@@ -0,0 +1,33 @@
language: node_js
node_js:
- "4.2.3"
- "5.11.0"
- "6.0"
- "6.1"
- "6.2"
- "6.3"
- "6.4"
- "6.5"
- "6.6"
- "6.7"
- "6.8"
- "7.0"
- "7.1"
before_install:
# before_install code came from here: https://github.com/node-inspector/node-inspector/issues/776
- sudo apt-get install python-software-properties
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
- sudo apt-get update
- sudo apt-get install gcc-5 g++-5
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5
- sudo update-alternatives --set gcc /usr/bin/gcc-5
# https://docs.travis-ci.com/user/customizing-the-build
# https://www.howtoinstall.co/en/ubuntu/trusty/libbluetooth-dev
- npm install -g node-pre-gyp-github node-gyp
- sudo apt-get install -qq bluetooth bluez libbluetooth-dev libudev-dev
install:
- npm install --all
script:
- npm run test-cov
+1145
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+3
Ver Arquivo
@@ -0,0 +1,3 @@
# 0.1.0
Initial release
+79
Ver Arquivo
@@ -0,0 +1,79 @@
const Ganglion = require('../../index').Ganglion;
const k = require('../../openBCIConstants');
const verbose = true;
var ganglion = new Ganglion({
// debug: true,
sendCounts: true,
verbose: verbose
});
function errorFunc (err) {
throw err;
}
ganglion.once(k.OBCIEmitterGanglionFound, (peripheral) => {
ganglion.searchStop().catch(errorFunc);
ganglion.on('sample', (sample) => {
/** Work with sample */
console.log(sample.sampleNumber);
// for (var i = 0; i < ganglion.numberOfChannels(); i++) {
// console.log("Channel " + (i + 1) + ": " + sample.channelData[i].toFixed(8) + " Volts.");
// }
});
ganglion.on('message', (message) => {
console.log('message: ', message.toString());
});
ganglion.once('ready', () => {
ganglion.syntheticEnable()
.then(() => {
return ganglion.streamStart();
})
.catch(errorFunc);
console.log('ready');
});
ganglion.connect(peripheral).catch(errorFunc);
});
ganglion.searchStart().catch(errorFunc);
function exitHandler (options, err) {
if (options.cleanup) {
if (verbose) console.log('clean');
// console.log(connectedPeripheral)
ganglion.manualDisconnect = true;
ganglion.disconnect();
}
if (err) console.log(err.stack);
if (options.exit) {
if (verbose) console.log('exit');
if (ganglion.isConnected()) {
ganglion.disconnect()
.then(() => {
process.exit();
})
.catch((err) => {
if (verbose) console.log(err);
process.exit();
});
}
}
}
// 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
}));
+118
Ver Arquivo
@@ -0,0 +1,118 @@
---
swagger: '2.0'
info:
version: 1.0.0
title: OpenBCI Ganglion API
################################################################################
# PATHS #
################################################################################
paths:
/s:
get:
description: Scan for a list of BLE devices
responses:
200:
description: List of ports
407:
description: No ports found
409:
description: Already scanning
/c:
put:
description: Open a BLE connection
parameters:
- name: port
in: query
description: The name of the BLE device to connect to.
type: string
required: true
responses:
200:
description: Port name connected to
402:
description: Unable to open connection to device
405:
description: Device not found
408:
description: Already connected to a device
/d:
delete:
description: Close an open BLE connection
responses:
200:
description: Closed connection
400:
description: No open BLE device
401:
description: Unable to disconnect from device, try again later.
/k:
put:
description: Send a command to an open BLE device
parameters:
- name: command
in: query
description: The command(s) to send to the BLE device
type: string
required: true
responses:
200:
description: Command sent
400:
description: No open BLE device
406:
description: Command not recognized
/q:
get:
description: Get the connection status
responses:
200:
description: true if connected false if not
/e:
post:
description: Error message from
parameters:
- name: code
in: query
description: The error code
type: integer
required: true
- name: msg
in: query
description: The plain text error message
type: string
responses:
200:
description: Error message
/t:
post:
description: Data from the OpenBCI Ganglion board
responses:
200:
description: Good data packet
500:
description: Bad data packet
/i:
post:
description: Impedance Data from the OpenBCI Ganglion board
parameters:
- name: channelNumber
description: The channel number, either 1, 2, 3, 4 or 0 for reference
in: res
type: integer
required: true
- name: impedance value
description: The impedance value in ohms
in: res
type: integer
required: true
/l:
post:
description: Log message from the node
parameters:
- name: msg
in: query
description: The plain test log message
type: string
+3
Ver Arquivo
@@ -0,0 +1,3 @@
module.exports.Cyton = require('./openBCICyton');
module.exports.Ganglion = require('./openBCIGanglion');
module.exports.Constants = require('./openBCIConstants');
+422
Ver Arquivo
@@ -0,0 +1,422 @@
/**
* Created by ajk on 12/16/15.
* Purpose: This file folds all the constants for the
* OpenBCI Board
*/
'use strict';
/** Turning channels off */
const obciChannelOff1 = '1';
const obciChannelOff2 = '2';
const obciChannelOff3 = '3';
const obciChannelOff4 = '4';
/** Turn channels on */
const obciChannelOn1 = '!';
const obciChannelOn2 = '@';
const obciChannelOn3 = '#';
const obciChannelOn4 = '$';
/** SD card Commands */
const obciSDLogForHour1 = 'G';
const obciSDLogForHour2 = 'H';
const obciSDLogForHour4 = 'J';
const obciSDLogForHour12 = 'K';
const obciSDLogForHour24 = 'L';
const obciSDLogForMin5 = 'A';
const obciSDLogForMin15 = 'S';
const obciSDLogForMin30 = 'F';
const obciSDLogForSec14 = 'a';
const obciSDLogStop = 'j';
/** SD Card String Commands */
const obciStringSDHour1 = '1hour';
const obciStringSDHour2 = '2hour';
const obciStringSDHour4 = '4hour';
const obciStringSDHour12 = '12hour';
const obciStringSDHour24 = '24hour';
const obciStringSDMin5 = '5min';
const obciStringSDMin15 = '15min';
const obciStringSDMin30 = '30min';
const obciStringSDSec14 = '14sec';
/** Stream Data Commands */
const obciStreamStart = 'b';
const obciStreamStop = 's';
/** Miscellaneous */
const obciMiscQueryRegisterSettings = '?';
const obciMiscSoftReset = 'v';
/** Possible number of channels */
const obciNumberOfChannelsGanglion = 4;
/** Possible OpenBCI board types */
const obciBoardGanglion = 'ganglion';
/** Possible Simulator Line Noise injections */
const obciSimulatorLineNoiseHz60 = '60Hz';
const obciSimulatorLineNoiseHz50 = '50Hz';
const obciSimulatorLineNoiseNone = 'none';
/** Possible Simulator Fragmentation modes */
const obciSimulatorFragmentationRandom = 'random';
const obciSimulatorFragmentationFullBuffers = 'fullBuffers';
const obciSimulatorFragmentationOneByOne = 'oneByOne';
const obciSimulatorFragmentationNone = 'none';
/** Possible Sample Rates */
const obciSampleRate200 = 200;
/** Errors */
const errorNobleAlreadyScanning = 'Scan already under way';
const errorNobleNotAlreadyScanning = 'No scan started';
const errorNobleNotInPoweredOnState = 'Please turn blue tooth on.';
const errorInvalidByteLength = 'Invalid Packet Byte Length';
const errorInvalidByteStart = 'Invalid Start Byte';
const errorInvalidByteStop = 'Invalid Stop Byte';
const errorInvalidType = 'Invalid Type';
const errorTimeSyncIsNull = "'this.sync.curSyncObj' must not be null";
const errorTimeSyncNoComma = 'Missed the time sync sent confirmation. Try sync again';
const errorUndefinedOrNullInput = 'Undefined or Null Input';
/** Max Master Buffer Size */
const obciMasterBufferSize = 4096;
/** Impedance */
const obciImpedanceTextBad = 'bad';
const obciImpedanceTextNone = 'none';
const obciImpedanceTextGood = 'good';
const obciImpedanceTextInit = 'init';
const obciImpedanceTextOk = 'ok';
const obciImpedanceThresholdGoodMin = 0;
const obciImpedanceThresholdGoodMax = 5000;
const obciImpedanceThresholdOkMin = 5001;
const obciImpedanceThresholdOkMax = 10000;
const obciImpedanceThresholdBadMin = 10001;
const obciImpedanceThresholdBadMax = 1000000;
const obciImpedanceSeriesResistor = 2200; // There is a 2.2 k Ohm series resistor that must be subtracted
/** Simulator */
const obciSimulatorPortName = 'OpenBCISimulator';
/** Parse */
const obciParseFailure = 'Failure';
const obciParseSuccess = 'Success';
/** Simulator Board Configurations */
const obciSimulatorRawAux = 'rawAux';
const obciSimulatorStandard = 'standard';
/** Emitters */
const obciEmitterBlePoweredUp = 'blePoweredOn';
const obciEmitterClose = 'close';
const obciEmitterDroppedPacket = 'droppedPacket';
const obciEmitterError = 'error';
const obciEmitterGanglionFound = 'ganglionFound';
const obciEmitterImpedance = 'impedance';
const obciEmitterMessage = 'message';
const obciEmitterQuery = 'query';
const obciEmitterRawDataPacket = 'rawDataPacket';
const obciEmitterReady = 'ready';
const obciEmitterSample = 'sample';
const obciEmitterSynced = 'synced';
/** Ganglion */
const obciGanglionBleSearchTime = 20000; // ms
const obciGanglionByteIdRawData = 0;
const obciGanglionByteIdSampleMax = 127;
const obciGanglionByteIdSampleMin = 1;
const obciGanglionByteIdAccel = 128;
const obciGanglionByteIdImpedanceChannel1 = 129;
const obciGanglionByteIdImpedanceChannel2 = 130;
const obciGanglionByteIdImpedanceChannel3 = 131;
const obciGanglionByteIdImpedanceChannel4 = 132;
const obciGanglionByteIdImpedanceChannelReference = 133;
const obciGanglionByteIdMultiPacket = 134;
const obciGanglionByteIdMultiPacketStop = 135;
const obciGanglionPacketSize = 20;
const obciGanglionSamplesPerPacket = 2;
const obciGanglionPacket = {
byteId: 0,
dataStart: 1,
dataStop: 20
};
const obciGanglionMCP3912Gain = 1.0; // assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
const obciGanglionMCP3912Vref = 1.2; // reference voltage for ADC in MCP3912 set in hardware
const obciGanglionPrefix = 'Ganglion';
const obciGanglionSyntheticDataEnable = 't';
const obciGanglionSyntheticDataDisable = 'T';
const obciGanglionImpedanceStart = 'z';
const obciGanglionImpedanceStop = 'Z';
const obciGanglionScaleFactorPerCountVolts = obciGanglionMCP3912Vref / (8388607.0 * obciGanglionMCP3912Gain * 1.5 * 51.0);
/** Simblee */
const simbleeUuidService = 'fe84';
const simbleeUuidReceive = '2d30c082f39f4ce6923f3484ea480596';
const simbleeUuidSend = '2d30c083f39f4ce6923f3484ea480596';
const simbleeUuidDisconnect = '2d30c084f39f4ce6923f3484ea480596';
/** Noble */
const obciNobleEmitterPeripheralConnect = 'connect';
const obciNobleEmitterPeripheralDisconnect = 'disconnect';
const obciNobleEmitterPeripheralDiscover = 'discover';
const obciNobleEmitterPeripheralServicesDiscover = 'servicesDiscover';
const obciNobleEmitterServiceCharacteristicsDiscover = 'characteristicsDiscover';
const obciNobleEmitterServiceRead = 'read';
const obciNobleEmitterDiscover = 'discover';
const obciNobleEmitterScanStart = 'scanStart';
const obciNobleEmitterScanStop = 'scanStop';
const obciNobleEmitterStateChange = 'stateChange';
const obciNobleStatePoweredOn = 'poweredOn';
module.exports = {
/** Turning channels off */
OBCIChannelOff1: obciChannelOff1,
OBCIChannelOff2: obciChannelOff2,
OBCIChannelOff3: obciChannelOff3,
OBCIChannelOff4: obciChannelOff4,
/**
* Purpose: To get the proper command to turn a channel off
* @param channelNumber - A number (1-16) of the desired channel
* @returns {Promise}
*/
commandChannelOff: function (channelNumber) {
return new Promise(function (resolve, reject) {
switch (channelNumber) {
case 1:
resolve(obciChannelOff1);
break;
case 2:
resolve(obciChannelOff2);
break;
case 3:
resolve(obciChannelOff3);
break;
case 4:
resolve(obciChannelOff4);
break;
default:
reject('Error [commandChannelOff]: Invalid Channel Number');
break;
}
});
},
/** Turning channels on */
OBCIChannelOn1: obciChannelOn1,
OBCIChannelOn2: obciChannelOn2,
OBCIChannelOn3: obciChannelOn3,
OBCIChannelOn4: obciChannelOn4,
commandChannelOn: function (channelNumber) {
return new Promise(function (resolve, reject) {
switch (channelNumber) {
case 1:
resolve(obciChannelOn1);
break;
case 2:
resolve(obciChannelOn2);
break;
case 3:
resolve(obciChannelOn3);
break;
case 4:
resolve(obciChannelOn4);
break;
default:
reject('Error [commandChannelOn]: Invalid Channel Number');
break;
}
});
},
/** SD card Commands */
OBCISDLogForHour1: obciSDLogForHour1,
OBCISDLogForHour2: obciSDLogForHour2,
OBCISDLogForHour4: obciSDLogForHour4,
OBCISDLogForHour12: obciSDLogForHour12,
OBCISDLogForHour24: obciSDLogForHour24,
OBCISDLogForMin5: obciSDLogForMin5,
OBCISDLogForMin15: obciSDLogForMin15,
OBCISDLogForMin30: obciSDLogForMin30,
OBCISDLogForSec14: obciSDLogForSec14,
OBCISDLogStop: obciSDLogStop,
/** SD Card String Commands */
OBCIStringSDHour1: obciStringSDHour1,
OBCIStringSDHour2: obciStringSDHour2,
OBCIStringSDHour4: obciStringSDHour4,
OBCIStringSDHour12: obciStringSDHour12,
OBCIStringSDHour24: obciStringSDHour24,
OBCIStringSDMin5: obciStringSDMin5,
OBCIStringSDMin15: obciStringSDMin15,
OBCIStringSDMin30: obciStringSDMin30,
OBCIStringSDSec14: obciStringSDSec14,
/**
* @description Converts a sd string into the proper setting.
* @param stringCommand {String} - The length of time you want to record to the SD for.
* @returns {Promise} The command to send to the Board, returns an error on improper `stringCommand`
*/
sdSettingForString: (stringCommand) => {
return new Promise((resolve, reject) => {
switch (stringCommand) {
case obciStringSDHour1:
resolve(obciSDLogForHour1);
break;
case obciStringSDHour2:
resolve(obciSDLogForHour2);
break;
case obciStringSDHour4:
resolve(obciSDLogForHour4);
break;
case obciStringSDHour12:
resolve(obciSDLogForHour12);
break;
case obciStringSDHour24:
resolve(obciSDLogForHour24);
break;
case obciStringSDMin5:
resolve(obciSDLogForMin5);
break;
case obciStringSDMin15:
resolve(obciSDLogForMin15);
break;
case obciStringSDMin30:
resolve(obciSDLogForMin30);
break;
case obciStringSDSec14:
resolve(obciSDLogForSec14);
break;
default:
reject(new Error(TypeError));
break;
}
});
},
/** Stream Data Commands */
OBCIStreamStart: obciStreamStart,
OBCIStreamStop: obciStreamStop,
/** Miscellaneous */
OBCIMiscQueryRegisterSettings: obciMiscQueryRegisterSettings,
OBCIMiscSoftReset: obciMiscSoftReset,
/** Possible number of channels */
OBCINumberOfChannelsGanglion: obciNumberOfChannelsGanglion,
/** Possible OpenBCI board types */
OBCIBoardGanglion: obciBoardGanglion,
/** Possible Sample Rates */
OBCISampleRate200: obciSampleRate200,
/** Errors */
OBCIErrorNobleAlreadyScanning: errorNobleAlreadyScanning,
OBCIErrorNobleNotAlreadyScanning: errorNobleNotAlreadyScanning,
OBCIErrorNobleNotInPoweredOnState: errorNobleNotInPoweredOnState,
OBCIErrorInvalidByteLength: errorInvalidByteLength,
OBCIErrorInvalidByteStart: errorInvalidByteStart,
OBCIErrorInvalidByteStop: errorInvalidByteStop,
OBCIErrorInvalidType: errorInvalidType,
OBCIErrorTimeSyncIsNull: errorTimeSyncIsNull,
OBCIErrorTimeSyncNoComma: errorTimeSyncNoComma,
OBCIErrorUndefinedOrNullInput: errorUndefinedOrNullInput,
/** Max Master Buffer Size */
OBCIMasterBufferSize: obciMasterBufferSize,
/** Impedance Setter Maker */
getImpedanceSetter: impedanceSetter,
/** Impedance */
OBCIImpedanceTextBad: obciImpedanceTextBad,
OBCIImpedanceTextGood: obciImpedanceTextGood,
OBCIImpedanceTextInit: obciImpedanceTextInit,
OBCIImpedanceTextOk: obciImpedanceTextOk,
OBCIImpedanceTextNone: obciImpedanceTextNone,
OBCIImpedanceThresholdBadMax: obciImpedanceThresholdBadMax,
OBCIImpedanceSeriesResistor: obciImpedanceSeriesResistor,
getTextForRawImpedance: (value) => {
if (value > obciImpedanceThresholdGoodMin && value < obciImpedanceThresholdGoodMax) {
return obciImpedanceTextGood;
} else if (value > obciImpedanceThresholdOkMin && value < obciImpedanceThresholdOkMax) {
return obciImpedanceTextOk;
} else if (value > obciImpedanceThresholdBadMin && value < obciImpedanceThresholdBadMax) {
return obciImpedanceTextBad;
} else {
return obciImpedanceTextNone;
}
},
/** Simulator */
OBCISimulatorPortName: obciSimulatorPortName,
/** Possible Simulator Line Noise injections */
OBCISimulatorLineNoiseHz60: obciSimulatorLineNoiseHz60,
OBCISimulatorLineNoiseHz50: obciSimulatorLineNoiseHz50,
OBCISimulatorLineNoiseNone: obciSimulatorLineNoiseNone,
/** Possible Simulator Fragmentation modes */
OBCISimulatorFragmentationRandom: obciSimulatorFragmentationRandom,
OBCISimulatorFragmentationFullBuffers: obciSimulatorFragmentationFullBuffers,
OBCISimulatorFragmentationOneByOne: obciSimulatorFragmentationOneByOne,
OBCISimulatorFragmentationNone: obciSimulatorFragmentationNone,
/** Parse */
OBCIParseFailure: obciParseFailure,
OBCIParseSuccess: obciParseSuccess,
/** Simulator Board Configurations */
OBCISimulatorRawAux: obciSimulatorRawAux,
OBCISimulatorStandard: obciSimulatorStandard,
getVersionNumber,
/** Emitters */
OBCIEmitterBlePoweredUp: obciEmitterBlePoweredUp,
OBCIEmitterClose: obciEmitterClose,
OBCIEmitterDroppedPacket: obciEmitterDroppedPacket,
OBCIEmitterError: obciEmitterError,
OBCIEmitterGanglionFound: obciEmitterGanglionFound,
OBCIEmitterImpedance: obciEmitterImpedance,
OBCIEmitterMessage: obciEmitterMessage,
OBCIEmitterQuery: obciEmitterQuery,
OBCIEmitterRawDataPacket: obciEmitterRawDataPacket,
OBCIEmitterReady: obciEmitterReady,
OBCIEmitterSample: obciEmitterSample,
OBCIEmitterSynced: obciEmitterSynced,
/** Ganglion */
OBCIGanglionBleSearchTime: obciGanglionBleSearchTime,
OBCIGanglionByteIdRawData: obciGanglionByteIdRawData,
OBCIGanglionByteIdSampleMax: obciGanglionByteIdSampleMax,
OBCIGanglionByteIdSampleMin: obciGanglionByteIdSampleMin,
OBCIGanglionByteIdAccel: obciGanglionByteIdAccel,
OBCIGanglionByteIdImpedanceChannel1: obciGanglionByteIdImpedanceChannel1,
OBCIGanglionByteIdImpedanceChannel2: obciGanglionByteIdImpedanceChannel2,
OBCIGanglionByteIdImpedanceChannel3: obciGanglionByteIdImpedanceChannel3,
OBCIGanglionByteIdImpedanceChannel4: obciGanglionByteIdImpedanceChannel4,
OBCIGanglionByteIdImpedanceChannelReference: obciGanglionByteIdImpedanceChannelReference,
OBCIGanglionByteIdMultiPacket: obciGanglionByteIdMultiPacket,
OBCIGanglionByteIdMultiPacketStop: obciGanglionByteIdMultiPacketStop,
OBCIGanglionMCP3912Gain: obciGanglionMCP3912Gain, // assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
OBCIGanglionMCP3912Vref: obciGanglionMCP3912Vref, // reference voltage for ADC in MCP3912 set in hardware
OBCIGanglionPacketSize: obciGanglionPacketSize,
OBCIGanglionPacket: obciGanglionPacket,
OBCIGanglionPrefix: obciGanglionPrefix,
OBCIGanglionSamplesPerPacket: obciGanglionSamplesPerPacket,
OBCIGanglionSyntheticDataEnable: obciGanglionSyntheticDataEnable,
OBCIGanglionSyntheticDataDisable: obciGanglionSyntheticDataDisable,
OBCIGanglionImpedanceStart: obciGanglionImpedanceStart,
OBCIGanglionImpedanceStop: obciGanglionImpedanceStop,
OBCIGanglionScaleFactorPerCountVolts: obciGanglionScaleFactorPerCountVolts,
/** Simblee */
SimbleeUuidService: simbleeUuidService,
SimbleeUuidReceive: simbleeUuidReceive,
SimbleeUuidSend: simbleeUuidSend,
SimbleeUuidDisconnect: simbleeUuidDisconnect,
/** Noble */
OBCINobleEmitterPeripheralConnect: obciNobleEmitterPeripheralConnect,
OBCINobleEmitterPeripheralDisconnect: obciNobleEmitterPeripheralDisconnect,
OBCINobleEmitterPeripheralDiscover: obciNobleEmitterPeripheralDiscover,
OBCINobleEmitterPeripheralServicesDiscover: obciNobleEmitterPeripheralServicesDiscover,
OBCINobleEmitterServiceCharacteristicsDiscover: obciNobleEmitterServiceCharacteristicsDiscover,
OBCINobleEmitterServiceRead: obciNobleEmitterServiceRead,
OBCINobleEmitterDiscover: obciNobleEmitterDiscover,
OBCINobleEmitterScanStart: obciNobleEmitterScanStart,
OBCINobleEmitterScanStop: obciNobleEmitterScanStop,
OBCINobleEmitterStateChange: obciNobleEmitterStateChange,
OBCINobleStatePoweredOn: obciNobleStatePoweredOn
};
/**
* @description This function is used to extract the major version from a github
* version string.
* @returns {Number} The major version number
*/
function getVersionNumber (versionStr) {
return Number(versionStr[1]);
}
+877
Ver Arquivo
@@ -0,0 +1,877 @@
'use strict';
const EventEmitter = require('events').EventEmitter;
const _ = require('lodash');
const noble = require('noble');
const util = require('util');
// Local imports
const utils = require('./openBCIGanglionUtils');
const k = require('./openBCIConstants');
const openBCIUtils = require('./openBCIUtils');
const clone = require('clone');
const _options = {
nobleAutoStart: true,
nobleScanOnPowerOn: true,
sendCounts: false,
simulate: false,
simulatorBoardFailure: false,
simulatorHasAccelerometer: true,
simulatorInternalClockDrift: 0,
simulatorInjectAlpha: true,
simulatorInjectLineNoise: [k.OBCISimulatorLineNoiseHz60, k.OBCISimulatorLineNoiseHz50, k.OBCISimulatorLineNoiseNone],
simulatorSampleRate: 200,
verbose: false,
debug: false
};
function Ganglion (options) {
if (!(this instanceof Ganglion)) {
return new Ganglion(options);
}
options = (typeof options !== 'function') && options || {};
let opts = {};
/** Configuring Options */
let o;
for (o in _options) {
var userOption = (o in options) ? o : o.toLowerCase();
var userValue = options[userOption];
delete options[userOption];
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[o] = _options[o][0];
}
} else {
// anything else takes the user value if provided, otherwise is a default
if (userValue !== undefined) {
opts[o] = userValue;
} else {
opts[o] = _options[o];
}
}
}
for (o in options) throw new Error('"' + o + '" is not a valid option');
// Set to global options object
this.options = clone(opts);
/** Private Properties (keep alphabetical) */
this._connected = false;
this._decompressedSamples = new Array(3);
this._droppedPacketCounter = 0;
this._lastDroppedPacket = null;
this._lastPacket = null;
this._localName = null;
this._multiPacketBuffer = null;
this._packetCounter = 0;
this._peripheral = null;
this._scanning = false;
this._sendCharacteristic = null;
this._streaming = false;
/** Public Properties (keep alphabetical) */
this.peripheralArray = [];
this.ganglionPeripheralArray = [];
this.previousPeripheralArray = [];
this.manualDisconnect = false;
/** Initializations */
if (this.options.nobleAutoStart) this._nobleInit(); // It get's the noble going
for (var i = 0; i < 3; i++) {
this._decompressedSamples[i] = [0, 0, 0, 0];
}
}
// This allows us to use the emitter class freely outside of the module
util.inherits(Ganglion, EventEmitter);
/**
* @description The essential precursor method to be called initially to establish a
* ble connection to the OpenBCI ganglion board.
* @param id {String | Object} - a string local name or peripheral object
* @returns {Promise} if the board was able to connect.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.connect = function (id) {
return new Promise((resolve, reject) => {
if (_.isString(id)) {
utils.getPeripheralWithLocalName(this.ganglionPeripheralArray, id)
.then((p) => {
this._nobleConnect(p);
})
.then(resolve)
.catch(reject);
} else if (_.isObject(id)) {
this._nobleConnect(id)
.then(resolve)
.catch(reject);
} else {
reject(k.OBCIErrorInvalidByteLength);
}
});
};
/**
* @description Called once when for any reason the ble connection is no longer open.
* @private
*/
Ganglion.prototype._disconnected = function () {
this._streaming = false;
// Clean up _noble
// TODO: Figure out how to fire function on process ending from inside module
// noble.removeListener('discover', this._nobleOnDeviceDiscoveredCallback);
if (this._peripheral) {
this._peripheral.removeAllListeners('servicesDiscover');
this._peripheral.removeAllListeners('connect');
this._peripheral.removeAllListeners('disconnect');
}
// _peripheral = null;
if (this.options.verbose) console.log('Disconnected');
if (!this.manualDisconnect) {
this.autoReconnect();
}
this.emit('close');
};
/**
* @description Closes the serial port. Waits for stop streaming command to
* be sent if currently streaming.
* @param stopStreaming {Boolean} (optional) - True if you want to stop streaming before disconnecting.
* @returns {Promise} - fulfilled by a successful close of the serial port object, rejected otherwise.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.disconnect = function (stopStreaming) {
if (!this.isConnected()) return Promise.reject('no board connected');
// no need for timeout here; streamStop already performs a delay
return Promise.resolve()
.then(() => {
if (stopStreaming) {
if (this.isStreaming()) {
if (this.options.verbose) console.log('stop streaming');
return this.streamStop();
}
}
})
.then(() => {
return new Promise((resolve, reject) => {
// serial emitting 'close' will call _disconnected
if (this._peripheral) {
this._peripheral.disconnect((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
} else {
reject('no peripheral to disconnect');
}
});
});
};
/**
* @description Checks if the driver is connected to a board.
* @returns {boolean} - True if connected.
*/
Ganglion.prototype.isConnected = function () {
return this._connected;
};
/**
* @description Checks if the board is currently sending samples.
* @returns {boolean} - True if streaming.
*/
Ganglion.prototype.isStreaming = function () {
return this._streaming;
};
/**
* @description Checks if noble is currently scanning.
* @returns {boolean} - True if streaming.
*/
Ganglion.prototype.isSearching = function () {
return this._scanning;
};
Ganglion.prototype.getLocalName = function () {
return this._localName;
};
/**
* @description Sends a start streaming command to the board.
* @returns {Promise} indicating if the signal was able to be sent.
* Note: You must have successfully connected to an OpenBCI board using the connect
* method. Just because the signal was able to be sent to the board, does not
* mean the board will start streaming.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.streamStart = function () {
return new Promise((resolve, reject) => {
if (this.isStreaming()) return reject('Error [.streamStart()]: Already streaming');
this._streaming = true;
this.write(k.OBCIStreamStart)
.then(() => {
if (this.options.verbose) console.log('Sent stream start to board.');
resolve();
})
.catch(reject);
});
};
/**
* @description Sends a stop streaming command to the board.
* @returns {Promise} indicating if the signal was able to be sent.
* Note: You must have successfully connected to an OpenBCI board using the connect
* method. Just because the signal was able to be sent to the board, does not
* mean the board stopped streaming.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.streamStop = function () {
return new Promise((resolve, reject) => {
if (!this.isStreaming()) return reject('Error [.streamStop()]: No stream to stop');
this._streaming = false;
this.write(k.OBCIStreamStop)
.then(() => {
resolve();
})
.catch(reject);
});
};
/**
* @description Puts the board in synthetic data generation mode.
* @returns {Promise} indicating if the signal was able to be sent.
* Note: You must have successfully connected to an OpenBCI board using the connect
* method. Just because the signal was able to be sent to the board, does not
* mean the board will start streaming.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.syntheticEnable = function () {
return new Promise((resolve, reject) => {
this.write(k.OBCIGanglionSyntheticDataEnable)
.then(() => {
if (this.options.verbose) console.log('Enabled synthetic data mode.');
resolve();
})
.catch(reject);
});
};
/**
* @description Sends a stop streaming command to the board.
* @returns {Promise} indicating if the signal was able to be sent.
* Note: You must have successfully connected to an OpenBCI board using the connect
* method. Just because the signal was able to be sent to the board, does not
* mean the board stopped streaming.
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.syntheticDisable = function () {
return new Promise((resolve, reject) => {
this.write(k.OBCIGanglionSyntheticDataDisable)
.then(() => {
if (this.options.verbose) console.log('Disabled synthetic data mode.');
resolve();
})
.catch(reject);
});
};
/**
* @description Should be used to send data to the board
* @param data {Buffer | String} - The data to write out
* @returns {Promise} if signal was able to be sent
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.write = function (data) {
return new Promise((resolve, reject) => {
if (this._sendCharacteristic) {
if (!Buffer.isBuffer(data)) {
data = new Buffer(data);
}
if (this.options.debug) openBCIUtils.debugBytes('>>>', data);
this._sendCharacteristic.write(data);
resolve();
} else {
reject('Send characteristic not set, please call connect method');
}
});
};
/**
* @description This function is used as a convenience method to determine how many
* channels the current board is using.
* @returns {Number} A number
* Note: This is dependent on if you configured the board correctly on setup options
* @author AJ Keller (@pushtheworldllc)
*/
Ganglion.prototype.numberOfChannels = function () {
return k.OBCINumberOfChannelsGanglion;
};
/**
* @description List available peripherals so the user can choose a device when not
* automatically found.
* @returns {Promise} - On fulfill will contain a ganglion.
*/
Ganglion.prototype.searchStart = function (maxSearchTime) {
const searchTime = maxSearchTime || k.OBCIGanglionBleSearchTime;
return new Promise((resolve, reject) => {
this._searchTimeout = setTimeout(() => {
this._nobleScanStop().catch(reject);
reject('Timeout: Unable to find Ganglion');
}, searchTime);
this._nobleScanStart().catch((err) => {
if (err !== k.OBCIErrorNobleAlreadyScanning) { // If it's already scanning
clearTimeout(this._searchTimeout);
reject(err);
}
});
});
};
/**
* Called to end a search.
*/
Ganglion.prototype.searchStop = function () {
return this._nobleScanStop();
};
/**
* Event driven function called when a new device is discovered while scanning.
* @param peripheral {Object} Peripheral object from noble.
* @private
*/
Ganglion.prototype._nobleOnDeviceDiscoveredCallback = function (peripheral) {
// if(this.options.verbose) console.log(peripheral.advertisement);
this.peripheralArray.push(peripheral);
if (utils.isPeripheralGanglion(peripheral)) {
if (this.options.verbose) console.log('Found ganglion!');
if (_.isUndefined(_.find(this.ganglionPeripheralArray, (p) => {
return p.advertisement.localName === peripheral.advertisement.localName;
}))) {
this.ganglionPeripheralArray.push(peripheral);
}
this.emit(k.OBCIEmitterGanglionFound, peripheral);
}
};
/**
* Call to add the noble event listeners.
* @private
*/
Ganglion.prototype._nobleInit = function () {
noble.on(k.OBCINobleEmitterStateChange, (state) => {
// TODO: send state change error to gui
// If the peripheral array is empty, do a scan to fill it.
if (state === k.OBCINobleStatePoweredOn) {
if (this.options.verbose) console.log('Bluetooth powered on');
this.emit(k.OBCIEmitterBlePoweredUp);
if (this.options.nobleScanOnPowerOn) {
this._nobleScanStart().catch((err) => {
throw err;
});
}
if (this.peripheralArray.length === 0) {
}
} else {
if (this.isSearching()) {
this._nobleScanStop().catch((err) => {
throw err;
});
}
}
});
noble.on(k.OBCINobleEmitterDiscover, this._nobleOnDeviceDiscoveredCallback.bind(this));
};
/**
* Call to destroy the noble event emitters.
* @private
*/
Ganglion.prototype._nobleDestroy = function () {
noble.removeAllListeners(k.OBCINobleEmitterStateChange);
noble.removeAllListeners(k.OBCINobleEmitterDiscover);
};
Ganglion.prototype._nobleReady = function () {
return noble.state === k.OBCINobleStatePoweredOn;
};
/**
* Call to perform a scan to get a list of peripherals.
* @returns {global.Promise|Promise}
* @private
*/
Ganglion.prototype._nobleScanStart = function () {
return new Promise((resolve, reject) => {
if (this.isSearching()) return reject(k.OBCIErrorNobleAlreadyScanning);
if (!this._nobleReady()) return reject(k.OBCIErrorNobleNotInPoweredOnState);
this.peripheralArray = [];
noble.once(k.OBCINobleEmitterScanStart, () => {
if (this.options.verbose) console.log('Scan started');
this._scanning = true;
resolve();
});
// Only look so simblee ble devices and allow duplicates (multiple ganglions)
// noble.startScanning([k.SimbleeUuidService], true);
noble.startScanning([], false);
});
};
Ganglion.prototype._nobleScanStop = function () {
return new Promise((resolve, reject) => {
if (this.isSearching()) return reject(k.OBCIErrorNobleNotAlreadyScanning);
if (this.options.verbose) console.log(`Stopping scan`);
noble.once(k.OBCINobleEmitterScanStop, () => {
this._scanning = false;
if (this.options.verbose) console.log('Scan stopped');
resolve();
});
// Stop noble from scanning
noble.stopScanning();
});
};
Ganglion.prototype._nobleConnect = function (peripheral) {
return new Promise((resolve, reject) => {
if (this.isConnected()) return reject('already connected!');
this._peripheral = peripheral;
this._localName = peripheral.advertisement.localName;
// if (_.contains(_peripheral.advertisement.localName, rfduino.localNamePrefix)) {
// TODO: slice first 8 of localName and see if that is ganglion
// here is where we can capture the advertisement data from the rfduino and check to make sure its ours
if (this.options.verbose) console.log('Device is advertising \'' + this._peripheral.advertisement.localName + '\' service.');
// TODO: filter based on advertising name ie make sure we are looking for the right thing
// if (this.options.verbose) console.log("serviceUUID: " + this._peripheral.advertisement.serviceUuids);
this._peripheral.on(k.OBCINobleEmitterPeripheralConnect, () => {
// if (this.options.verbose) console.log("got connect event");
this._peripheral.discoverServices();
if (this._scanning) this._nobleScanStop();
});
this._peripheral.on(k.OBCINobleEmitterPeripheralDisconnect, () => {
this._disconnected();
});
this._peripheral.on(k.OBCINobleEmitterPeripheralServicesDiscover, (services) => {
let rfduinoService;
for (var i = 0; i < services.length; i++) {
if (services[i].uuid === k.SimbleeUuidService) {
rfduinoService = services[i];
// if (this.options.verbose) console.log("Found simblee Service");
break;
}
}
if (!rfduinoService) {
reject('Couldn\'t find the simblee service.');
}
rfduinoService.on(k.OBCINobleEmitterServiceCharacteristicsDiscover, (characteristics) => {
// if (this.options.verbose) console.log('Discovered ' + characteristics.length + ' service characteristics');
var receiveCharacteristic;
for (var i = 0; i < characteristics.length; i++) {
// console.log(characteristics[i].uuid);
if (characteristics[i].uuid === k.SimbleeUuidReceive) {
receiveCharacteristic = characteristics[i];
}
if (characteristics[i].uuid === k.SimbleeUuidSend) {
// if (this.options.verbose) console.log("Found sendCharacteristicUUID");
this._sendCharacteristic = characteristics[i];
if (this.options.verbose) console.log('connected');
this._connected = true;
this.emit(k.OBCIEmitterReady);
resolve();
}
}
if (receiveCharacteristic) {
receiveCharacteristic.on(k.OBCINobleEmitterServiceRead, (data) => {
// TODO: handle all the data, both streaming and not
this._processBytes(data);
});
// if (this.options.verbose) console.log('Subscribing for data notifications');
receiveCharacteristic.notify(true);
}
});
rfduinoService.discoverCharacteristics();
});
// if (this.options.verbose) console.log("Calling connect");
this._peripheral.connect((err) => {
if (err) {
if (this.options.verbose) console.log(`Unable to connect with error: ${err}`);
this._connected = false;
this._peripheral = null;
reject(err);
}
});
});
};
Ganglion.prototype.autoReconnect = function () {
// TODO: send back reconnect status, or reconnect fail
if (noble.state === k.OBCINobleStatePoweredOn) {
this._nobleScanStart();
} else {
console.warn('BLE not AVAILABLE');
}
};
Ganglion.prototype.decompressSamples = function (receivedDeltas) {
// add the delta to the previous value
for (let i = 1; i < 3; i++) {
for (let j = 0; j < 4; j++) {
this._decompressedSamples[i][j] = this._decompressedSamples[i - 1][j] - receivedDeltas[i - 1][j];
}
}
};
Ganglion.prototype.decompressDeltas = function (buffer) {
let D = new Array(k.OBCIGanglionSamplesPerPacket); // 2
D[0] = [0, 0, 0, 0];
D[1] = [0, 0, 0, 0];
let receivedDeltas = [];
for (let i = 0; i < k.OBCIGanglionSamplesPerPacket; i++) {
receivedDeltas.push([0, 0, 0, 0]);
}
let miniBuf;
// Sample 1 - Channel 1
miniBuf = new Buffer(
[
(buffer[0] >> 5),
((buffer[0] & 0x1F) << 3) | (buffer[1] >> 5),
((buffer[1] & 0x1F) << 3) | (buffer[2] >> 5)
]
);
receivedDeltas[0][0] = utils.convert19bitAsInt32(miniBuf);
// Sample 1 - Channel 2
miniBuf = new Buffer(
[
(buffer[2] & 0x1F) >> 2,
(buffer[2] << 6) | (buffer[3] >> 2),
(buffer[3] << 6) | (buffer[4] >> 2)
]);
// miniBuf = new Buffer([(buffer[2] & 0x1F), buffer[3], buffer[4] >> 2]);
receivedDeltas[0][1] = utils.convert19bitAsInt32(miniBuf);
// Sample 1 - Channel 3
miniBuf = new Buffer(
[
((buffer[4] & 0x03) << 1) | (buffer[5] >> 7),
((buffer[5] & 0x7F) << 1) | (buffer[6] >> 7),
((buffer[6] & 0x7F) << 1) | (buffer[7] >> 7)
]);
receivedDeltas[0][2] = utils.convert19bitAsInt32(miniBuf);
// Sample 1 - Channel 4
miniBuf = new Buffer(
[
((buffer[7] & 0x7F) >> 4),
((buffer[7] & 0x0F) << 4) | (buffer[8] >> 4),
((buffer[8] & 0x0F) << 4) | (buffer[9] >> 4)
]);
receivedDeltas[0][3] = utils.convert19bitAsInt32(miniBuf);
// Sample 2 - Channel 1
miniBuf = new Buffer(
[
((buffer[9] & 0x0F) >> 1),
(buffer[9] << 7) | (buffer[10] >> 1),
(buffer[10] << 7) | (buffer[11] >> 1)
]);
receivedDeltas[1][0] = utils.convert19bitAsInt32(miniBuf);
// Sample 2 - Channel 2
miniBuf = new Buffer(
[
((buffer[11] & 0x01) << 2) | (buffer[12] >> 6),
(buffer[12] << 2) | (buffer[13] >> 6),
(buffer[13] << 2) | (buffer[14] >> 6)
]);
receivedDeltas[1][1] = utils.convert19bitAsInt32(miniBuf);
// Sample 2 - Channel 3
miniBuf = new Buffer(
[
((buffer[14] & 0x38) >> 3),
((buffer[14] & 0x07) << 5) | ((buffer[15] & 0xF8) >> 3),
((buffer[15] & 0x07) << 5) | ((buffer[16] & 0xF8) >> 3)
]);
receivedDeltas[1][2] = utils.convert19bitAsInt32(miniBuf);
// Sample 2 - Channel 4
miniBuf = new Buffer([(buffer[16] & 0x07), buffer[17], buffer[18]]);
receivedDeltas[1][3] = utils.convert19bitAsInt32(miniBuf);
return receivedDeltas;
};
Ganglion.prototype._processBytes = function (data) {
if (this.options.debug) openBCIUtils.debugBytes('<<', data);
this.lastPacket = data;
let byteId = parseInt(data[0]);
if (byteId <= k.OBCIGanglionByteIdSampleMax) {
if (byteId === k.OBCIGanglionByteIdRawData) {
this._processUncompressedData(data);
} else {
this._processCompressedData(data);
}
} else {
switch (byteId) {
case k.OBCIGanglionByteIdAccel:
this._processAccel(data);
break;
case k.OBCIGanglionByteIdMultiPacket:
this._processMultiBytePacket(data);
break;
case k.OBCIGanglionByteIdMultiPacketStop:
this._processMultiBytePacketStop(data);
break;
case k.OBCIGanglionByteIdImpedanceChannel1:
case k.OBCIGanglionByteIdImpedanceChannel2:
case k.OBCIGanglionByteIdImpedanceChannel3:
case k.OBCIGanglionByteIdImpedanceChannel4:
case k.OBCIGanglionByteIdImpedanceChannelReference:
this._processImpedanceData(data);
break;
default:
this._processOtherData(data);
}
}
};
Ganglion.prototype._processAccel = function (data) {
openBCIUtils.debugBytes('Accel <<< ', data);
};
/**
* Process an compressed packet of data.
* @param data {Buffer}
* Data packet buffer from noble.
* @private
*/
Ganglion.prototype._processCompressedData = function (data) {
// check for dropped packet
if (parseInt(data[0]) - this._packetCounter !== 1) {
this.lastDroppedPacket = parseInt(data[0]); // - 2;
// var retryString = "&"+dropped;
// var reset = Buffer.from(retryString);
// _sendCharacteristic.write(reset);
this._droppedPacketCounter++;
this.emit(k.OBCIEmitterDroppedPacket, [parseInt(data[0]) - 1]);
if (this.options.verbose) console.error('\t>>>PACKET DROP<<< ' + this._packetCounter + ' ' + this.lastDroppedPacket + ' ' + this._droppedPacketCounter);
}
let buffer = data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop);
if (k.getVersionNumber(process.version) >= 6) {
// From introduced in node version 6.x.x
buffer = Buffer.from(buffer);
} else {
buffer = new Buffer(buffer);
}
// Decompress the buffer into array
this.decompressSamples(this.decompressDeltas(buffer));
this._packetCounter = parseInt(data[0]);
const sample1 = this._buildSample(this._packetCounter * 2 - 1, this._decompressedSamples[1]);
this.emit(k.OBCIEmitterSample, sample1);
const sample2 = this._buildSample(this._packetCounter * 2, this._decompressedSamples[2]);
this.emit(k.OBCIEmitterSample, sample2);
// Rotate the 0 position for next time
for (let i = 0; i < k.OBCINumberOfChannelsGanglion; i++) {
this._decompressedSamples[0][i] = this._decompressedSamples[2][i];
}
};
/**
* Process and emit an impedance value
* @param data {Buffer}
* @private
*/
Ganglion.prototype._processImpedanceData = function (data) {
if (this.options.verbose) openBCIUtils.debugBytes('Impedance <<< ', data);
const byteId = parseInt(data[0]);
let channelNumber;
switch (byteId) {
case k.OBCIGanglionByteIdImpedanceChannel1:
channelNumber = 1;
break;
case k.OBCIGanglionByteIdImpedanceChannel2:
channelNumber = 2;
break;
case k.OBCIGanglionByteIdImpedanceChannel3:
channelNumber = 3;
break;
case k.OBCIGanglionByteIdImpedanceChannel4:
channelNumber = 4;
break;
case k.OBCIGanglionByteIdImpedanceChannelReference:
channelNumber = 0;
break;
}
let output = {
channelNumber: channelNumber,
impedanceValue: 0
};
let end = data.length;
while (_.isNaN(Number(data.slice(1, end))) && end !== 0) {
end--;
}
if (end !== 0) {
output.impedanceValue = Number(data.slice(1, end));
}
this.emit('impedance', output);
};
/**
* Used to stack multi packet buffers into the multi packet buffer. This is finally emitted when a stop packet byte id
* is received.
* @param data {Buffer}
* The multi packet buffer.
* @private
*/
Ganglion.prototype._processMultiBytePacket = function (data) {
if (this._multiPacketBuffer) {
this._multiPacketBuffer = Buffer.concat([this._multiPacketBuffer, data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop)]);
} else {
this._multiPacketBuffer = data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop);
}
};
/**
* Adds the `data` buffer to the multi packet buffer and emits the buffer as 'message'
* @param data {Buffer}
* The multi packet stop buffer.
* @private
*/
Ganglion.prototype._processMultiBytePacketStop = function (data) {
this._processMultiBytePacket(data);
this.emit(k.OBCIEmitterMessage, this._multiPacketBuffer);
this.destroyMultiPacketBuffer();
};
/**
* The default route when a ByteId is not recognized.
* @param data {Buffer}
* @private
*/
Ganglion.prototype._processOtherData = function (data) {
openBCIUtils.debugBytes('OtherData <<< ', data);
};
/**
* Process an uncompressed packet of data.
* @param data {Buffer}
* Data packet buffer from noble.
* @private
*/
Ganglion.prototype._processUncompressedData = function (data) {
let start = 1;
// Resets the packet counter back to zero
this._packetCounter = k.OBCIGanglionByteIdRawData; // used to find dropped packets
for (let i = 0; i < 4; i++) {
this._decompressedSamples[0][i] = interpret24bitAsInt32(data, start); // seed the decompressor
start += 3;
}
const newSample = this._buildSample(0, this._decompressedSamples[0]);
this.emit(k.OBCIEmitterSample, newSample);
};
/**
* Destroys the multi packet buffer.
*/
Ganglion.prototype.destroyMultiPacketBuffer = function () {
this._multiPacketBuffer = null;
};
/**
* Get's the multi packet buffer.
* @return {null|Buffer} - Can be null if no multi packets recieved.
*/
Ganglion.prototype.getMutliPacketBuffer = function () {
return this._multiPacketBuffer;
};
/**
* Builds a sample object from an array and sample number.
* @param sampleNumber
* @param rawData
* @return {{sampleNumber: *}}
* @private
*/
Ganglion.prototype._buildSample = function (sampleNumber, rawData) {
let sample = {
sampleNumber: sampleNumber
};
if (this.options.sendCounts) {
sample['channelDataCounts'] = rawData;
} else {
sample['channelData'] = [];
for (let j = 0; j < k.OBCINumberOfChannelsGanglion; j++) {
sample.channelData.push(rawData[j] * k.OBCIGanglionScaleFactorPerCountVolts);
}
}
return sample;
};
module.exports = Ganglion;
function interpret24bitAsInt32 (byteArray, index) {
// little endian
var newInt = (
((0xFF & byteArray[index]) << 16) |
((0xFF & byteArray[index + 1]) << 8) |
(0xFF & byteArray[index + 2])
);
if ((newInt & 0x00800000) > 0) {
newInt |= 0xFF000000;
} else {
newInt &= 0x00FFFFFF;
}
return newInt;
}
+143
Ver Arquivo
@@ -0,0 +1,143 @@
// const dgram = require('dgram');
// const gaussian = require('gaussian');
// const k = require('openBCIContants');
// let stream;
// var startStream = () => {
// const intervalInMS = 1000 / k.OBCISampleRate200;
// let sampleNumber = 0;
// let sampleGenerator = randomSample(4, k.OBCISampleRate200, true, `60Hz`);
//
// var getSample = sampleNumber => {
// let arr = getArrayFromSample(sampleGenerator(sampleNumber));
// // console.log(`${sampleNumber},${arr[0].toString()},${arr[1].toString()},${arr[2].toString()},${arr[3].toString()}`);
// return `${sampleNumber},${arr[0].toString()},${arr[1].toString()},${arr[2].toString()},${arr[3].toString()}`;
// };
//
// stream = setInterval(() => {
// let sampArray = getSample(sampleNumber);
// // Send the packet to all clients
// this.emit('read', samp);
// // Increment the sample number
// sampleNumber++;
// }, intervalInMS);
// };
/**
* @description Mainly used by the simulator to convert a randomly generated sample into a std OpenBCI V3 Packet
* @param sample - A sample object
* @returns {Array}
*/
// function getArrayFromSample (sample) {
// var array = [];
// for (var i = 0; i < 4; i++) {
// array.push(Math.floor(interpret24bitAsInt32(floatTo3ByteBuffer(sample.channelData[i]))));
// }
// return array;
// }
// function floatTo3ByteBuffer (float) {
// var intBuf = new Buffer(3); // 3 bytes for 24 bits
// intBuf.fill(0); // Fill the buffer with 0s
//
// var temp = float / (k.OBCIGanglionMCP3912Vref / k.OBCIGanglionMCP3912Gain / (Math.pow(2, 23) - 1)); // Convert to counts
//
// temp = Math.floor(temp); // Truncate counts number
//
// // Move into buffer
// intBuf[2] = temp & 255;
// intBuf[1] = (temp & (255 << 8)) >> 8;
// intBuf[0] = (temp & (255 << 16)) >> 16;
//
// return intBuf;
// }
//
// /**
// * @description Create a configurable function to return samples for a simulator. This implements 1/f filtering injection to create more brain like data.
// * @param numberOfChannels {Number} - The number of channels in the sample... either 8 or 16
// * @param sampleRateHz {Number} - The sample rate
// * @param injectAlpha {Boolean} - True if you want to inject noise
// * @param lineNoise {String} - A string that can be either:
// * `60Hz` - 60Hz line noise (Default) (ex. __United States__)
// * `50Hz` - 50Hz line noise (ex. __Europe__)
// * `None` - Do not inject line noise.
// *
// * @returns {Function}
// */
// var randomSample = (numberOfChannels, sampleRateHz, injectAlpha, lineNoise) => {
// const distribution = gaussian(0, 1);
// const sineWaveFreqHz10 = 10;
// const sineWaveFreqHz50 = 50;
// const sineWaveFreqHz60 = 60;
// const uVolts = 1000000;
//
// var sinePhaseRad = new Array(numberOfChannels + 1); // prevent index error with '+1'
// sinePhaseRad.fill(0);
//
// // Init arrays to hold coefficients for each channel and init to 0
// // This gives the 1/f filter memory on each iteration
// var b0 = new Array(numberOfChannels).fill(0);
// var b1 = new Array(numberOfChannels).fill(0);
// var b2 = new Array(numberOfChannels).fill(0);
//
// /**
// * @description Use a 1/f filter
// * @param previousSampleNumber {Number} - The previous sample number
// */
// return previousSampleNumber => {
// let sample = newSample();
// for (var i = 0; i < numberOfChannels; i++) { // channels are 0 indexed
// // This produces white noise
// let whiteNoise = distribution.ppf(Math.random()) * Math.sqrt(sampleRateHz / 2) / uVolts;
//
// switch (i) {
// case 0: // Add 10Hz signal to channel 1... brainy
// case 1:
// if (injectAlpha) {
// sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz10 / sampleRateHz;
// if (sinePhaseRad[i] > 2 * Math.PI) {
// sinePhaseRad[i] -= 2 * Math.PI;
// }
// whiteNoise += (5 * Math.SQRT2 * Math.sin(sinePhaseRad[i])) / uVolts;
// }
// break;
// default:
// sinePhaseRad[i] += 2 * Math.PI * sineWaveFreqHz60 / sampleRateHz;
// if (sinePhaseRad[i] > 2 * Math.PI) {
// sinePhaseRad[i] -= 2 * Math.PI;
// }
// whiteNoise += (8 * Math.SQRT2 * Math.sin(sinePhaseRad[i])) / uVolts;
// break;
// }
// /**
// * See http://www.firstpr.com.au/dsp/pink-noise/ section "Filtering white noise to make it pink"
// */
// b0[i] = 0.99765 * b0[i] + whiteNoise * 0.0990460;
// b1[i] = 0.96300 * b1[i] + whiteNoise * 0.2965164;
// b2[i] = 0.57000 * b2[i] + whiteNoise * 1.0526913;
// sample.channelData[i] = b0[i] + b1[i] + b2[i] + whiteNoise * 0.1848;
// }
// if (previousSampleNumber == 255) {
// sample.sampleNumber = 0;
// } else {
// sample.sampleNumber = previousSampleNumber + 1;
// }
//
// return sample;
// };
// };
//
// function newSample (sampleNumber) {
// if (sampleNumber || sampleNumber === 0) {
// if (sampleNumber > 255) {
// sampleNumber = 255;
// }
// } else {
// sampleNumber = 0;
// }
// return {
// sampleNumber: sampleNumber,
// channelData: []
// };
// }
+162
Ver Arquivo
@@ -0,0 +1,162 @@
'use strict';
const k = require('./openBCIConstants');
const _ = require('underscore');
module.exports = {
getPeripheralLocalNames,
getPeripheralWithLocalName,
/**
* Converts a special ganglion 19 bit compressed number
* The compressions uses the LSB, bit 1, as the signed bit, instead of using
* the MSB. Therefore you must not look to the MSB for a sign extension, one
* must look to the LSB, and the same rules applies, if it's a 1, then it's a
* negative and if it's 0 then it's a positive number.
* @param threeByteBuffer {Buffer}
* A 3-byte buffer with only 19 bits of actual data.
* @return {number} A signed integer.
*/
convert19bitAsInt32: (threeByteBuffer) => {
let prefix = 0;
if (threeByteBuffer[2] & 0x01 > 0) {
// console.log('\t\tNegative number')
prefix = 0b1111111111111;
}
return (prefix << 19) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2];
},
/**
* @description Very safely checks to see if the noble peripheral is a
* ganglion by way of checking the local name property.
*/
isPeripheralGanglion: (peripheral) => {
if (peripheral) {
if (peripheral.hasOwnProperty('advertisement')) {
if (peripheral.advertisement !== null && peripheral.advertisement.hasOwnProperty('localName')) {
if (peripheral.advertisement.localName !== undefined && peripheral.advertisement.localName !== null) {
if (peripheral.advertisement.localName.indexOf(k.OBCIGanglionPrefix) > -1) {
return true;
}
}
}
}
}
return false;
},
sampleAccel: () => {
return new Buffer([k.OBCIGanglionByteIdAccel, 0, 0, 1, 0, 0, 2, 0, 0, 3]);
},
sampleCompressedData: (sampleNumber) => {
return new Buffer(
[
sampleNumber, // 0
0b00000000, // 0
0b00000000, // 1
0b00000000, // 2
0b00000000, // 3
0b00001000, // 4
0b00000000, // 5
0b00000101, // 6
0b00000000, // 7
0b00000000, // 8
0b01001000, // 9
0b00000000, // 10
0b00001001, // 11
0b11110000, // 12
0b00000001, // 13
0b10110000, // 14
0b00000000, // 15
0b00110000, // 16
0b00000000, // 17
0b00001000 // 18
]);
},
sampleImpedanceChannel1: () => {
return new Buffer([k.OBCIGanglionByteIdImpedanceChannel1, 0, 0, 1]);
},
sampleImpedanceChannel2: () => {
return new Buffer([k.OBCIGanglionByteIdImpedanceChannel2, 0, 0, 1]);
},
sampleImpedanceChannel3: () => {
return new Buffer([k.OBCIGanglionByteIdImpedanceChannel3, 0, 0, 1]);
},
sampleImpedanceChannel4: () => {
return new Buffer([k.OBCIGanglionByteIdImpedanceChannel4, 0, 0, 1]);
},
sampleImpedanceChannelReference: () => {
return new Buffer([k.OBCIGanglionByteIdImpedanceChannelReference, 0, 0, 1]);
},
sampleMultiBytePacket: (data) => {
const bufPre = new Buffer([k.OBCIGanglionByteIdMultiPacket]);
return Buffer.concat([bufPre, data]);
},
sampleMultiBytePacketStop: (data) => {
const bufPre = new Buffer([k.OBCIGanglionByteIdMultiPacketStop]);
return Buffer.concat([bufPre, data]);
},
sampleOtherData: (data) => {
const bufPre = new Buffer([255]);
return Buffer.concat([bufPre, data]);
},
sampleUncompressedData: () => {
return new Buffer(
[
0b00000000, // 0
0b00000000, // 1
0b00000000, // 2
0b00000001, // 3
0b00000000, // 4
0b00000000, // 5
0b00000010, // 6
0b00000000, // 7
0b00000000, // 8
0b00000011, // 9
0b00000000, // 10
0b00000000, // 11
0b00000100, // 12
0b00000001, // 13
0b00000010, // 14
0b00000011, // 15
0b00000100, // 16
0b00000101, // 17
0b00000110, // 18
0b00000111 // 19
]);
}
};
/**
* @description Get a list of local names from an array of peripherals
*/
function getPeripheralLocalNames (pArray) {
return new Promise((resolve, reject) => {
var list = [];
_.each(pArray, perif => {
list.push(perif.advertisement.localName);
});
if (list.length > 0) {
return resolve(list);
} else {
return reject(`No peripherals discovered with prefix equal to ${k.OBCIGanglionPrefix}`);
}
});
}
/**
* @description Get a peripheral with a local name
* @param `pArray` {Array} - Array of peripherals
* @param `localName` {String} - The local name of the BLE device.
*/
function getPeripheralWithLocalName (pArray, localName) {
return new Promise((resolve, reject) => {
if (typeof (pArray) !== 'object') return reject(`pArray must be of type Object`);
_.each(pArray, perif => {
if (perif.advertisement.hasOwnProperty('localName')) {
if (perif.advertisement.localName === localName) {
return resolve(perif);
}
}
});
return reject(`No peripheral found with localName: ${localName}`);
});
}
+35
Ver Arquivo
@@ -0,0 +1,35 @@
module.exports = {
debugBytes
};
/**
* @description Output passed bytes on the console as a hexdump, if enabled
* @param prefix - label to show to the left of bytes
* @param data - bytes to output, a buffer or string
* @private
*/
function debugBytes (prefix, data) {
if (typeof data === 'string') data = new Buffer(data);
console.log('Debug bytes:');
for (var j = 0; j < data.length;) {
var hexPart = '';
var ascPart = '';
for (var end = Math.min(data.length, j + 16); j < end; ++j) {
var byt = data[j];
var hex = ('0' + byt.toString(16)).slice(-2);
hexPart += (((j & 0xf) === 0x8) ? ' ' : ' '); // puts an extra space 8 bytes in
hexPart += hex;
var asc = (byt >= 0x20 && byt < 0x7f) ? String.fromCharCode(byt) : '.';
ascPart += asc;
}
// pad to fixed width for alignment
hexPart = (hexPart + ' ').substring(0, 3 * 17);
console.log(prefix + ' ' + hexPart + '|' + ascPart + '|');
}
}
+70
Ver Arquivo
@@ -0,0 +1,70 @@
{
"name": "openbci-ganglion",
"version": "0.1.0",
"description": "The official Node.js SDK for the OpenBCI Ganglion Biosensor Board.",
"main": "index.js",
"scripts": {
"test": "semistandard | snazzy && mocha test",
"test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec && codecov"
},
"keywords": [
"openbci",
"openbci-node",
"ganglion"
],
"author": "AJ Keller <aj@pushtheworld.us> (www.openbci.com)",
"license": "MIT",
"dependencies": {
"buffer-equal": "^1.0.0",
"clone": "^2.0.0",
"gaussian": "^1.0.0",
"lodash": "^4.16.6",
"mathjs": "^3.3.0",
"noble": "^1.7.0",
"performance-now": "^0.2.0",
"sntp": "^2.0.0",
"streamsearch": "^0.1.2",
"underscore": "^1.8.3"
},
"directories": {
"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",
"snazzy": "^5.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/openbci/openbci_nodejs.git"
},
"bugs": {
"url": "https://github.com/openbci/openbci_nodejs/issues"
},
"homepage": "https://github.com/openbci/openbci_nodejs#readme",
"engines": {
"node": ">=4.0.0"
},
"semistandard": {
"globals": [
"describe",
"xdescribe",
"context",
"before",
"beforeEach",
"after",
"afterEach",
"it",
"expect",
"should"
]
}
}
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+142
Ver Arquivo
@@ -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 });
});
+352
Ver Arquivo
@@ -0,0 +1,352 @@
'use strict';
// const bluebirdChecks = require('./bluebirdChecks');
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const should = chai.should(); // eslint-disable-line no-unused-vars
const Ganglion = require('../openBCIGanglion');
const k = require('../openBCIConstants');
const chaiAsPromised = require('chai-as-promised');
const sinonChai = require('sinon-chai');
const bufferEqual = require('buffer-equal');
const utils = require('../openBCIGanglionUtils');
const clone = require('clone');
chai.use(chaiAsPromised);
chai.use(sinonChai);
describe('#ganglion', function () {
const mockProperties = {
nobleAutoStart: false,
nobleScanOnPowerOn: false,
simulate: false,
simulatorBoardFailure: false,
simulatorHasAccelerometer: true,
simulatorInternalClockDrift: 0,
simulatorInjectAlpha: true,
simulatorInjectLineNoise: k.OBCISimulatorLineNoiseHz60,
simulatorSampleRate: 200,
verbose: false,
debug: false,
sendCounts: false
};
const expectedProperties = clone(mockProperties);
const ganglion = new Ganglion(mockProperties);
it('should have properties', function () {
expect(ganglion.options).to.deep.equal(expectedProperties);
});
it('should return 4 channels', function () {
expect(ganglion.numberOfChannels()).to.equal(4);
});
it('should extract the proper values for each channel', function () {
let buffer = new Buffer(
[
0b00000000, // 0
0b00000000, // 1
0b00000000, // 2
0b00000000, // 3
0b00001000, // 4
0b00000000, // 5
0b00000101, // 6
0b00000000, // 7
0b00000000, // 8
0b01001000, // 9
0b00000000, // 10
0b00001001, // 11
0b11110000, // 12
0b00000001, // 13
0b10110000, // 14
0b00000000, // 15
0b00110000, // 16
0b00000000, // 17
0b00001000 // 18
]);
let expectedValue = [[0, 2, 10, 4], [262148, 507910, 393222, 8]];
let actualValue = ganglion.decompressDeltas(buffer);
for (let i = 0; i < 4; i++) {
(actualValue[0][i]).should.equal(expectedValue[0][i]);
(actualValue[1][i]).should.equal(expectedValue[1][i]);
}
});
it('should extract the proper values for each channel (neg test)', function () {
let buffer = new Buffer(
[
0b11111111, // 0
0b11111111, // 1
0b10111111, // 2
0b11111111, // 3
0b11101111, // 4
0b11111111, // 5
0b11111100, // 6
0b11111111, // 7
0b11111111, // 8
0b01011000, // 9
0b00000000, // 10
0b00001011, // 11
0b00111110, // 12
0b00111000, // 13
0b11100000, // 14
0b00000000, // 15
0b00111111, // 16
0b11110000, // 17
0b00000001 // 18
]);
let expectedValue = [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]];
let actualValue = ganglion.decompressDeltas(buffer);
for (let i = 0; i < 4; i++) {
(actualValue[0][i]).should.equal(expectedValue[0][i]);
(actualValue[1][i]).should.equal(expectedValue[1][i]);
}
});
it('should destroy the multi packet buffer', function () {
ganglion.destroyMultiPacketBuffer();
expect(ganglion.getMutliPacketBuffer()).to.equal(null);
});
it('should stack and emit one buffer from several multi packet buffer', function () {
const bufMultPacket = new Buffer([k.OBCIGanglionByteIdMultiPacket]);
const bufMultPacketStop = new Buffer([k.OBCIGanglionByteIdMultiPacketStop]);
const buf1 = new Buffer('taco');
const newBuffer1 = Buffer.concat([bufMultPacket, buf1]);
ganglion._processMultiBytePacket(newBuffer1);
expect(bufferEqual(ganglion.getMutliPacketBuffer(), buf1)).to.equal(true);
const buf2 = new Buffer('vegas');
const newBuffer2 = Buffer.concat([bufMultPacket, buf2]);
ganglion._processMultiBytePacket(newBuffer2);
expect(bufferEqual(ganglion.getMutliPacketBuffer(), Buffer.concat([buf1, buf2])));
const bufStop = new Buffer('hola');
const newBufferStop = Buffer.concat([bufMultPacketStop, bufStop]);
let messageEventCalled = false;
ganglion.once('message', (data) => {
expect(bufferEqual(data, Buffer.concat([buf1, buf2, bufStop]))).to.equal(true);
messageEventCalled = true;
});
ganglion._processMultiBytePacketStop(newBufferStop);
expect(ganglion.getMutliPacketBuffer()).to.equal(null);
expect(messageEventCalled).to.equal(true);
ganglion.once('message', (data) => {
expect(bufferEqual(data, bufStop)).to.equal(true);
});
ganglion._processMultiBytePacketStop(newBufferStop);
expect(ganglion.getMutliPacketBuffer()).to.equal(null);
});
it('should be able to just get one packet buffer message', function () {
const bufStop = new Buffer('hola');
const bufMultPacketStop = new Buffer([k.OBCIGanglionByteIdMultiPacketStop]);
const newBufferStop = Buffer.concat([bufMultPacketStop, bufStop]);
let messageEventCalled = false;
ganglion.once('message', (data) => {
expect(bufferEqual(data, bufStop)).to.equal(true);
messageEventCalled = true;
});
ganglion._processMultiBytePacketStop(newBufferStop);
expect(ganglion.getMutliPacketBuffer()).to.equal(null);
expect(messageEventCalled).to.equal(true);
});
describe('_processBytes', function () {
let funcSpyAccel;
let funcSpyCompressedData;
let funcSpyImpedanceData;
let funcSpyMultiBytePacket;
let funcSpyMultiBytePacketStop;
let funcSpyOtherData;
let funcSpyUncompressedData;
before(function () {
// Put watchers on all functions
funcSpyAccel = sinon.spy(ganglion, '_processAccel');
funcSpyCompressedData = sinon.spy(ganglion, '_processCompressedData');
funcSpyImpedanceData = sinon.spy(ganglion, '_processImpedanceData');
funcSpyMultiBytePacket = sinon.spy(ganglion, '_processMultiBytePacket');
funcSpyMultiBytePacketStop = sinon.spy(ganglion, '_processMultiBytePacketStop');
funcSpyOtherData = sinon.spy(ganglion, '_processOtherData');
funcSpyUncompressedData = sinon.spy(ganglion, '_processUncompressedData');
});
beforeEach(function () {
funcSpyAccel.reset();
funcSpyCompressedData.reset();
funcSpyImpedanceData.reset();
funcSpyMultiBytePacket.reset();
funcSpyMultiBytePacketStop.reset();
funcSpyOtherData.reset();
funcSpyUncompressedData.reset();
});
it('should route accel packet', function () {
ganglion._processBytes(utils.sampleAccel());
funcSpyAccel.should.have.been.calledOnce;
});
it('should route compressed data packet', function () {
ganglion._processBytes(utils.sampleCompressedData(3));
funcSpyCompressedData.should.have.been.calledOnce;
});
it('should route impedance channel 1 packet', function () {
ganglion._processBytes(utils.sampleImpedanceChannel1());
funcSpyImpedanceData.should.have.been.calledOnce;
});
it('should route impedance channel 2 packet', function () {
ganglion._processBytes(utils.sampleImpedanceChannel2());
funcSpyImpedanceData.should.have.been.calledOnce;
});
it('should route impedance channel 3 packet', function () {
ganglion._processBytes(utils.sampleImpedanceChannel3());
funcSpyImpedanceData.should.have.been.calledOnce;
});
it('should route impedance channel 4 packet', function () {
ganglion._processBytes(utils.sampleImpedanceChannel4());
funcSpyImpedanceData.should.have.been.calledOnce;
});
it('should route impedance channel reference packet', function () {
ganglion._processBytes(utils.sampleImpedanceChannelReference());
funcSpyImpedanceData.should.have.been.calledOnce;
});
it('should route multi packet data', function () {
ganglion._processBytes(utils.sampleMultiBytePacket(new Buffer('taco')));
funcSpyMultiBytePacket.should.have.been.calledOnce;
});
it('should route multi packet stop data', function () {
ganglion._processBytes(utils.sampleMultiBytePacketStop(new Buffer('taco')));
funcSpyMultiBytePacketStop.should.have.been.calledOnce;
});
it('should route other data packet', function () {
ganglion._processBytes(utils.sampleOtherData(new Buffer('blah')));
funcSpyOtherData.should.have.been.calledOnce;
});
it('should route uncompressed data packet', function () {
ganglion._processBytes(utils.sampleUncompressedData());
funcSpyUncompressedData.should.have.been.calledOnce;
});
});
it('should emit impedance value', function() {
let expectedImpedanceValue = 1099;
const payloadBuf = new Buffer(`${expectedImpedanceValue}${k.OBCIGanglionImpedanceStop}`);
let totalEvents = 0;
let runningEventCount = 0;
// Channel 1
totalEvents++;
let expectedChannelNumber = 1;
let impPre = new Buffer([k.OBCIGanglionByteIdImpedanceChannel1]);
let expectedReturnValue = {
channelNumber: expectedChannelNumber,
impedanceValue: expectedImpedanceValue
};
let dataBuf = Buffer.concat([impPre, payloadBuf]);
ganglion.once('impedance', (actualImpedanceValue) => {
expect(actualImpedanceValue).to.deep.equal(expectedReturnValue);
runningEventCount++;
});
ganglion._processImpedanceData(dataBuf);
// Channel 2
totalEvents++;
expectedChannelNumber = 2;
impPre[0] = k.OBCIGanglionByteIdImpedanceChannel2;
expectedReturnValue = {
channelNumber: expectedChannelNumber,
impedanceValue: expectedImpedanceValue
};
dataBuf = Buffer.concat([impPre, payloadBuf]);
ganglion.once('impedance', (actualImpedanceValue) => {
expect(actualImpedanceValue).to.deep.equal(expectedReturnValue);
runningEventCount++;
});
ganglion._processImpedanceData(dataBuf);
// Channel 3
totalEvents++;
expectedChannelNumber = 3;
impPre[0] = k.OBCIGanglionByteIdImpedanceChannel3;
expectedReturnValue = {
channelNumber: expectedChannelNumber,
impedanceValue: expectedImpedanceValue
};
dataBuf = Buffer.concat([impPre, payloadBuf]);
ganglion.once('impedance', (actualImpedanceValue) => {
expect(actualImpedanceValue).to.deep.equal(expectedReturnValue);
runningEventCount++;
});
ganglion._processImpedanceData(dataBuf);
// Channel 4
totalEvents++;
expectedChannelNumber = 4;
impPre[0] = k.OBCIGanglionByteIdImpedanceChannel4;
expectedReturnValue = {
channelNumber: expectedChannelNumber,
impedanceValue: expectedImpedanceValue
};
dataBuf = Buffer.concat([impPre, payloadBuf]);
ganglion.once('impedance', (actualImpedanceValue) => {
expect(actualImpedanceValue).to.deep.equal(expectedReturnValue);
runningEventCount++;
});
ganglion._processImpedanceData(dataBuf);
// Channel Reference
totalEvents++;
expectedChannelNumber = 0;
impPre[0] = k.OBCIGanglionByteIdImpedanceChannelReference;
expectedReturnValue = {
channelNumber: expectedChannelNumber,
impedanceValue: expectedImpedanceValue
};
dataBuf = Buffer.concat([impPre, payloadBuf]);
ganglion.once('impedance', (actualImpedanceValue) => {
expect(actualImpedanceValue).to.deep.equal(expectedReturnValue);
runningEventCount++;
});
ganglion._processImpedanceData(dataBuf);
// Makes sure the correct amount of events were called.
expect(runningEventCount).to.equal(totalEvents)
});
});
xdescribe('#noble', function () {
xdescribe('#_nobleInit', function () {
it('should emit powered on', function (done) {
const ganglion = new Ganglion({
verbose: true,
nobleAutoStart: false,
nobleScanOnPowerOn: false
});
ganglion.once(k.OBCIEmitterBlePoweredUp, () => {
// Able to get powered up thing
done();
});
ganglion._nobleInit();
});
});
describe('#_nobleScan', function () {
const searchTime = k.OBCIGanglionBleSearchTime * 2;
this.timeout(searchTime + 1000);
it('gets peripherals', function (done) {
const ganglion = new Ganglion({
verbose: true,
nobleScanOnPowerOn: false
});
const doScan = () => {
ganglion._nobleScan(searchTime)
.then((list) => {
console.log('listPeripherals', list);
if (list) done();
})
.catch((err) => {
done(err);
console.log(err);
});
};
if (ganglion._nobleReady()) {
doScan();
} else {
ganglion.on('blePoweredOn', doScan());
}
});
});
});
+139
Ver Arquivo
@@ -0,0 +1,139 @@
'use strict';
// const sinon = require('sinon');
const chai = require('chai');
const should = chai.should(); // eslint-disable-line no-unused-vars
const expect = chai.expect;
const assert = chai.assert;
const utils = require('../openBCIGanglionUtils');
const k = require('../openBCIConstants');
var chaiAsPromised = require('chai-as-promised');
var sinonChai = require('sinon-chai');
chai.use(chaiAsPromised);
chai.use(sinonChai);
var getListOfPeripheralsOfSize = (perifsToMake) => {
perifsToMake = perifsToMake || 3;
let output = [];
for (var i = 0; i < perifsToMake; i++) {
output.push({
advertisement: {
localName: makeLocalName(i),
txPowerLevel: undefined,
manufacturerData: undefined,
serviceData: [],
serviceUuids: []
}
});
}
return output;
};
var makeLocalName = (num) => {
let localName = `${k.OBCIGanglionPrefix}-00`;
if (num < 10) {
localName = `${localName}0${num}`;
} else {
localName = `${localName}${num}`;
}
return localName;
};
describe('openBCIGanglionUtils', function () {
describe('#convert19bitAsInt32', function () {
it('converts a small positive number', function () {
const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680
const num = utils.convert19bitAsInt32(buf1);
assert.equal(num, 1680);
});
it('converts a small positive number', function () {
const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680
const num = utils.convert19bitAsInt32(buf1);
assert.equal(num, 1680);
});
it('converts a large positive number', function () {
const buf1 = new Buffer([0x02, 0xC0, 0x00]); // 0x02C001 === 180225
const num = utils.convert19bitAsInt32(buf1);
assert.equal(num, 180224);
});
it('converts a small negative number', function () {
const buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1
const num = utils.convert19bitAsInt32(buf1);
num.should.be.approximately(-1, 1);
});
it('converts a large negative number', function () {
const buf1 = new Buffer([0x04, 0xA1, 0x01]); // 0x04A101 === -220927
const num = utils.convert19bitAsInt32(buf1);
num.should.be.approximately(-220927, 1);
});
});
describe('#getPeripheralLocalNames', function () {
it('should resolve a list of localNames from an array of peripherals', function (done) {
let numPerifs = 3;
let perifs = getListOfPeripheralsOfSize(numPerifs);
utils.getPeripheralLocalNames(perifs).then(list => {
expect(list.length).to.equal(numPerifs);
for (var i = 0; i < list.length; i++) {
expect(list[i]).to.equal(makeLocalName(i));
}
done();
}).catch(done);
});
it('should reject if array is empty', function (done) {
utils.getPeripheralLocalNames([]).should.be.rejected.and.notify(done);
});
});
describe('#getPeripheralWithLocalName', function () {
it('should resovle a peripheral with local name', function (done) {
let numOfPerifs = 4;
let perifs = getListOfPeripheralsOfSize(numOfPerifs);
// console.log('perifs', perifs)
let goodName = makeLocalName(numOfPerifs - 1); // Will be in the list
// console.log(`goodName: ${goodName}`)
utils.getPeripheralWithLocalName(perifs, goodName).should.be.fulfilled.and.notify(done);
});
it('should reject if local name is not in perif list', function (done) {
let numOfPerifs = 4;
let perifs = getListOfPeripheralsOfSize(numOfPerifs);
let badName = makeLocalName(numOfPerifs + 2); // Garuenteed to not be in the list
utils.getPeripheralWithLocalName(perifs, badName).should.be.rejected.and.notify(done);
});
it('should reject if pArray is not array local name is not in perif list', function (done) {
let badName = makeLocalName(1); // Garuenteed to not be in the list
utils.getPeripheralWithLocalName(badName).should.be.rejected.and.notify(done);
});
});
describe('#isPeripheralGanglion', function () {
it('should return true when proper localName', function () {
let list = getListOfPeripheralsOfSize(1);
let perif = list[0];
expect(utils.isPeripheralGanglion(perif)).to.equal(true);
});
it('should return false when incorrect localName', function () {
let list = getListOfPeripheralsOfSize(1);
let perif = list[0];
perif.advertisement.localName = 'burrito';
expect(utils.isPeripheralGanglion(perif)).to.equal(false);
});
it('should return false when bad object', function () {
expect(utils.isPeripheralGanglion({})).to.equal(false);
});
it('should return false if nothing input', function () {
expect(utils.isPeripheralGanglion()).to.equal(false);
});
it('should return false if undfined unput input', function () {
let list = getListOfPeripheralsOfSize(1);
let perif = list[0];
perif.advertisement.localName = undefined;
expect(utils.isPeripheralGanglion(perif)).to.equal(false);
});
it('should return false when missing advertisement object', function () {
let list = getListOfPeripheralsOfSize(1);
let perif = list[0];
perif.advertisement = null;
expect(utils.isPeripheralGanglion(perif)).to.equal(false);
});
});
});
+108
Ver Arquivo
@@ -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 ++;
};