Skip to content

Commit 77d82bd

Browse files
author
AJ Keller
committed
Add support for ganglion, cyton and daisy sample parsing
1 parent af51f5e commit 77d82bd

File tree

2 files changed

+72
-41
lines changed

2 files changed

+72
-41
lines changed

openBCIConstants.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ const obciRadioCmdBaudRateSetFast = 0x06;
230230
const obciRadioCmdSystemStatus = 0x07;
231231

232232
/** Possible number of channels */
233+
const obciNumberOfChannelsCyton = 8;
233234
const obciNumberOfChannelsDaisy = 16;
234-
const obciNumberOfChannelsDefault = 8;
235+
const obciNumberOfChannelsDefault = obciNumberOfChannelsCyton;
235236
const obciNumberOfChannelsGanglion = 4;
236237

237238
/** Possible OpenBCI board types */
@@ -456,14 +457,14 @@ const obciGanglionPacket19Bit = {
456457
dataStart: 1,
457458
dataStop: 20
458459
};
459-
const obciGanglionMCP3912Gain = 1.0; // assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
460+
const obciGanglionMCP3912Gain = 51.0; // assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
460461
const obciGanglionMCP3912Vref = 1.2; // reference voltage for ADC in MCP3912 set in hardware
461462
const obciGanglionPrefix = 'Ganglion';
462463
const obciGanglionSyntheticDataEnable = 't';
463464
const obciGanglionSyntheticDataDisable = 'T';
464465
const obciGanglionImpedanceStart = 'z';
465466
const obciGanglionImpedanceStop = 'Z';
466-
const obciGanglionScaleFactorPerCountVolts = obciGanglionMCP3912Vref / (8388607.0 * obciGanglionMCP3912Gain * 1.5 * 51.0);
467+
const obciGanglionScaleFactorPerCountVolts = obciGanglionMCP3912Vref / (8388607.0 * obciGanglionMCP3912Gain * 1.5);
467468

468469
/** Simblee */
469470
const simbleeUuidService = 'fe84';
@@ -484,6 +485,11 @@ const obciNobleEmitterScanStop = 'scanStop';
484485
const obciNobleEmitterStateChange = 'stateChange';
485486
const obciNobleStatePoweredOn = 'poweredOn';
486487

488+
/** Protocols */
489+
const obciProtocolBLE = "ble";
490+
const obciProtocolSerial = "serial";
491+
const obciProtocolWifi = "wifi";
492+
487493
module.exports = {
488494
/** Turning channels off */
489495
OBCIChannelOff1: obciChannelOff1,
@@ -878,6 +884,7 @@ module.exports = {
878884
/** Triggers */
879885
OBCITrigger: obciTrigger,
880886
/** Possible number of channels */
887+
OBCINumberOfChannelsCyton: obciNumberOfChannelsCyton,
881888
OBCINumberOfChannelsDaisy: obciNumberOfChannelsDaisy,
882889
OBCINumberOfChannelsDefault: obciNumberOfChannelsDefault,
883890
OBCINumberOfChannelsGanglion: obciNumberOfChannelsGanglion,
@@ -1170,7 +1177,11 @@ module.exports = {
11701177
isPeripheralGanglion,
11711178
commandSampleRateForCmdCyton,
11721179
commandSampleRateForCmdGanglion,
1173-
commandBoardModeForMode
1180+
commandBoardModeForMode,
1181+
/** Protocols */
1182+
OBCIProtocolBLE: obciProtocolBLE,
1183+
OBCIProtocolSerial: obciProtocolSerial,
1184+
OBCIProtocolWifi: obciProtocolWifi
11741185
};
11751186

11761187
/**

openBCIUtilities.js

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ function newSyncObject () {
881881
* @description Used transform raw data packets into fully qualified packets
882882
* @param o {Object}
883883
* @param o.rawDataPackets {Array} - An array of rawDataPackets
884-
* @param o.gains {Array}
884+
* @param o.channelSettings {Array}
885885
* @param o.timeOffset {Number} (optional) for non time stamp use cases i.e. 0xC0 or 0xC1 (default and raw aux)
886886
* @param o.accelArray {Array} (optional) for non time stamp use cases
887887
* @param o.verbose {Boolean} (optional) for verbose output
@@ -900,22 +900,22 @@ function transformRawDataPacketsToSample (o) {
900900
case k.OBCIStreamPacketStandardAccel:
901901
sample = utilitiesModule.parsePacketStandardAccel({
902902
rawDataPacket,
903-
gains: o.gains,
903+
channelSettings: o.channelSettings,
904904
scale: o.scale
905905
});
906906
break;
907907
case k.OBCIStreamPacketStandardRawAux:
908908
sample = utilitiesModule.parsePacketStandardRawAux({
909909
rawDataPacket,
910-
gains: o.gains,
910+
channelSettings: o.channelSettings,
911911
scale: o.scale
912912
});
913913
break;
914914
case k.OBCIStreamPacketAccelTimeSyncSet:
915915
case k.OBCIStreamPacketAccelTimeSynced:
916916
sample = utilitiesModule.parsePacketTimeSyncedAccel({
917917
rawDataPacket,
918-
gains: o.gains,
918+
channelSettings: o.channelSettings,
919919
timeOffset: o.timeOffset,
920920
accelArray: o.accelArray
921921
});
@@ -924,7 +924,7 @@ function transformRawDataPacketsToSample (o) {
924924
case k.OBCIStreamPacketRawAuxTimeSynced:
925925
sample = utilitiesModule.parsePacketTimeSyncedRawAux({
926926
rawDataPacket,
927-
gains: o.gains,
927+
channelSettings: o.channelSettings,
928928
timeOffset: o.timeOffset
929929
});
930930
break;
@@ -948,7 +948,7 @@ function transformRawDataPacketsToSample (o) {
948948
* @description This method parses a 33 byte OpenBCI V3 packet and converts to a sample object
949949
* @param o {Object} - The input object
950950
* @param o.rawDataPacket {Buffer} - The 33byte raw packet
951-
* @param o.gains {Array} - An array of channel settings that is an Array that has shape similar to the one
951+
* @param o.channelSettings {Array} - An array of channel settings that is an Array that has shape similar to the one
952952
* calling k.channelSettingsArrayInit(). The most important rule here is that it is
953953
* Array of objects that have key-value pair {gain:NUMBER}
954954
* @param o.scale {Boolean} - Do you want to scale the results? Default true
@@ -966,7 +966,7 @@ function parsePacketStandardAccel (o) {
966966
sampleObject.accelData = getDataArrayAccel(o.rawDataPacket.slice(k.OBCIPacketPositionStartAux, k.OBCIPacketPositionStopAux + 1));
967967

968968
if (k.isUndefined(o.scale) || k.isNull(o.scale)) o.scale = true;
969-
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.gains);
969+
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.channelSettings);
970970
else sampleObject.channelDataCounts = getChannelDataArrayNoScale(o.rawDataPacket);
971971

972972
if (k.getVersionNumber(process.version) >= 6) {
@@ -989,7 +989,7 @@ function parsePacketStandardAccel (o) {
989989
* @description This method parses a 33 byte OpenBCI V3 packet and converts to a sample object
990990
* @param o {Object} - The input object
991991
* @param o.rawDataPacket {Buffer} - The 33byte raw packet
992-
* @param o.gains {Array} - An array of channel settings that is an Array that has shape similar to the one
992+
* @param o.channelSettings {Array} - An array of channel settings that is an Array that has shape similar to the one
993993
* calling k.channelSettingsArrayInit(). The most important rule here is that it is
994994
* Array of objects that have key-value pair {gain:NUMBER}
995995
* @param o.scale {Boolean} - Do you want to scale the results? Default is true
@@ -1007,7 +1007,7 @@ function parsePacketStandardRawAux (o) {
10071007

10081008
// Store the channel data
10091009
if (k.isUndefined(o.scale) || k.isNull(o.scale)) o.scale = true;
1010-
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.gains);
1010+
if (o.scale) sampleObject.channelData = getChannelDataArray(o);
10111011
else sampleObject.channelDataCounts = getChannelDataArrayNoScale(o.rawDataPacket);
10121012

10131013
// Slice the buffer for the aux data
@@ -1034,7 +1034,7 @@ function parsePacketStandardRawAux (o) {
10341034
* Z axis data is sent with every sampleNumber % 10 === 2
10351035
* @param o {Object} - The input object
10361036
* @param o.rawDataPacket {Buffer} - The 33byte raw time synced accel packet
1037-
* @param o.gains {Array} - An array of channel settings that is an Array that has shape similar to the one
1037+
* @param o.channelSettings {Array} - An array of channel settings that is an Array that has shape similar to the one
10381038
* calling OpenBCIConstans.channelSettingsArrayInit(). The most important rule here is that it is
10391039
* Array of objects that have key-value pair {gain:NUMBER}
10401040
* @param o.timeOffset {Number} - The difference between board time and current time calculated with sync methods.
@@ -1075,24 +1075,22 @@ function parsePacketTimeSyncedAccel (o) {
10751075
}
10761076

10771077
if (k.isUndefined(o.scale) || k.isNull(o.scale)) o.scale = true;
1078-
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.gains);
1078+
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.channelSettings);
10791079
else sampleObject.channelDataCounts = getChannelDataArrayNoScale(o.rawDataPacket);
10801080

10811081
return sampleObject;
10821082
}
10831083

10841084
/**
1085-
* @description Grabs an accel value from a raw but time synced packet. Important that this utilizes the fact that:
1086-
* X axis data is sent with every sampleNumber % 10 === 0
1087-
* Y axis data is sent with every sampleNumber % 10 === 1
1088-
* Z axis data is sent with every sampleNumber % 10 === 2
1085+
* @description Raw aux
10891086
* @param o {Object} - The input object
10901087
* @param o.rawDataPacket {Buffer} - The 33byte raw time synced accel packet
1091-
* @param o.gains {Array} - An array of channel settings that is an Array that has shape similar to the one
1088+
* @param o.channelSettings {Array} - An array of channel settings that is an Array that has shape similar to the one
10921089
* calling k.channelSettingsArrayInit(). The most important rule here is that it is
10931090
* Array of objects that have key-value pair {gain:NUMBER}
10941091
* @param o.timeOffset {Number} - The difference between board time and current time calculated with sync methods.
10951092
* @param o.scale {Boolean} - Do you want to scale the results? Default is true
1093+
* @param o.lastSampleNumber {Number} - The last sample number
10961094
* @returns {Sample} - A sample object. NOTE: The aux data is placed in a 2 byte buffer
10971095
*/
10981096
function parsePacketTimeSyncedRawAux (o) {
@@ -1124,7 +1122,7 @@ function parsePacketTimeSyncedRawAux (o) {
11241122

11251123
// Grab the channel data.
11261124
if (k.isUndefined(o.scale) || k.isNull(o.scale)) o.scale = true;
1127-
if (o.scale) sampleObject.channelData = getChannelDataArray(o.rawDataPacket, o.gains);
1125+
if (o.scale) sampleObject.channelData = getChannelDataArray(o);
11281126
else sampleObject.channelDataCounts = getChannelDataArrayNoScale(o.rawDataPacket);
11291127

11301128
return sampleObject;
@@ -1212,40 +1210,62 @@ function getDataArrayAccel (dataBuf) {
12121210
return accelData;
12131211
}
12141212
/**
1215-
* @description Takes a buffer filled with 24 bit signed integers from an OpenBCI device with gain settings in
1216-
* channelSettingsArray[index].gain and converts based on settings of ADS1299... spits out an
1217-
* array of floats in VOLTS
1218-
* @param dataBuf {Buffer} - Buffer with 33 bit signed integers, number of elements is same as channelSettingsArray.length * 3
1219-
* @param channelSettingsArray {Array} - The channel settings array, see OpenBCIConstants.channelSettingsArrayInit() for specs
1220-
* @returns {Array} - Array filled with floats for each channel's voltage in VOLTS
1221-
* @author AJ Keller (@pushtheworldllc)
1222-
*/
1223-
function getChannelDataArray (dataBuf, channelSettingsArray) {
1224-
if (!Array.isArray(channelSettingsArray)) {
1213+
* @description Takes a buffer filled with 24 bit signed integers from an OpenBCI device with gain settings in
1214+
* channelSettingsArray[index].gain and converts based on settings of ADS1299... spits out an
1215+
* array of floats in VOLTS
1216+
* @param o {Object} - The input object
1217+
* @param o.rawDataPacket {Buffer} - The 33byte raw time synced accel packet
1218+
* @param o.channelSettings {Array} - An array of channel settings that is an Array that has shape similar to the one
1219+
* calling k.channelSettingsArrayInit(). The most important rule here is that it is
1220+
* Array of objects that have key-value pair {gain:NUMBER}
1221+
* @param o.scale {Boolean} - Do you want to scale the results? Default is true
1222+
* @param o.lastSampleNumber {Number} - The last sample number
1223+
* @param o.protocol {String} - Either `Serial` or `Wifi` (Default is `Wifi`)
1224+
* @returns {Array} - Array filled with floats for each channel's voltage in VOLTS
1225+
* @author AJ Keller (@pushtheworldllc)
1226+
*/
1227+
function getChannelDataArray (o) {
1228+
if (!Array.isArray(o.channelSettings)) {
12251229
throw new Error('Error [getChannelDataArray]: Channel Settings must be an array!');
12261230
}
12271231
var channelData = [];
12281232
// Grab the sample number from the buffer
1229-
var sampleNumber = dataBuf[k.OBCIPacketPositionSampleNumber];
1230-
var daisy = channelSettingsArray.length > k.OBCINumberOfChannelsDefault;
1233+
var sampleNumber = o.rawDataPacket[k.OBCIPacketPositionSampleNumber];
1234+
var daisy = o.channelSettings.length === k.OBCINumberOfChannelsDaisy;
12311235

12321236
// Channel data arrays are always 8 long
12331237
for (var i = 0; i < k.OBCINumberOfChannelsDefault; i++) {
1234-
if (!channelSettingsArray[i].hasOwnProperty('gain')) {
1238+
if (!o.channelSettings[i].hasOwnProperty('gain')) {
12351239
throw new Error(`Error [getChannelDataArray]: Invalid channel settings object at index ${i}`);
12361240
}
1237-
if (!k.isNumber(channelSettingsArray[i].gain)) {
1241+
if (!k.isNumber(o.channelSettings[i].gain)) {
12381242
throw new Error('Error [getChannelDataArray]: Property gain of channelSettingsObject not or type Number');
12391243
}
12401244

1241-
var scaleFactor = 0;
1242-
if (isEven(sampleNumber) && daisy) {
1243-
scaleFactor = ADS1299_VREF / channelSettingsArray[i + k.OBCINumberOfChannelsDefault].gain / (Math.pow(2, 23) - 1);
1244-
} else {
1245-
scaleFactor = ADS1299_VREF / channelSettingsArray[i].gain / (Math.pow(2, 23) - 1);
1245+
let scaleFactor = 0;
1246+
1247+
if (o.protocol === k.OBCIProtocolSerial) {
1248+
if (isEven(sampleNumber) && daisy) {
1249+
scaleFactor = ADS1299_VREF / o.channelSettings[i + k.OBCINumberOfChannelsDefault].gain / (Math.pow(2, 23) - 1);
1250+
} else {
1251+
scaleFactor = ADS1299_VREF / o.channelSettings[i].gain / (Math.pow(2, 23) - 1);
1252+
}
1253+
} else if (o.protocol === k.OBCIProtocolWifi) {
1254+
if (daisy) {
1255+
if (o.lastSampleNumber === sampleNumber) {
1256+
scaleFactor = ADS1299_VREF / o.channelSettings[i + k.OBCINumberOfChannelsDefault].gain / (Math.pow(2, 23) - 1);
1257+
} else {
1258+
scaleFactor = ADS1299_VREF / o.channelSettings[i].gain / (Math.pow(2, 23) - 1);
1259+
}
1260+
} else if (o.channelSettings.length === k.OBCINumberOfChannelsCyton) {
1261+
scaleFactor = ADS1299_VREF / o.channelSettings[i].gain / (Math.pow(2, 23) - 1);
1262+
} else {
1263+
scaleFactor = k.OBCIGanglionScaleFactorPerCountVolts;
1264+
}
12461265
}
1266+
12471267
// Convert the three byte signed integer and convert it
1248-
channelData.push(scaleFactor * utilitiesModule.interpret24bitAsInt32(dataBuf.slice((i * 3) + k.OBCIPacketPositionChannelDataStart, (i * 3) + k.OBCIPacketPositionChannelDataStart + 3)));
1268+
channelData.push(scaleFactor * utilitiesModule.interpret24bitAsInt32(o.rawDataPacket.slice((i * 3) + k.OBCIPacketPositionChannelDataStart, (i * 3) + k.OBCIPacketPositionChannelDataStart + 3)));
12491269
}
12501270
return channelData;
12511271
}

0 commit comments

Comments
 (0)