From 605b59d89b4bbea2cf94c88c44b58d093a1261a2 Mon Sep 17 00:00:00 2001 From: AJ Keller Date: Tue, 13 Dec 2016 12:56:47 -0500 Subject: [PATCH] Add: 19bit code back in and update byte ids for new mult-state parsing --- changelog.md | 12 + openBCIConstants.js | 122 +++++++-- openBCIGanglion.js | 210 ++++++---------- openBCIGanglionSample.js | 327 ++++++++++++++++++++++++ openBCIGanglionUtils.js | 197 --------------- package.json | 3 +- test/OpenBCIConstants-test.js | 162 ++++++++++-- test/openBCIGanglion-test.js | 389 ++++++++++++++++------------- test/openBCIGanglionSample-test.js | 195 +++++++++++++++ test/openBCIGanglionUtils-test.js | 139 ----------- 10 files changed, 1059 insertions(+), 697 deletions(-) create mode 100644 openBCIGanglionSample.js delete mode 100644 openBCIGanglionUtils.js create mode 100644 test/openBCIGanglionSample-test.js delete mode 100644 test/openBCIGanglionUtils-test.js diff --git a/changelog.md b/changelog.md index 5867a20..f540acf 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +# 0.3.0 + +### New Features +* Get accelerometer data from the ganglion! (Previous did not work while streaming) + +### Breaking Changes +* Major change in how bytes are parse based on byte ID. + +### Enhancements +* Refactor file names for clarity +* Removed dependency `underscore` + # 0.2.0 ### Enhancements diff --git a/openBCIConstants.js b/openBCIConstants.js index e834c1c..0b255b7 100644 --- a/openBCIConstants.js +++ b/openBCIConstants.js @@ -1,9 +1,11 @@ +'use strict'; /** * Created by ajk on 12/16/15. * Purpose: This file folds all the constants for the * OpenBCI Board */ -'use strict'; +const _ = require('lodash'); + /** Turning channels off */ const obciChannelOff1 = '1'; const obciChannelOff2 = '2'; @@ -129,29 +131,45 @@ const obciEmitterReady = 'ready'; const obciEmitterSample = 'sample'; const obciEmitterSynced = 'synced'; +/** Accel packets */ +const obciGanglionAccelAxisX = 1; +const obciGanglionAccelAxisY = 2; +const obciGanglionAccelAxisZ = 3; + +/** Accel scale factor */ +const obciGanglionAccelScaleFactor = 0.032; // mG per count + /** Ganglion */ const obciGanglionBleSearchTime = 20000; // ms const obciGanglionByteIdUncompressed = 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 obciGanglionByteId18Bit = { + max: 100, + min: 1 +}; +const obciGanglionByteId19Bit = { + max: 200, + min: 101 +}; +const obciGanglionByteIdImpedanceChannel1 = 201; +const obciGanglionByteIdImpedanceChannel2 = 202; +const obciGanglionByteIdImpedanceChannel3 = 203; +const obciGanglionByteIdImpedanceChannel4 = 204; +const obciGanglionByteIdImpedanceChannelReference = 205; +const obciGanglionByteIdMultiPacket = 206; +const obciGanglionByteIdMultiPacketStop = 207; const obciGanglionPacketSize = 20; const obciGanglionSamplesPerPacket = 2; -const obciGanglionPacket = { - accelStart: 1, - accelStop: 7, +const obciGanglionPacket18Bit = { auxByte: 20, byteId: 0, dataStart: 1, dataStop: 19 }; +const obciGanglionPacket19Bit = { + 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'; @@ -317,6 +335,8 @@ module.exports = { /** Accel enable/disable commands */ OBCIAccelStart: obciAccelStart, OBCIAccelStop: obciAccelStop, + /** Accel scale factor */ + OBCIGanglionAccelScaleFactor: obciGanglionAccelScaleFactor, /** Errors */ OBCIEmitterAccelerometer: obciEmitterAccelerometer, OBCIErrorNobleAlreadyScanning: errorNobleAlreadyScanning, @@ -367,7 +387,6 @@ module.exports = { /** Simulator Board Configurations */ OBCISimulatorRawAux: obciSimulatorRawAux, OBCISimulatorStandard: obciSimulatorStandard, - getVersionNumber, /** Emitters */ OBCIEmitterBlePoweredUp: obciEmitterBlePoweredUp, OBCIEmitterClose: obciEmitterClose, @@ -381,12 +400,15 @@ module.exports = { OBCIEmitterReady: obciEmitterReady, OBCIEmitterSample: obciEmitterSample, OBCIEmitterSynced: obciEmitterSynced, + /** Accel packets */ + OBCIGanglionAccelAxisX: obciGanglionAccelAxisX, + OBCIGanglionAccelAxisY: obciGanglionAccelAxisY, + OBCIGanglionAccelAxisZ: obciGanglionAccelAxisZ, /** Ganglion */ OBCIGanglionBleSearchTime: obciGanglionBleSearchTime, OBCIGanglionByteIdUncompressed: obciGanglionByteIdUncompressed, - OBCIGanglionByteIdSampleMax: obciGanglionByteIdSampleMax, - OBCIGanglionByteIdSampleMin: obciGanglionByteIdSampleMin, - OBCIGanglionByteIdAccel: obciGanglionByteIdAccel, + OBCIGanglionByteId18Bit: obciGanglionByteId18Bit, + OBCIGanglionByteId19Bit: obciGanglionByteId19Bit, OBCIGanglionByteIdImpedanceChannel1: obciGanglionByteIdImpedanceChannel1, OBCIGanglionByteIdImpedanceChannel2: obciGanglionByteIdImpedanceChannel2, OBCIGanglionByteIdImpedanceChannel3: obciGanglionByteIdImpedanceChannel3, @@ -397,7 +419,8 @@ module.exports = { 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, + OBCIGanglionPacket18Bit: obciGanglionPacket18Bit, + OBCIGanglionPacket19Bit: obciGanglionPacket19Bit, OBCIGanglionPrefix: obciGanglionPrefix, OBCIGanglionSamplesPerPacket: obciGanglionSamplesPerPacket, OBCIGanglionSyntheticDataEnable: obciGanglionSyntheticDataEnable, @@ -421,9 +444,49 @@ module.exports = { OBCINobleEmitterScanStart: obciNobleEmitterScanStart, OBCINobleEmitterScanStop: obciNobleEmitterScanStop, OBCINobleEmitterStateChange: obciNobleEmitterStateChange, - OBCINobleStatePoweredOn: obciNobleStatePoweredOn + OBCINobleStatePoweredOn: obciNobleStatePoweredOn, + getPeripheralLocalNames, + getPeripheralWithLocalName, + getVersionNumber, + isPeripheralGanglion }; +/** + * @description Get a list of local names from an array of peripherals + */ +function getPeripheralLocalNames (pArray) { + return new Promise((resolve, reject) => { + var list = []; + _.forEach(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`); + _.forEach(pArray, (perif) => { + if (perif.advertisement.hasOwnProperty('localName')) { + if (perif.advertisement.localName === localName) { + return resolve(perif); + } + } + }); + return reject(`No peripheral found with localName: ${localName}`); + }); +} + /** * @description This function is used to extract the major version from a github * version string. @@ -432,3 +495,22 @@ module.exports = { function getVersionNumber (versionStr) { return Number(versionStr[1]); } + +/** + * @description Very safely checks to see if the noble peripheral is a + * ganglion by way of checking the local name property. + */ +function 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(obciGanglionPrefix) > -1) { + return true; + } + } + } + } + } + return false; +} \ No newline at end of file diff --git a/openBCIGanglion.js b/openBCIGanglion.js index fa14c34..36585a2 100644 --- a/openBCIGanglion.js +++ b/openBCIGanglion.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const noble = require('noble'); const util = require('util'); // Local imports -const utils = require('./openBCIGanglionUtils'); +const ganglionSample = require('./openBCIGanglionSample'); const k = require('./openBCIConstants'); const openBCIUtils = require('./openBCIUtils'); const clone = require('clone'); @@ -104,6 +104,7 @@ function Ganglion (options) { this.options = clone(opts); /** Private Properties (keep alphabetical) */ + this._accelArray = [0, 0, 0]; this._connected = false; this._decompressedSamples = new Array(3); this._droppedPacketCounter = 0; @@ -112,7 +113,7 @@ function Ganglion (options) { this._lastPacket = null; this._localName = null; this._multiPacketBuffer = null; - this._packetCounter = k.OBCIGanglionByteIdSampleMax; + this._packetCounter = k.OBCIGanglionByteId18Bit.max; this._peripheral = null; this._scanning = false; this._sendCharacteristic = null; @@ -199,7 +200,7 @@ Ganglion.prototype.channelOn = function (channelNumber) { Ganglion.prototype.connect = function (id) { return new Promise((resolve, reject) => { if (_.isString(id)) { - utils.getPeripheralWithLocalName(this.ganglionPeripheralArray, id) + ganglionSample.getPeripheralWithLocalName(this.ganglionPeripheralArray, id) .then((p) => { this._nobleConnect(p); }) @@ -281,13 +282,7 @@ Ganglion.prototype.getMutliPacketBuffer = function () { * @return {global.Promise|Promise} */ Ganglion.prototype.impedanceStart = function () { - return new Promise((resolve, reject) => { - this.write(k.OBCIGanglionImpedanceStart) - .then(() => { - resolve(); - }) - .catch(reject); - }); + return this.write(k.OBCIGanglionImpedanceStart); }; /** @@ -295,13 +290,7 @@ Ganglion.prototype.impedanceStart = function () { * @return {global.Promise|Promise} */ Ganglion.prototype.impedanceStop = function () { - return new Promise((resolve, reject) => { - this.write(k.OBCIGanglionImpedanceStop) - .then(() => { - resolve(); - }) - .catch(reject); - }); + return this.write(k.OBCIGanglionImpedanceStop); }; /** @@ -526,97 +515,6 @@ Ganglion.prototype._buildSample = function (sampleNumber, rawData) { return sample; }; -/** - * Called to when a compressed packet is received. - * @param buffer {Buffer} Just the data portion of the sample. So 18 bytes. - * @return {Array} - An array of deltas of shape 2x4 (2 samples per packet - * and 4 channels per sample.) - * @private - */ -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] >> 6), - ((buffer[0] & 0x3F) << 2) | (buffer[1] >> 6), - ((buffer[1] & 0x3F) << 2) | (buffer[2] >> 6) - ] - ); - receivedDeltas[0][0] = utils.convert18bitAsInt32(miniBuf); - - // Sample 1 - Channel 2 - miniBuf = new Buffer( - [ - (buffer[2] & 0x3F) >> 4, - (buffer[2] << 4) | (buffer[3] >> 4), - (buffer[3] << 4) | (buffer[4] >> 4) - ]); - // miniBuf = new Buffer([(buffer[2] & 0x1F), buffer[3], buffer[4] >> 2]); - receivedDeltas[0][1] = utils.convert18bitAsInt32(miniBuf); - - // Sample 1 - Channel 3 - miniBuf = new Buffer( - [ - (buffer[4] & 0x0F) >> 2, - (buffer[4] << 6) | (buffer[5] >> 2), - (buffer[5] << 6) | (buffer[6] >> 2) - ]); - receivedDeltas[0][2] = utils.convert18bitAsInt32(miniBuf); - - // Sample 1 - Channel 4 - miniBuf = new Buffer( - [ - (buffer[6] & 0x03), - buffer[7], - buffer[8] - ]); - receivedDeltas[0][3] = utils.convert18bitAsInt32(miniBuf); - - // Sample 2 - Channel 1 - miniBuf = new Buffer( - [ - (buffer[9] >> 6), - ((buffer[9] & 0x3F) << 2) | (buffer[10] >> 6), - ((buffer[10] & 0x3F) << 2) | (buffer[11] >> 6) - ]); - receivedDeltas[1][0] = utils.convert18bitAsInt32(miniBuf); - - // Sample 2 - Channel 2 - miniBuf = new Buffer( - [ - (buffer[11] & 0x3F) >> 4, - (buffer[11] << 4) | (buffer[12] >> 4), - (buffer[12] << 4) | (buffer[13] >> 4) - ]); - receivedDeltas[1][1] = utils.convert18bitAsInt32(miniBuf); - - // Sample 2 - Channel 3 - miniBuf = new Buffer( - [ - (buffer[13] & 0x0F) >> 2, - (buffer[13] << 6) | (buffer[14] >> 2), - (buffer[14] << 6) | (buffer[15] >> 2) - ]); - receivedDeltas[1][2] = utils.convert18bitAsInt32(miniBuf); - - // Sample 2 - Channel 4 - miniBuf = new Buffer([(buffer[15] & 0x03), buffer[16], buffer[17]]); - receivedDeltas[1][3] = utils.convert18bitAsInt32(miniBuf); - - return receivedDeltas; -}; - /** * Utilize `receivedDeltas` to get actual count values. * @param receivedDeltas {Array} - An array of deltas @@ -790,7 +688,7 @@ Ganglion.prototype._nobleInit = function () { Ganglion.prototype._nobleOnDeviceDiscoveredCallback = function (peripheral) { // if(this.options.verbose) console.log(peripheral.advertisement); this.peripheralArray.push(peripheral); - if (utils.isPeripheralGanglion(peripheral)) { + if (k.isPeripheralGanglion(peripheral)) { if (this.options.verbose) console.log('Found ganglion!'); if (_.isUndefined(_.find(this.ganglionPeripheralArray, (p) => { @@ -857,13 +755,10 @@ 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.OBCIGanglionByteId19Bit.max) { this._processProcessSampleData(data); } else { switch (byteId) { - case k.OBCIGanglionByteIdAccel: - this._processAccel(data); - break; case k.OBCIGanglionByteIdMultiPacket: this._processMultiBytePacket(data); break; @@ -883,18 +778,6 @@ Ganglion.prototype._processBytes = function (data) { } }; -/** - * Process an accel packet of data. - * @param data {Buffer} - * Data packet buffer from noble. - * @private - */ -Ganglion.prototype._processAccel = function (data) { - if (this.options.debug) openBCIUtils.debugBytes('Accel <<< ', data); - const accelData = utils.getDataArrayAccel(data.slice(k.OBCIGanglionPacket.accelStart, k.OBCIGanglionPacket.accelStop), this.options.sendCounts); - this.emit(k.OBCIEmitterAccelerometer, accelData); -}; - /** * Process an compressed packet of data. * @param data {Buffer} @@ -902,16 +785,41 @@ Ganglion.prototype._processAccel = function (data) { * @private */ Ganglion.prototype._processCompressedData = function (data) { + // Save the packet counter + this._packetCounter = parseInt(data[0]); + // Decompress the buffer into array - this._decompressSamples(this._decompressDeltas(data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop))); + if (this._packetCounter <= k.OBCIGanglionByteId18Bit.max) { + this._decompressSamples(ganglionSample.decompressDeltas18Bit(data.slice(k.OBCIGanglionPacket18Bit.dataStart, k.OBCIGanglionPacket18Bit.dataStop))); + switch (this._packetCounter % 10) { + case k.OBCIGanglionAccelAxisX: + this._accelArray[0] = this.options.sendCounts ? data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) : data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) * k.OBCIGanglionAccelScaleFactor; + break; + case k.OBCIGanglionAccelAxisY: + this._accelArray[1] = this.options.sendCounts ? data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) : data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) * k.OBCIGanglionAccelScaleFactor; + break; + case k.OBCIGanglionAccelAxisZ: + this._accelArray[2] = this.options.sendCounts ? data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) : data.readInt8(k.OBCIGanglionPacket18Bit.auxByte - 1) * k.OBCIGanglionAccelScaleFactor; + this.emit(k.OBCIEmitterAccelerometer, this._accelArray); + break; + default: + break; + } + const sample1 = this._buildSample(this._packetCounter * 2 - 1, this._decompressedSamples[1]); + this.emit(k.OBCIEmitterSample, sample1); - this._packetCounter = parseInt(data[0]); + const sample2 = this._buildSample(this._packetCounter * 2, this._decompressedSamples[2]); + this.emit(k.OBCIEmitterSample, sample2); - const sample1 = this._buildSample(this._packetCounter * 2 - 1, this._decompressedSamples[1]); - this.emit(k.OBCIEmitterSample, sample1); + } else { + this._decompressSamples(ganglionSample.decompressDeltas19Bit(data.slice(k.OBCIGanglionPacket19Bit.dataStart, k.OBCIGanglionPacket19Bit.dataStop))); + + const sample1 = this._buildSample((this._packetCounter - 100) * 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); + const sample2 = this._buildSample((this._packetCounter - 100) * 2, this._decompressedSamples[2]); + this.emit(k.OBCIEmitterSample, sample2); + } // Rotate the 0 position for next time for (let i = 0; i < k.OBCINumberOfChannelsGanglion; i++) { @@ -973,9 +881,9 @@ Ganglion.prototype._processImpedanceData = function (data) { */ Ganglion.prototype._processMultiBytePacket = function (data) { if (this._multiPacketBuffer) { - this._multiPacketBuffer = Buffer.concat([this._multiPacketBuffer, data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop)]); + this._multiPacketBuffer = Buffer.concat([this._multiPacketBuffer, data.slice(k.OBCIGanglionPacket19Bit.dataStart, k.OBCIGanglionPacket19Bit.dataStop)]); } else { - this._multiPacketBuffer = data.slice(k.OBCIGanglionPacket.dataStart, k.OBCIGanglionPacket.dataStop); + this._multiPacketBuffer = data.slice(k.OBCIGanglionPacket19Bit.dataStart, k.OBCIGanglionPacket19Bit.dataStop); } }; @@ -1002,6 +910,11 @@ Ganglion.prototype._droppedPacket = function (droppedPacketNumber) { this._droppedPacketCounter++; }; +/** + * Checks for dropped packets + * @param data {Buffer} + * @private + */ Ganglion.prototype._processProcessSampleData = function(data) { const curByteId = parseInt(data[0]); const difByteId = curByteId - this._packetCounter; @@ -1014,22 +927,39 @@ Ganglion.prototype._processProcessSampleData = function(data) { // Wrap around situation if (difByteId < 0) { - if (this._packetCounter === 127) { - if (curByteId !== 0) { + if (this._packetCounter <= k.OBCIGanglionByteId18Bit.max) { + if (this._packetCounter === k.OBCIGanglionByteId18Bit.max) { + if (curByteId !== k.OBCIGanglionByteIdUncompressed) { + this._droppedPacket(curByteId - 1); + } + } else { + let tempCounter = this._packetCounter + 1; + while (tempCounter <= k.OBCIGanglionByteId18Bit.max) { + this._droppedPacket(tempCounter); + tempCounter++; + } + } + } else if (this._packetCounter === k.OBCIGanglionByteId19Bit.max) { + if (curByteId !== k.OBCIGanglionByteIdUncompressed) { this._droppedPacket(curByteId - 1); } } else { let tempCounter = this._packetCounter + 1; - while (tempCounter <= 127) { + while (tempCounter <= k.OBCIGanglionByteId19Bit.max) { this._droppedPacket(tempCounter); tempCounter++; } } } else if (difByteId > 1) { - let tempCounter = this._packetCounter + 1; - while (tempCounter < curByteId) { - this._droppedPacket(tempCounter); - tempCounter++; + if (this._packetCounter === k.OBCIGanglionByteIdUncompressed && curByteId === k.OBCIGanglionByteId19Bit.min) { + this._processRouteSampleData(data); + return; + } else { + let tempCounter = this._packetCounter + 1; + while (tempCounter < curByteId) { + this._droppedPacket(tempCounter); + tempCounter++; + } } } this._processRouteSampleData(data); diff --git a/openBCIGanglionSample.js b/openBCIGanglionSample.js new file mode 100644 index 0000000..5ef2ceb --- /dev/null +++ b/openBCIGanglionSample.js @@ -0,0 +1,327 @@ +'use strict'; +const k = require('./openBCIConstants'); + +module.exports = { + convert18bitAsInt32, + convert19bitAsInt32, + decompressDeltas18Bit, + decompressDeltas19Bit, + 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 + ]); + } +}; + +/** + * Converts a two byte buffer into a 32bit number + * @param twoByteBuffer {Buffer} - two's complement number + * @return {number} + * Converted number + */ +function interpret16bitAsInt32 (twoByteBuffer) { + var prefix = 0; + + if (twoByteBuffer[0] > 127) { + // console.log('\t\tNegative number') + prefix = 65535; // 0xFFFF + } + + return (prefix << 16) | (twoByteBuffer[0] << 8) | twoByteBuffer[1]; +} + +/** + * Converts a special ganglion 18 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 18 bits of actual data. + * @return {number} A signed integer. + */ +function convert18bitAsInt32 (threeByteBuffer) { + let prefix = 0; + + if (threeByteBuffer[2] & 0x01 > 0) { + // console.log('\t\tNegative number') + prefix = 0b11111111111111; + } + + return (prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2]; +} + +/** + * 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. + */ +function 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]; +} + +/** + * Called to when a compressed packet is received. + * @param buffer {Buffer} Just the data portion of the sample. So 18 bytes. + * @return {Array} - An array of deltas of shape 2x4 (2 samples per packet + * and 4 channels per sample.) + * @private + */ +function decompressDeltas18Bit (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] >> 6), + ((buffer[0] & 0x3F) << 2) | (buffer[1] >> 6), + ((buffer[1] & 0x3F) << 2) | (buffer[2] >> 6) + ] + ); + receivedDeltas[0][0] = convert18bitAsInt32(miniBuf); + + // Sample 1 - Channel 2 + miniBuf = new Buffer( + [ + (buffer[2] & 0x3F) >> 4, + (buffer[2] << 4) | (buffer[3] >> 4), + (buffer[3] << 4) | (buffer[4] >> 4) + ]); + // miniBuf = new Buffer([(buffer[2] & 0x1F), buffer[3], buffer[4] >> 2]); + receivedDeltas[0][1] = convert18bitAsInt32(miniBuf); + + // Sample 1 - Channel 3 + miniBuf = new Buffer( + [ + (buffer[4] & 0x0F) >> 2, + (buffer[4] << 6) | (buffer[5] >> 2), + (buffer[5] << 6) | (buffer[6] >> 2) + ]); + receivedDeltas[0][2] = convert18bitAsInt32(miniBuf); + + // Sample 1 - Channel 4 + miniBuf = new Buffer( + [ + (buffer[6] & 0x03), + buffer[7], + buffer[8] + ]); + receivedDeltas[0][3] = convert18bitAsInt32(miniBuf); + + // Sample 2 - Channel 1 + miniBuf = new Buffer( + [ + (buffer[9] >> 6), + ((buffer[9] & 0x3F) << 2) | (buffer[10] >> 6), + ((buffer[10] & 0x3F) << 2) | (buffer[11] >> 6) + ]); + receivedDeltas[1][0] = convert18bitAsInt32(miniBuf); + + // Sample 2 - Channel 2 + miniBuf = new Buffer( + [ + (buffer[11] & 0x3F) >> 4, + (buffer[11] << 4) | (buffer[12] >> 4), + (buffer[12] << 4) | (buffer[13] >> 4) + ]); + receivedDeltas[1][1] = convert18bitAsInt32(miniBuf); + + // Sample 2 - Channel 3 + miniBuf = new Buffer( + [ + (buffer[13] & 0x0F) >> 2, + (buffer[13] << 6) | (buffer[14] >> 2), + (buffer[14] << 6) | (buffer[15] >> 2) + ]); + receivedDeltas[1][2] = convert18bitAsInt32(miniBuf); + + // Sample 2 - Channel 4 + miniBuf = new Buffer([(buffer[15] & 0x03), buffer[16], buffer[17]]); + receivedDeltas[1][3] = convert18bitAsInt32(miniBuf); + + return receivedDeltas; +} + +/** + * Called to when a compressed packet is received. + * @param buffer {Buffer} Just the data portion of the sample. So 19 bytes. + * @return {Array} - An array of deltas of shape 2x4 (2 samples per packet + * and 4 channels per sample.) + * @private + */ +function decompressDeltas19Bit (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] = 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] = 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] = 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] = 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] = 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] = 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] = convert19bitAsInt32(miniBuf); + + // Sample 2 - Channel 4 + miniBuf = new Buffer([(buffer[16] & 0x07), buffer[17], buffer[18]]); + receivedDeltas[1][3] = convert19bitAsInt32(miniBuf); + + return receivedDeltas; +}; diff --git a/openBCIGanglionUtils.js b/openBCIGanglionUtils.js deleted file mode 100644 index 5819436..0000000 --- a/openBCIGanglionUtils.js +++ /dev/null @@ -1,197 +0,0 @@ -'use strict'; -const k = require('./openBCIConstants'); -const _ = require('underscore'); - -module.exports = { - getPeripheralLocalNames, - getPeripheralWithLocalName, - /** - * Converts a special ganglion 18 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 18 bits of actual data. - * @return {number} A signed integer. - */ - convert18bitAsInt32: (threeByteBuffer) => { - let prefix = 0; - - if (threeByteBuffer[2] & 0x01 > 0) { - // console.log('\t\tNegative number') - prefix = 0b11111111111111; - } - - return (prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2]; - }, - /** - * @description Takes a buffer filled with 3 16 bit integers from an OpenBCI device and converts based on settings - * of the MPU, values are in ? - * @param dataBuf {Buffer} - Buffer that is 6 bytes long - * @param sendCounts {Boolean} - Multiply by scale factor. - * @returns {Array} - Array of floats 3 elements long - * @author AJ Keller (@pushtheworldllc) - */ - getDataArrayAccel: (dataBuf, sendCounts) => { - const ACCEL_NUMBER_AXIS = 3; - // Scale factor for aux data - const SCALE_FACTOR_ACCEL = 0.008 / Math.pow(2, 6); - // Must assume +/-4g, normal mode (opposed to low power mode), 12 bits left justfied. - let accelData = []; - for (let i = 0; i < ACCEL_NUMBER_AXIS; i++) { - let index = i * 2; - if (sendCounts) { - accelData.push(interpret16bitAsInt32(dataBuf.slice(index, index + 2))); - } else { - accelData.push(interpret16bitAsInt32(dataBuf.slice(index, index + 2)) * SCALE_FACTOR_ACCEL); - } - } - return accelData; - }, - /** - * @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, 0, 1, 0, 2]); - }, - 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}`); - }); -} - -function interpret16bitAsInt32 (twoByteBuffer) { - var prefix = 0; - - if (twoByteBuffer[0] > 127) { - // console.log('\t\tNegative number') - prefix = 65535; // 0xFFFF - } - - return (prefix << 16) | (twoByteBuffer[0] << 8) | twoByteBuffer[1]; -} diff --git a/package.json b/package.json index 2919f8e..de35f1f 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ "noble": "^1.7.0", "performance-now": "^0.2.0", "sntp": "^2.0.0", - "streamsearch": "^0.1.2", - "underscore": "^1.8.3" + "streamsearch": "^0.1.2" }, "directories": { "test": "test" diff --git a/test/OpenBCIConstants-test.js b/test/OpenBCIConstants-test.js index df8fe70..0e99d63 100644 --- a/test/OpenBCIConstants-test.js +++ b/test/OpenBCIConstants-test.js @@ -11,6 +11,35 @@ const should = chai.should(); // eslint-disable-line no-unused-vars const chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); +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('OpenBCIConstants', function () { // afterEach(() => bluebirdChecks.noPendingPromises()); describe('Turning Channels Off', function () { @@ -169,6 +198,17 @@ describe('OpenBCIConstants', function () { assert.equal('N', k.OBCIAccelStop); }); }); + describe('Accel packet numbers', function () { + it('X axis', function () { + assert.equal(1, k.OBCIGanglionAccelAxisX); + }); + it('Y axis', function () { + assert.equal(2, k.OBCIGanglionAccelAxisY); + }); + it('Z axis', function () { + assert.equal(3, k.OBCIGanglionAccelAxisZ); + }); + }); describe('Miscellaneous', function () { it('queries register settings', function () { assert.equal('?', k.OBCIMiscQueryRegisterSettings); @@ -292,6 +332,9 @@ describe('OpenBCIConstants', function () { it('Ganglion prefix', function () { assert.equal('Ganglion', k.OBCIGanglionPrefix); }); + it('Ganglion ble search time', function () { + assert.equal(0.032, k.OBCIGanglionAccelScaleFactor); + }); it('Ganglion ble search time', function () { assert.equal(20000, k.OBCIGanglionBleSearchTime); }); @@ -301,11 +344,20 @@ describe('OpenBCIConstants', function () { it('samples per packet', function () { assert.equal(2, k.OBCIGanglionSamplesPerPacket); }); - it('packet positions', function () { - assert.equal(0, k.OBCIGanglionPacket.byteId); - assert.equal(1, k.OBCIGanglionPacket.dataStart); - assert.equal(19, k.OBCIGanglionPacket.dataStop); - assert.equal(20, k.OBCIGanglionPacket.auxByte); + it('packet positions 18 bit', function () { + expect(k.OBCIGanglionPacket18Bit).to.deep.equal({ + auxByte: 20, + byteId: 0, + dataStart: 1, + dataStop: 19 + }); + }); + it('packet positions 19 bit', function () { + expect(k.OBCIGanglionPacket19Bit).to.deep.equal({ + byteId: 0, + dataStart: 1, + dataStop: 20 + }); }); }); describe('Commands', function () { @@ -323,38 +375,41 @@ describe('OpenBCIConstants', function () { }); }); describe('Byte Id', function () { - it('Raw data', function () { + it('Uncompressed', function () { assert.equal(0, k.OBCIGanglionByteIdUncompressed); }); - it('Sample maximum', function () { - assert.equal(127, k.OBCIGanglionByteIdSampleMax); - }); - it('Sample minimum', function () { - assert.equal(1, k.OBCIGanglionByteIdSampleMin); + it('should have correct values for 18 bit', function () { + expect(k.OBCIGanglionByteId18Bit).to.deep.equal({ + max: 100, + min: 1 + }) }); - it('Accel', function () { - assert.equal(128, k.OBCIGanglionByteIdAccel); + it('should have correct values for 18 bit', function () { + expect(k.OBCIGanglionByteId19Bit).to.deep.equal({ + max: 200, + min: 101 + }) }); it('Impedance channel 1', function () { - assert.equal(129, k.OBCIGanglionByteIdImpedanceChannel1); + assert.equal(201, k.OBCIGanglionByteIdImpedanceChannel1); }); it('Impedance channel 2', function () { - assert.equal(130, k.OBCIGanglionByteIdImpedanceChannel2); + assert.equal(202, k.OBCIGanglionByteIdImpedanceChannel2); }); it('Impedance channel 3', function () { - assert.equal(131, k.OBCIGanglionByteIdImpedanceChannel3); + assert.equal(203, k.OBCIGanglionByteIdImpedanceChannel3); }); it('Impedance channel 4', function () { - assert.equal(132, k.OBCIGanglionByteIdImpedanceChannel4); + assert.equal(204, k.OBCIGanglionByteIdImpedanceChannel4); }); it('Impedance channel reference', function () { - assert.equal(133, k.OBCIGanglionByteIdImpedanceChannelReference); + assert.equal(205, k.OBCIGanglionByteIdImpedanceChannelReference); }); it('Multi packet', function () { - assert.equal(134, k.OBCIGanglionByteIdMultiPacket); + assert.equal(206, k.OBCIGanglionByteIdMultiPacket); }); it('Multi packet stop', function () { - assert.equal(135, k.OBCIGanglionByteIdMultiPacketStop); + assert.equal(207, k.OBCIGanglionByteIdMultiPacketStop); }); }); describe('simblee', function () { @@ -406,4 +461,71 @@ describe('OpenBCIConstants', function () { assert.equal('poweredOn', k.OBCINobleStatePoweredOn); }); }); + describe('#getPeripheralLocalNames', function () { + it('should resolve a list of localNames from an array of peripherals', function (done) { + let numPerifs = 3; + let perifs = getListOfPeripheralsOfSize(numPerifs); + k.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) { + k.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}`) + k.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 + k.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 + k.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(k.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(k.isPeripheralGanglion(perif)).to.equal(false); + }); + it('should return false when bad object', function () { + expect(k.isPeripheralGanglion({})).to.equal(false); + }); + it('should return false if nothing input', function () { + expect(k.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(k.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(k.isPeripheralGanglion(perif)).to.equal(false); + }); + }); }); diff --git a/test/openBCIGanglion-test.js b/test/openBCIGanglion-test.js index a3b3532..642232b 100644 --- a/test/openBCIGanglion-test.js +++ b/test/openBCIGanglion-test.js @@ -9,7 +9,7 @@ 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 ganglionSample = require('../openBCIGanglionSample'); const clone = require('clone'); chai.use(chaiAsPromised); @@ -38,66 +38,6 @@ describe('#ganglion', function () { 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 - 0b00100000, // 4 - 0b00000000, // 5 - 0b00101000, // 6 - 0b00000000, // 7 - 0b00000100, // 8 - 0b10000000, // 9 - 0b00000000, // 10 - 0b10111100, // 11 - 0b00000000, // 12 - 0b00000111, // 13 - 0b00000000, // 14 - 0b00101000, // 15 - 0b11000000, // 16 - 0b00001010 // 17 - ]); - let expectedValue = [[0, 2, 10, 4], [131074, 245760, 114698, 49162]]; - 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 - 0b01111111, // 2 - 0b11111111, // 3 - 0b10111111, // 4 - 0b11111111, // 5 - 0b11100111, // 6 - 0b11111111, // 7 - 0b11110101, // 8 - 0b00000000, // 9 - 0b00000001, // 10 - 0b01001111, // 11 - 0b10001110, // 12 - 0b00110000, // 13 - 0b00000000, // 14 - 0b00011111, // 15 - 0b11110000, // 16 - 0b00000001 // 17 - ]); - 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); @@ -145,41 +85,6 @@ describe('#ganglion', function () { expect(ganglion.getMutliPacketBuffer()).to.equal(null); expect(messageEventCalled).to.equal(true); }); - describe('accel', function () { - after(() => { - ganglion.removeAllListeners('accelerometer'); - }); - afterEach(() => { - ganglion.options.sendCounts = false; - }); - it('should emit a accel data array with counts', function () { - const bufAccel = utils.sampleAccel(); - const dimensions = 3; - const accelDataFunc = (accelData) => { - expect(accelData.length).to.equal(dimensions); - for (let i = 0; i < dimensions; i++) { - expect(accelData[i]).to.equal(i); - } - }; - ganglion.on('accelerometer', accelDataFunc); - ganglion.options.sendCounts = true; - ganglion._processAccel(bufAccel); - ganglion.removeListener('accelerometer', accelDataFunc); - }); - it('should emit a accel data array with counts', function () { - const bufAccel = utils.sampleAccel(); - const dimensions = 3; - const accelDataFunc = (accelData) => { - expect(accelData.length).to.equal(dimensions); - for (let i = 0; i < dimensions; i++) { - expect(accelData[i]).to.equal(i * 0.008 / Math.pow(2, 6)); - } - }; - ganglion.on('accelerometer', accelDataFunc); - ganglion._processAccel(bufAccel); - ganglion.removeListener('accelerometer', accelDataFunc); - }); - }); describe('#_processProcessSampleData', function () { let funcSpyCompressedData; let funcSpyDroppedPacket; @@ -195,83 +100,215 @@ describe('#ganglion', function () { funcSpyUncompressedData.reset(); ganglion._resetDroppedPacketSystem(); }); - it('should call proper functions if no dropped packets', function () { - it('should work on uncompressed data', function () { - ganglion._processProcessSampleData(utils.sampleUncompressedData()); - funcSpyUncompressedData.should.have.been.called; + describe('18bit', function () { + it('should call proper functions if no dropped packets', function () { + it('should work on uncompressed data', function () { + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + funcSpyUncompressedData.should.have.been.called; + funcSpyDroppedPacket.should.not.have.been.called; + }); + + it('should work on compressed data', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(1)); + funcSpyCompressedData.should.have.been.called; + funcSpyDroppedPacket.should.not.have.been.called; + }); + }); + it('should recognize 0 packet dropped', function () { + // Send the last buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId18Bit.max)); + funcSpyCompressedData.should.have.been.called; + const expectedMissedSample = k.OBCIGanglionByteIdUncompressed; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyCompressedData.should.have.been.calledTwice; + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should not find a dropped packet on wrap around', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId18Bit.max - 1)); + funcSpyCompressedData.should.have.been.calledOnce; + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId18Bit.max)); + funcSpyCompressedData.should.have.been.calledTwice; + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + funcSpyCompressedData.should.have.been.calledTwice; + funcSpyUncompressedData.should.have.been.calledOnce; + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteIdUncompressed + 1)); + funcSpyCompressedData.should.have.been.calledThrice; funcSpyDroppedPacket.should.not.have.been.called; }); + it('should recognize dropped packet 99', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId18Bit.max - 1)); + const expectedMissedSample = k.OBCIGanglionByteId18Bit.max; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleUncompressedData(); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should recognize dropped packet 98 and 99', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId18Bit.max - 2)); + const expectedMissedSample1 = k.OBCIGanglionByteId18Bit.max - 1; + const expectedMissedSample2 = k.OBCIGanglionByteId18Bit.max; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleUncompressedData(); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); + }); + it('should detect dropped packet 1 and process packet 2', function () { + // Send the raw buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + const expectedMissedSample = k.OBCIGanglionByteIdUncompressed + 1; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should detect dropped packet 1 & 2 and add process packet 3', function () { + // Send the last buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + const expectedMissedSample1 = k.OBCIGanglionByteIdUncompressed + 1; + const expectedMissedSample2 = k.OBCIGanglionByteIdUncompressed + 2; + // Call the function under test with two more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample2 + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); + }); + it('should emit a accel data array with counts', function () { + const bufAccelX = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisX); + const bufAccelY = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisY); + const bufAccelZ = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisZ); + const expectedXCount = 0; + const expectedYCount = 1; + const expectedZCount = 2; + bufAccelX[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedXCount; + bufAccelY[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedYCount; + bufAccelZ[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedZCount; + const dimensions = 3; + let accelDataFuncCalled = false; + const accelDataFunc = (accelData) => { + accelDataFuncCalled = true; + expect(accelData.length).to.equal(dimensions); + for (let i = 0; i < dimensions; i++) { + expect(accelData[i]).to.equal(i); + } + }; + ganglion.once('accelerometer', accelDataFunc); + ganglion.options.sendCounts = true; + ganglion._processProcessSampleData(bufAccelX); + ganglion._processProcessSampleData(bufAccelY); + ganglion._processProcessSampleData(bufAccelZ); + expect(accelDataFuncCalled).to.be.equal(true); + ganglion.options.sendCounts = false; + ganglion.removeListener('accelerometer', accelDataFunc); + }); + it('should emit a accel data array with scaled values', function () { + const bufAccelX = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisX); + const bufAccelY = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisY); + const bufAccelZ = ganglionSample.sampleCompressedData(k.OBCIGanglionAccelAxisZ); + const expectedXCount = 0; + const expectedYCount = 1; + const expectedZCount = 2; + bufAccelX[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedXCount; + bufAccelY[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedYCount; + bufAccelZ[k.OBCIGanglionPacket18Bit.auxByte - 1] = expectedZCount; + const dimensions = 3; + let accelDataFuncCalled = false; + const accelDataFunc = (accelData) => { + accelDataFuncCalled = true; + expect(accelData.length).to.equal(dimensions); + for (let i = 0; i < dimensions; i++) { + expect(accelData[i]).to.equal(i * 0.032); + } + }; + ganglion.once('accelerometer', accelDataFunc); + ganglion.options.sendCounts = false; + ganglion._processProcessSampleData(bufAccelX); + ganglion._processProcessSampleData(bufAccelY); + ganglion._processProcessSampleData(bufAccelZ); + expect(accelDataFuncCalled).to.be.equal(true); + ganglion.removeListener('accelerometer', accelDataFunc); + }); + }); + describe('19bit', function () { + it('should call proper functions if no dropped packets', function () { + it('should work on uncompressed data', function () { + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + funcSpyUncompressedData.should.have.been.called; + funcSpyDroppedPacket.should.not.have.been.called; + }); - it('should work on compressed data', function () { - ganglion._processProcessSampleData(utils.sampleCompressedData(1)); + it('should work on compressed data', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.min)); + funcSpyCompressedData.should.have.been.called; + funcSpyDroppedPacket.should.not.have.been.called; + }); + }); + it('should recognize packet 101 was dropped', function () { + // Send the last buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.max)); funcSpyCompressedData.should.have.been.called; + const expectedMissedSample = k.OBCIGanglionByteIdUncompressed; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyCompressedData.should.have.been.calledTwice; + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should not find a dropped packet on wrap around', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.max - 1)); + funcSpyCompressedData.should.have.been.calledOnce; + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.max)); + funcSpyCompressedData.should.have.been.calledTwice; + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + funcSpyCompressedData.should.have.been.calledTwice; + funcSpyUncompressedData.should.have.been.calledOnce; + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.min)); + funcSpyCompressedData.should.have.been.calledThrice; funcSpyDroppedPacket.should.not.have.been.called; }); - }); - it('should recognize 0 packet dropped', function () { - // Send the last buffer, set's ganglion._packetCounter - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdSampleMax)); - funcSpyCompressedData.should.have.been.called; - const expectedMissedSample = k.OBCIGanglionByteIdUncompressed; - // Call the function under test with one more then expected - const nextPacket = utils.sampleCompressedData(expectedMissedSample + 1); - ganglion._processProcessSampleData(nextPacket); - funcSpyCompressedData.should.have.been.calledTwice; - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); - }); - it('should not find a dropped packet on wrap around', function () { - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdSampleMax - 1)); - funcSpyCompressedData.should.have.been.calledOnce; - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdSampleMax)); - funcSpyCompressedData.should.have.been.calledTwice; - ganglion._processProcessSampleData(utils.sampleUncompressedData()); - funcSpyCompressedData.should.have.been.calledTwice; - funcSpyUncompressedData.should.have.been.calledOnce; - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdUncompressed + 1)); - funcSpyCompressedData.should.have.been.calledThrice; - funcSpyDroppedPacket.should.not.have.been.called; - }); - it('should try to resend packet 127', function () { - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdSampleMax - 1)); - const expectedMissedSample = k.OBCIGanglionByteIdSampleMax; - // Call the function under test with one more then expected - const nextPacket = utils.sampleUncompressedData(); - ganglion._processProcessSampleData(nextPacket); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); - }); - it('should try to resend packet 126 and 127', function () { - ganglion._processProcessSampleData(utils.sampleCompressedData(k.OBCIGanglionByteIdSampleMax - 2)); - const expectedMissedSample1 = k.OBCIGanglionByteIdSampleMax - 1; - const expectedMissedSample2 = k.OBCIGanglionByteIdSampleMax; - // Call the function under test with one more then expected - const nextPacket = utils.sampleUncompressedData(); - ganglion._processProcessSampleData(nextPacket); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); - }); - it('should try to resend packet 1 and add packet 2 to buffer', function () { - // Send the last buffer, set's ganglion._packetCounter - ganglion._processProcessSampleData(utils.sampleUncompressedData()); - const expectedMissedSample = k.OBCIGanglionByteIdUncompressed + 1; - // Call the function under test with one more then expected - const nextPacket = utils.sampleCompressedData(expectedMissedSample + 1); - ganglion._processProcessSampleData(nextPacket); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); - }); - it('should try to resend packet 1 & 2 and add packet 3 to buffer', function () { - // Send the last buffer, set's ganglion._packetCounter - ganglion._processProcessSampleData(utils.sampleUncompressedData()); - const expectedMissedSample1 = k.OBCIGanglionByteIdUncompressed + 1; - const expectedMissedSample2 = k.OBCIGanglionByteIdUncompressed + 2; - // Call the function under test with two more then expected - const nextPacket = utils.sampleCompressedData(expectedMissedSample2 + 1); - ganglion._processProcessSampleData(nextPacket); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); - funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); + it('should recognize dropped packet 199', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.max - 1)); + const expectedMissedSample = k.OBCIGanglionByteId19Bit.max; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleUncompressedData(); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should recognize dropped packet 198 and 199', function () { + ganglion._processProcessSampleData(ganglionSample.sampleCompressedData(k.OBCIGanglionByteId19Bit.max - 2)); + const expectedMissedSample1 = k.OBCIGanglionByteId19Bit.max - 1; + const expectedMissedSample2 = k.OBCIGanglionByteId19Bit.max; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleUncompressedData(); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); + }); + it('should detect dropped packet 101 and process packet 102', function () { + // Send the raw buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + const expectedMissedSample = k.OBCIGanglionByteIdUncompressed + 1; + // Call the function under test with one more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample); + }); + it('should detect dropped packet 101 & 1022 and add process packet 103', function () { + // Send the last buffer, set's ganglion._packetCounter + ganglion._processProcessSampleData(ganglionSample.sampleUncompressedData()); + const expectedMissedSample1 = k.OBCIGanglionByteIdUncompressed + 1; + const expectedMissedSample2 = k.OBCIGanglionByteIdUncompressed + 2; + // Call the function under test with two more then expected + const nextPacket = ganglionSample.sampleCompressedData(expectedMissedSample2 + 1); + ganglion._processProcessSampleData(nextPacket); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample1); + funcSpyDroppedPacket.should.have.been.calledWith(expectedMissedSample2); + }); }); }); describe('_processBytes', function () { - let funcSpyAccel; let funcSpyImpedanceData; let funcSpyMultiBytePacket; let funcSpyMultiBytePacketStop; @@ -280,7 +317,6 @@ describe('#ganglion', function () { before(function () { // Put watchers on all functions - funcSpyAccel = sinon.spy(ganglion, '_processAccel'); funcSpyImpedanceData = sinon.spy(ganglion, '_processImpedanceData'); funcSpyMultiBytePacket = sinon.spy(ganglion, '_processMultiBytePacket'); funcSpyMultiBytePacketStop = sinon.spy(ganglion, '_processMultiBytePacketStop'); @@ -288,51 +324,46 @@ describe('#ganglion', function () { funcSpyProcessedData = sinon.spy(ganglion, '_processProcessSampleData'); }); beforeEach(function () { - funcSpyAccel.reset(); funcSpyImpedanceData.reset(); funcSpyMultiBytePacket.reset(); funcSpyMultiBytePacketStop.reset(); funcSpyOtherData.reset(); funcSpyProcessedData.reset(); }); - it('should route accel packet', function () { - ganglion._processBytes(utils.sampleAccel()); - funcSpyAccel.should.have.been.calledOnce; - }); it('should route impedance channel 1 packet', function () { - ganglion._processBytes(utils.sampleImpedanceChannel1()); + ganglion._processBytes(ganglionSample.sampleImpedanceChannel1()); funcSpyImpedanceData.should.have.been.calledOnce; }); it('should route impedance channel 2 packet', function () { - ganglion._processBytes(utils.sampleImpedanceChannel2()); + ganglion._processBytes(ganglionSample.sampleImpedanceChannel2()); funcSpyImpedanceData.should.have.been.calledOnce; }); it('should route impedance channel 3 packet', function () { - ganglion._processBytes(utils.sampleImpedanceChannel3()); + ganglion._processBytes(ganglionSample.sampleImpedanceChannel3()); funcSpyImpedanceData.should.have.been.calledOnce; }); it('should route impedance channel 4 packet', function () { - ganglion._processBytes(utils.sampleImpedanceChannel4()); + ganglion._processBytes(ganglionSample.sampleImpedanceChannel4()); funcSpyImpedanceData.should.have.been.calledOnce; }); it('should route impedance channel reference packet', function () { - ganglion._processBytes(utils.sampleImpedanceChannelReference()); + ganglion._processBytes(ganglionSample.sampleImpedanceChannelReference()); funcSpyImpedanceData.should.have.been.calledOnce; }); it('should route multi packet data', function () { - ganglion._processBytes(utils.sampleMultiBytePacket(new Buffer('taco'))); + ganglion._processBytes(ganglionSample.sampleMultiBytePacket(new Buffer('taco'))); funcSpyMultiBytePacket.should.have.been.calledOnce; }); it('should route multi packet stop data', function () { - ganglion._processBytes(utils.sampleMultiBytePacketStop(new Buffer('taco'))); + ganglion._processBytes(ganglionSample.sampleMultiBytePacketStop(new Buffer('taco'))); funcSpyMultiBytePacketStop.should.have.been.calledOnce; }); it('should route other data packet', function () { - ganglion._processBytes(utils.sampleOtherData(new Buffer('blah'))); + ganglion._processBytes(ganglionSample.sampleOtherData(new Buffer('blah'))); funcSpyOtherData.should.have.been.calledOnce; }); it('should route processed data packet', function () { - ganglion._processBytes(utils.sampleUncompressedData()); + ganglion._processBytes(ganglionSample.sampleUncompressedData()); funcSpyProcessedData.should.have.been.calledOnce; }); }); diff --git a/test/openBCIGanglionSample-test.js b/test/openBCIGanglionSample-test.js new file mode 100644 index 0000000..3c8adec --- /dev/null +++ b/test/openBCIGanglionSample-test.js @@ -0,0 +1,195 @@ +'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 ganglionSample = require('../openBCIGanglionSample'); +const k = require('../openBCIConstants'); + +var chaiAsPromised = require('chai-as-promised'); +var sinonChai = require('sinon-chai'); +chai.use(chaiAsPromised); +chai.use(sinonChai); + +describe('openBCIGanglionUtils', function () { + describe('#convert18bitAsInt32', function () { + it('converts a small positive number', function () { + const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + const num = ganglionSample.convert18bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a small positive number', function () { + const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + const num = ganglionSample.convert18bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a large positive number', function () { + const buf1 = new Buffer([0x02, 0xC0, 0x00]); // 0x02C001 === 180225 + const num = ganglionSample.convert18bitAsInt32(buf1); + assert.equal(num, 180224); + }); + it('converts a small negative number', function () { + const buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 + const num = ganglionSample.convert18bitAsInt32(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 = ganglionSample.convert18bitAsInt32(buf1); + num.should.be.approximately(-220927, 1); + }); + }); + describe('#convert19bitAsInt32', function () { + it('converts a small positive number', function () { + const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + const num = ganglionSample.convert19bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a small positive number', function () { + const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 + const num = ganglionSample.convert19bitAsInt32(buf1); + assert.equal(num, 1680); + }); + it('converts a large positive number', function () { + const buf1 = new Buffer([0x02, 0xC0, 0x00]); // 0x02C001 === 180225 + const num = ganglionSample.convert19bitAsInt32(buf1); + assert.equal(num, 180224); + }); + it('converts a small negative number', function () { + const buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 + const num = ganglionSample.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 = ganglionSample.convert19bitAsInt32(buf1); + num.should.be.approximately(-220927, 1); + }); + }); + describe('decompressDeltas18Bit', function () { + it('should extract the proper values for each channel', function () { + let buffer = new Buffer( + [ + 0b00000000, // 0 + 0b00000000, // 1 + 0b00000000, // 2 + 0b00000000, // 3 + 0b00100000, // 4 + 0b00000000, // 5 + 0b00101000, // 6 + 0b00000000, // 7 + 0b00000100, // 8 + 0b10000000, // 9 + 0b00000000, // 10 + 0b10111100, // 11 + 0b00000000, // 12 + 0b00000111, // 13 + 0b00000000, // 14 + 0b00101000, // 15 + 0b11000000, // 16 + 0b00001010 // 17 + ]); + let expectedValue = [[0, 2, 10, 4], [131074, 245760, 114698, 49162]]; + let actualValue = ganglionSample.decompressDeltas18Bit(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 + 0b01111111, // 2 + 0b11111111, // 3 + 0b10111111, // 4 + 0b11111111, // 5 + 0b11100111, // 6 + 0b11111111, // 7 + 0b11110101, // 8 + 0b00000000, // 9 + 0b00000001, // 10 + 0b01001111, // 11 + 0b10001110, // 12 + 0b00110000, // 13 + 0b00000000, // 14 + 0b00011111, // 15 + 0b11110000, // 16 + 0b00000001 // 17 + ]); + let expectedValue = [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]]; + let actualValue = ganglionSample.decompressDeltas18Bit(buffer); + + for (let i = 0; i < 4; i++) { + (actualValue[0][i]).should.equal(expectedValue[0][i]); + (actualValue[1][i]).should.equal(expectedValue[1][i]); + } + }); + }); + describe('decompressDeltas19Bit', function () { + 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 = ganglionSample.decompressDeltas19Bit(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 = ganglionSample.decompressDeltas19Bit(buffer); + + for (let i = 0; i < 4; i++) { + (actualValue[0][i]).should.equal(expectedValue[0][i]); + (actualValue[1][i]).should.equal(expectedValue[1][i]); + } + }); + }); +}); diff --git a/test/openBCIGanglionUtils-test.js b/test/openBCIGanglionUtils-test.js deleted file mode 100644 index 0e6cf4e..0000000 --- a/test/openBCIGanglionUtils-test.js +++ /dev/null @@ -1,139 +0,0 @@ -'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('#convert18bitAsInt32', function () { - it('converts a small positive number', function () { - const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 - const num = utils.convert18bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a small positive number', function () { - const buf1 = new Buffer([0x00, 0x06, 0x90]); // 0x000690 === 1680 - const num = utils.convert18bitAsInt32(buf1); - assert.equal(num, 1680); - }); - it('converts a large positive number', function () { - const buf1 = new Buffer([0x02, 0xC0, 0x00]); // 0x02C001 === 180225 - const num = utils.convert18bitAsInt32(buf1); - assert.equal(num, 180224); - }); - it('converts a small negative number', function () { - const buf1 = new Buffer([0xFF, 0xFF, 0xFF]); // 0xFFFFFF === -1 - const num = utils.convert18bitAsInt32(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.convert18bitAsInt32(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); - }); - }); -});