Skip to content

Commit

Permalink
Add decompression for ganglion firmware v3
Browse files Browse the repository at this point in the history
  • Loading branch information
philippitts committed Jul 28, 2023
1 parent 3a9b248 commit bd43203
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 95 deletions.
239 changes: 144 additions & 95 deletions src/board_controller/openbci/ganglion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#include "custom_cast.h"
#include "ganglion.h"
#include "ganglion_types.h"
#include "get_dll_dir.h"


Expand Down Expand Up @@ -226,9 +225,7 @@ void Ganglion::read_thread ()
int max_attempts = params.timeout * 1000 / sleep_time;
float last_data[8] = {0};

double accel_x = 0.;
double accel_y = 0.;
double accel_z = 0.;
double acceleration[3] = {0.};

double resist_ref = 0.0;
double resist_first = 0.0;
Expand Down Expand Up @@ -267,72 +264,16 @@ void Ganglion::read_thread ()
safe_logger (spdlog::level::debug, "start streaming");
}

// delta holds 8 nums (4 by each package)
float delta[8] = {0.f};
int bits_per_num = 0;
unsigned char package_bits[160] = {0}; // 20 * 8
for (int i = 0; i < 20; i++)
if (data.data[0] <= 200)
{
uchar_to_bits (data.data[i], package_bits + i * 8);
}

// no compression, used to init variable
if (data.data[0] == 0)
{
// shift the last data packet to make room for a newer one
last_data[0] = last_data[4];
last_data[1] = last_data[5];
last_data[2] = last_data[6];
last_data[3] = last_data[7];

// add new packet
last_data[4] = (float)cast_24bit_to_int32 (data.data + 1);
last_data[5] = (float)cast_24bit_to_int32 (data.data + 4);
last_data[6] = (float)cast_24bit_to_int32 (data.data + 7);
last_data[7] = (float)cast_24bit_to_int32 (data.data + 10);

// scale new packet and insert into result
package[board_descr["default"]["package_num_channel"].get<int> ()] = 0.;
package[board_descr["default"]["eeg_channels"][0].get<int> ()] =
eeg_scale * last_data[4];
package[board_descr["default"]["eeg_channels"][1].get<int> ()] =
eeg_scale * last_data[5];
package[board_descr["default"]["eeg_channels"][2].get<int> ()] =
eeg_scale * last_data[6];
package[board_descr["default"]["eeg_channels"][3].get<int> ()] =
eeg_scale * last_data[7];
package[board_descr["default"]["accel_channels"][0].get<int> ()] = accel_x;
package[board_descr["default"]["accel_channels"][1].get<int> ()] = accel_y;
package[board_descr["default"]["accel_channels"][2].get<int> ()] = accel_z;
package[board_descr["default"]["timestamp_channel"].get<int> ()] = data.timestamp;
push_package (package);
continue;
}
// 18 bit compression, sends delta from previous value instead of real value!
else if ((data.data[0] >= 1) && (data.data[0] <= 100))
{
int last_digit = data.data[0] % 10;
switch (last_digit)
if (firmware == 3)
{
// accel data is signed, so we must cast it to signed char
// due to a known bug in ganglion firmware, we must swap x and z, and invert z.
case 0:
accel_z = -accel_scale * (char)data.data[19];
break;
case 1:
accel_y = accel_scale * (char)data.data[19];
break;
case 2:
accel_x = accel_scale * (char)data.data[19];
break;
default:
break;
decompress_3 (&data, last_data, acceleration);
}
else if (firmware == 2)
{
decompress_2 (&data, last_data, acceleration);
}
bits_per_num = 18;
}
else if ((data.data[0] >= 101) && (data.data[0] <= 200))
{
bits_per_num = 19;
}
else if ((data.data[0] > 200) && (data.data[0] < 206))
{
Expand Down Expand Up @@ -401,30 +342,6 @@ void Ganglion::read_thread ()
}
continue;
}
// handle compressed data for 18 or 19 bits
for (int i = 8, counter = 0; i < bits_per_num * 8; i += bits_per_num, counter++)
{
if (bits_per_num == 18)
{
delta[counter] = (float)cast_ganglion_bits_to_int32<18> (package_bits + i);
}
else
{
delta[counter] = (float)cast_ganglion_bits_to_int32<19> (package_bits + i);
}
}

// apply the first delta to the last data we got in the previous iteration
for (int i = 0; i < 4; i++)
{
last_data[i] = last_data[i + 4] - delta[i];
}

// apply the second delta to the previous packet which we just decompressed above
for (int i = 4; i < 8; i++)
{
last_data[i] = last_data[i - 4] - delta[i];
}

// add first encoded package
package[board_descr["default"]["package_num_channel"].get<int> ()] = data.data[0];
Expand All @@ -436,9 +353,9 @@ void Ganglion::read_thread ()
eeg_scale * last_data[2];
package[board_descr["default"]["eeg_channels"][3].get<int> ()] =
eeg_scale * last_data[3];
package[board_descr["default"]["accel_channels"][0].get<int> ()] = accel_x;
package[board_descr["default"]["accel_channels"][1].get<int> ()] = accel_y;
package[board_descr["default"]["accel_channels"][2].get<int> ()] = accel_z;
package[board_descr["default"]["accel_channels"][0].get<int> ()] = acceleration[0];
package[board_descr["default"]["accel_channels"][1].get<int> ()] = acceleration[1];
package[board_descr["default"]["accel_channels"][2].get<int> ()] = acceleration[2];
package[board_descr["default"]["timestamp_channel"].get<int> ()] = data.timestamp;
push_package (package);
// add second package
Expand Down Expand Up @@ -479,6 +396,138 @@ void Ganglion::read_thread ()
delete[] package;
}

void Ganglion::decompress_3 (

struct GanglionLib::GanglionData *data, float *last_data, double *acceleration)
{
int bits_per_num = 0;
unsigned char package_bits[160] = {0}; // 20 * 8
for (int i = 0; i < 20; i++)
{
uchar_to_bits (data->data[i], package_bits + i * 8);
}

// 18 bit compression, sends 17 MSBs + sign bit of 24-bit sample
if ((data->data[0] >= 0) && (data->data[0] <= 100))
{
int last_digit = data->data[0] % 10;
switch (last_digit)
{
// accel data is signed, so we must cast it to signed char
// swap x and z, and invert z to convert to standard coordinate space.
case 0:
acceleration[2] = -accel_scale * (char)data->data[19];
break;
case 1:
acceleration[1] = accel_scale * (char)data->data[19];
break;
case 2:
acceleration[0] = accel_scale * (char)data->data[19];
break;
default:
break;
}
bits_per_num = 18;
}
else if ((data->data[0] >= 101) && (data->data[0] <= 200))
{
bits_per_num = 19;
}

// handle compressed data for 18 or 19 bits
for (int i = 8, counter = 0; i < bits_per_num * 8; i += bits_per_num, counter++)
{
if (bits_per_num == 18)
{
last_data[counter] = (float)(cast_ganglion_bits_to_int32<18> (package_bits + i) << 6);
}
else
{
last_data[counter] = (float)(cast_ganglion_bits_to_int32<19> (package_bits + i) << 5);
}
}
}

void Ganglion::decompress_2 (
struct GanglionLib::GanglionData *data, float *last_data, double *acceleration)
{
int bits_per_num = 0;
unsigned char package_bits[160] = {0}; // 20 * 8
for (int i = 0; i < 20; i++)
{
uchar_to_bits (data->data[i], package_bits + i * 8);
}

float delta[8] = {0.f}; // delta holds 8 nums (4 by each package)

// no compression, used to init variable
if (data->data[0] == 0)
{
// shift the last data packet to make room for a newer one
last_data[0] = last_data[4];
last_data[1] = last_data[5];
last_data[2] = last_data[6];
last_data[3] = last_data[7];

// add new packet
last_data[4] = (float)cast_24bit_to_int32 (data->data + 1);
last_data[5] = (float)cast_24bit_to_int32 (data->data + 4);
last_data[6] = (float)cast_24bit_to_int32 (data->data + 7);
last_data[7] = (float)cast_24bit_to_int32 (data->data + 10);
}
// 18 bit compression, sends delta from previous value instead of real value!
else if ((data->data[0] >= 1) && (data->data[0] <= 100))
{
int last_digit = data->data[0] % 10;
switch (last_digit)
{
// accel data is signed, so we must cast it to signed char
// swap x and z, and invert z to convert to standard coordinate space.
case 0:
acceleration[2] = -accel_scale * (char)data->data[19];
break;
case 1:
acceleration[1] = accel_scale * (char)data->data[19];
break;
case 2:
acceleration[0] = accel_scale * (char)data->data[19];
break;
default:
break;
}
bits_per_num = 18;
}
else if ((data->data[0] >= 101) && (data->data[0] <= 200))
{
bits_per_num = 19;
}

// handle compressed data for 18 or 19 bits
for (int i = 8, counter = 0; i < bits_per_num * 8; i += bits_per_num, counter++)
{
if (bits_per_num == 18)
{
delta[counter] = (float)cast_ganglion_bits_to_int32<18> (package_bits + i);
}
else
{
delta[counter] = (float)cast_ganglion_bits_to_int32<19> (package_bits + i);
}
}

// apply the first delta to the last data we got in the previous iteration
for (int i = 0; i < 4; i++)
{
last_data[i] = last_data[i + 4] - delta[i];
}

// apply the second delta to the previous packet which we just decompressed above
for (int i = 4; i < 8; i++)
{
last_data[i] = last_data[i - 4] - delta[i];
}
}

int Ganglion::config_board (std::string config, std::string &response)
{
const char *conf = config.c_str ();
Expand Down Expand Up @@ -607,6 +656,7 @@ int Ganglion::call_open ()

safe_logger (
spdlog::level::info, "detected firmware version {}", connection_params.firmware);
firmware = connection_params.firmware;
}
else
{
Expand All @@ -620,7 +670,6 @@ int Ganglion::call_open ()
safe_logger (
spdlog::level::info, "mac address is not specified, try to find ganglion without it");

uint8_t firmware = 2;
res = func ((void *)&firmware);

safe_logger (spdlog::level::info, "detected firmware version {}", firmware);
Expand Down
7 changes: 7 additions & 0 deletions src/board_controller/openbci/inc/ganglion.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "board.h"
#include "board_controller.h"
#include "data_buffer.h"
#include "ganglion_types.h"
#include "runtime_dll_loader.h"


Expand All @@ -32,6 +33,7 @@ class Ganglion : public Board

bool use_mac_addr;
int num_channels;
uint8_t firmware;

// legacy from shared library, now we can do the same wo these helpers but lets keep it
int call_init ();
Expand Down Expand Up @@ -60,4 +62,9 @@ class Ganglion : public Board
int stop_stream ();
int release_session ();
int config_board (std::string config, std::string &response);

void decompress_3 (
struct GanglionLib::GanglionData *data, float *last_data, double *acceleration);
void decompress_2 (
struct GanglionLib::GanglionData *data, float *last_data, double *acceleration);
};

0 comments on commit bd43203

Please sign in to comment.