From 33dd32199e29c0a2f31035f594a5fa166fb7b2fe Mon Sep 17 00:00:00 2001 From: Philip Pitts <84428015+philippitts@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:09:34 -0800 Subject: [PATCH 1/4] Create explicit boards for Ganglion v2 and v3 firmwares --- src/board_controller/board_controller.cpp | 16 +- src/board_controller/openbci/ganglion.cpp | 212 +-------------- .../ganglion_bglib/inc/ganglion_types.h | 1 - .../openbci/ganglion_bglib/main.cpp | 7 +- .../openbci/ganglion_native.cpp | 241 +----------------- .../openbci/ganglion_native_v2.cpp | 132 ++++++++++ .../openbci/ganglion_native_v3.cpp | 88 +++++++ src/board_controller/openbci/ganglion_v2.cpp | 120 +++++++++ src/board_controller/openbci/ganglion_v3.cpp | 78 ++++++ src/board_controller/openbci/inc/ganglion.h | 18 +- .../openbci/inc/ganglion_native.h | 10 +- .../openbci/inc/ganglion_native_v2.h | 13 + .../openbci/inc/ganglion_native_v3.h | 12 + .../openbci/inc/ganglion_v2.h | 14 + .../openbci/inc/ganglion_v3.h | 14 + src/utils/inc/brainflow_constants.h | 4 +- 16 files changed, 508 insertions(+), 472 deletions(-) create mode 100644 src/board_controller/openbci/ganglion_native_v2.cpp create mode 100644 src/board_controller/openbci/ganglion_native_v3.cpp create mode 100644 src/board_controller/openbci/ganglion_v2.cpp create mode 100644 src/board_controller/openbci/ganglion_v3.cpp create mode 100644 src/board_controller/openbci/inc/ganglion_native_v2.h create mode 100644 src/board_controller/openbci/inc/ganglion_native_v3.h create mode 100644 src/board_controller/openbci/inc/ganglion_v2.h create mode 100644 src/board_controller/openbci/inc/ganglion_v3.h diff --git a/src/board_controller/board_controller.cpp b/src/board_controller/board_controller.cpp index f39d1fa57..fb4d049f5 100644 --- a/src/board_controller/board_controller.cpp +++ b/src/board_controller/board_controller.cpp @@ -38,8 +38,10 @@ #include "explore.h" #include "freeeeg.h" #include "galea.h" -#include "ganglion.h" -#include "ganglion_native.h" +#include "ganglion_native_v2.h" +#include "ganglion_native_v3.h" +#include "ganglion_v2.h" +#include "ganglion_v3.h" #include "ganglion_wifi.h" #include "gforce_dual.h" #include "gforce_pro.h" @@ -106,7 +108,7 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) board = std::shared_ptr (new Cyton (params)); break; case BoardIds::GANGLION_BOARD: - board = std::shared_ptr (new Ganglion (params)); + board = std::shared_ptr (new GanglionV3 (params)); break; case BoardIds::CYTON_DAISY_BOARD: board = std::shared_ptr (new CytonDaisy (params)); @@ -250,7 +252,7 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) board = std::shared_ptr (new Explore (board_id, params)); break; case BoardIds::GANGLION_NATIVE_BOARD: - board = std::shared_ptr (new GanglionNative (params)); + board = std::shared_ptr (new GanglionNativeV3 (params)); break; case BoardIds::EMOTIBIT_BOARD: board = std::shared_ptr (new Emotibit (params)); @@ -299,6 +301,12 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) case BoardIds::BIOLISTENER_BOARD: board = std::shared_ptr (new BioListener<8> (board_id, params)); break; + case BoardIds::GANGLION_V2_BOARD: + board = std::shared_ptr (new GanglionV2 (params)); + break; + case BoardIds::GANGLION_V2_NATIVE_BOARD: + board = std::shared_ptr (new GanglionNativeV2 (params)); + break; default: return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; } diff --git a/src/board_controller/openbci/ganglion.cpp b/src/board_controller/openbci/ganglion.cpp index a21a8cd57..a55766ace 100644 --- a/src/board_controller/openbci/ganglion.cpp +++ b/src/board_controller/openbci/ganglion.cpp @@ -17,8 +17,8 @@ int Ganglion::num_objects = 0; -Ganglion::Ganglion (struct BrainFlowInputParams params) - : Board ((int)BoardIds::GANGLION_BOARD, params) +Ganglion::Ganglion (BoardIds board_id, struct BrainFlowInputParams params) + : Board ((int)board_id, params) { Ganglion::num_objects++; if (Ganglion::num_objects > 1) @@ -30,7 +30,6 @@ Ganglion::Ganglion (struct BrainFlowInputParams params) is_valid = true; } use_mac_addr = (params.mac_address.empty ()) ? false : true; - firmware = 0; is_streaming = false; keep_alive = false; initialized = false; @@ -268,14 +267,7 @@ void Ganglion::read_thread () if (data.data[0] <= 200) { - if (firmware == 3) - { - decompress_firmware_3 (&data, last_data, acceleration, package); - } - else if (firmware == 2) - { - decompress_firmware_2 (&data, last_data, acceleration, package); - } + decompress (&data, last_data, acceleration, package); } else if ((data.data[0] > 200) && (data.data[0] < 206)) { @@ -371,195 +363,8 @@ void Ganglion::read_thread () delete[] package; } -void Ganglion::decompress_firmware_3 ( - struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, double *package) -{ - 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] >= 100 && 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); - } - } - - // add first encoded package - package[board_descr["default"]["package_num_channel"].get ()] = data->data[0]; - package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[0]; - package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[1]; - package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[2]; - package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[3]; - package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; - package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; - package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; - package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; - push_package (package); - // add second package - package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; - package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; - push_package (package); -} - -void Ganglion::decompress_firmware_2 ( - struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, double *package) -{ - 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); - - // scale new packet and insert into result - package[board_descr["default"]["package_num_channel"].get ()] = 0.; - package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; - package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; - package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; - package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; - package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; - push_package (package); - return; - } - // 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]; - } - - // add first encoded package - package[board_descr["default"]["package_num_channel"].get ()] = data->data[0]; - package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[0]; - package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[1]; - package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[2]; - package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[3]; - package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; - package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; - package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; - package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; - push_package (package); - // add second package - package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; - package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; - push_package (package); -} - int Ganglion::config_board (std::string config, std::string &response) { - if (config == "get_firmware_version") - { - response = std::to_string (firmware); - return (int)BrainFlowExitCodes::STATUS_OK; - } const char *conf = config.c_str (); safe_logger (spdlog::level::debug, "Trying to config Ganglion with {}", conf); // need to pause, config and restart. I have no idea why it doesnt work if I restart it inside @@ -674,17 +479,12 @@ int Ganglion::call_open () GanglionLib::ConnectionParameters connection_params; connection_params.mac_address = const_cast (params.mac_address.c_str ()); - connection_params.firmware = 2; res = func ((void *)&connection_params); - - safe_logger ( - spdlog::level::info, "detected firmware version {}", connection_params.firmware); - firmware = connection_params.firmware; } else { - int (*func) (void *) = (int (*) (void *))dll_loader->get_address ("open_ganglion"); + int (*func) () = (int (*) ())dll_loader->get_address ("open_ganglion"); if (func == NULL) { safe_logger (spdlog::level::err, "failed to get function address for open_ganglion"); @@ -694,9 +494,7 @@ int Ganglion::call_open () safe_logger ( spdlog::level::info, "mac address is not specified, try to find ganglion without it"); - res = func ((void *)&firmware); - - safe_logger (spdlog::level::info, "detected firmware version {}", firmware); + res = func (); } if (res != GanglionLib::CustomExitCodes::STATUS_OK) { diff --git a/src/board_controller/openbci/ganglion_bglib/inc/ganglion_types.h b/src/board_controller/openbci/ganglion_bglib/inc/ganglion_types.h index 73ae4feb0..83b199e61 100644 --- a/src/board_controller/openbci/ganglion_bglib/inc/ganglion_types.h +++ b/src/board_controller/openbci/ganglion_bglib/inc/ganglion_types.h @@ -91,6 +91,5 @@ namespace GanglionLib struct ConnectionParameters { char *mac_address; - uint8_t firmware; }; } \ No newline at end of file diff --git a/src/board_controller/openbci/ganglion_bglib/main.cpp b/src/board_controller/openbci/ganglion_bglib/main.cpp index f19067311..1a3d26c45 100644 --- a/src/board_controller/openbci/ganglion_bglib/main.cpp +++ b/src/board_controller/openbci/ganglion_bglib/main.cpp @@ -24,7 +24,6 @@ namespace GanglionLib std::deque data_queue; SpinLock lock; volatile bd_addr connect_addr; - volatile uint8 firmware = 0; volatile uint8 connection = -1; volatile uint16 ganglion_handle_start = 0; volatile uint16 ganglion_handle_end = 0; @@ -64,7 +63,7 @@ namespace GanglionLib return (int)CustomExitCodes::STATUS_OK; } - int open_ganglion (void *param) + int open_ganglion () { if (uart_open (uart_port)) { @@ -88,9 +87,6 @@ namespace GanglionLib int result = open_ble_dev (); - uint8_t *firmware_ptr = (uint8_t *)param; - *firmware_ptr = firmware; - return result; } @@ -140,7 +136,6 @@ namespace GanglionLib ble_cmd_gap_end_procedure (); int result = open_ble_dev (); - connection_parameters->firmware = firmware; return result; } diff --git a/src/board_controller/openbci/ganglion_native.cpp b/src/board_controller/openbci/ganglion_native.cpp index 71173bb12..35ac0854e 100644 --- a/src/board_controller/openbci/ganglion_native.cpp +++ b/src/board_controller/openbci/ganglion_native.cpp @@ -33,8 +33,8 @@ static void ganglion_read_notifications (simpleble_peripheral_t handle, simplebl ((GanglionNative *)(board))->read_data (service, characteristic, data, size); } -GanglionNative::GanglionNative (struct BrainFlowInputParams params) - : BLELibBoard ((int)BoardIds::GANGLION_NATIVE_BOARD, params) +GanglionNative::GanglionNative (BoardIds board_id, struct BrainFlowInputParams params) + : BLELibBoard ((int)board_id, params) { initialized = false; ganglion_adapter = NULL; @@ -42,7 +42,6 @@ GanglionNative::GanglionNative (struct BrainFlowInputParams params) is_streaming = false; start_command = "b"; stop_command = "s"; - firmware = 0; } GanglionNative::~GanglionNative () @@ -166,19 +165,6 @@ int GanglionNative::prepare_session () safe_logger (spdlog::level::trace, "found service {}", service.uuid.value); for (size_t j = 0; j < service.characteristic_count; j++) { - // Read the software revision characteristic to get the firmware version - if (strcmp (service.characteristics[j].uuid.value, GANGLION_SOFTWARE_REVISION) == 0) - { - uint8_t *data; - size_t data_length; - simpleble_peripheral_read (ganglion_peripheral, service.uuid, - service.characteristics[j].uuid, &data, &data_length); - - // Data should be in the form x.x.x stored as ASCII values in the data buffer - firmware = (data[0] == '3') ? 3 : 2; - safe_logger (spdlog::level::info, "Detected firmware version {}", firmware); - } - if (strcmp (service.characteristics[j].uuid.value, GANGLION_WRITE_CHAR) == 0) // Write Characteristics { @@ -315,18 +301,6 @@ int GanglionNative::release_session () return (int)BrainFlowExitCodes::STATUS_OK; } -int GanglionNative::config_board (std::string config, std::string &response) -{ - // todo do smth to unify available commands - // and make them more or less standard between devices - if (config == "get_firmware_version") - { - response = std::to_string (firmware); - return (int)BrainFlowExitCodes::STATUS_OK; - } - return config_board (config); -} - int GanglionNative::config_board (std::string config) { if (!initialized) @@ -493,14 +467,7 @@ void GanglionNative::read_data ( if (data[0] <= 200 && size == 20) { - if (firmware == 3) - { - decompress_firmware_3 (data, package); - } - else if (firmware == 2) - { - decompress_firmware_2 (data, package); - } + decompress (data, package); delete[] package; } else if ((data[0] > 200) && (data[0] < 206)) @@ -574,206 +541,4 @@ void GanglionNative::read_data ( delete[] package; return; } -} - -void GanglionNative::decompress_firmware_3 (const uint8_t *data, double *package) -{ - int bits_per_num = 0; - unsigned char package_bits[160] = {0}; // 20 * 8 - for (int i = 0; i < 20; i++) - { - uchar_to_bits (data[i], package_bits + i * 8); - } - - // 18 bit compression, sends 17 MSBs + sign bit of 24-bit sample - if (data[0] >= 0 && data[0] < 100) - { - int last_digit = 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: - temp_data.accel_z = -accel_scale * (char)data[19]; - break; - case 1: - temp_data.accel_y = accel_scale * (char)data[19]; - break; - case 2: - temp_data.accel_x = accel_scale * (char)data[19]; - break; - default: - break; - } - bits_per_num = 18; - } - else if (data[0] >= 100 && 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) - { - temp_data.last_data[counter] = - (float)(cast_ganglion_bits_to_int32<18> (package_bits + i) << 6); - } - else - { - temp_data.last_data[counter] = - (float)(cast_ganglion_bits_to_int32<19> (package_bits + i) << 5); - } - } - - // add first encoded package - package[board_descr["default"]["package_num_channel"].get ()] = data[0]; - package[board_descr["default"]["eeg_channels"][0].get ()] = - eeg_scale * temp_data.last_data[0]; - package[board_descr["default"]["eeg_channels"][1].get ()] = - eeg_scale * temp_data.last_data[1]; - package[board_descr["default"]["eeg_channels"][2].get ()] = - eeg_scale * temp_data.last_data[2]; - package[board_descr["default"]["eeg_channels"][3].get ()] = - eeg_scale * temp_data.last_data[3]; - package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; - package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; - package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; - package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); - push_package (package); - // add second package - package[board_descr["default"]["eeg_channels"][0].get ()] = - eeg_scale * temp_data.last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = - eeg_scale * temp_data.last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = - eeg_scale * temp_data.last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = - eeg_scale * temp_data.last_data[7]; - package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); - push_package (package); -} - -void GanglionNative::decompress_firmware_2 (const uint8_t *data, double *package) -{ - int bits_per_num = 0; - unsigned char package_bits[160] = {0}; // 20 * 8 - for (int i = 0; i < 20; i++) - { - uchar_to_bits (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[0] == 0) - { - // shift the last data packet to make room for a newer one - temp_data.last_data[0] = temp_data.last_data[4]; - temp_data.last_data[1] = temp_data.last_data[5]; - temp_data.last_data[2] = temp_data.last_data[6]; - temp_data.last_data[3] = temp_data.last_data[7]; - - // add new packet - temp_data.last_data[4] = (float)cast_24bit_to_int32 (data + 1); - temp_data.last_data[5] = (float)cast_24bit_to_int32 (data + 4); - temp_data.last_data[6] = (float)cast_24bit_to_int32 (data + 7); - temp_data.last_data[7] = (float)cast_24bit_to_int32 (data + 10); - - // scale new packet and insert into result - package[board_descr["default"]["package_num_channel"].get ()] = 0.; - package[board_descr["default"]["eeg_channels"][0].get ()] = - eeg_scale * temp_data.last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = - eeg_scale * temp_data.last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = - eeg_scale * temp_data.last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = - eeg_scale * temp_data.last_data[7]; - package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; - package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; - package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; - package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); - push_package (package); - return; - } - // 18 bit compression, sends delta from previous value instead of real value! - if (data[0] >= 1 && data[0] <= 100) - { - int last_digit = 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: - temp_data.accel_z = -accel_scale * (char)data[19]; - break; - case 1: - temp_data.accel_y = accel_scale * (char)data[19]; - break; - case 2: - temp_data.accel_x = accel_scale * (char)data[19]; - break; - default: - break; - } - bits_per_num = 18; - } - else if (data[0] >= 101 && 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++) - { - temp_data.last_data[i] = temp_data.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++) - { - temp_data.last_data[i] = temp_data.last_data[i - 4] - delta[i]; - } - - // add first encoded package - package[board_descr["default"]["package_num_channel"].get ()] = data[0]; - package[board_descr["default"]["eeg_channels"][0].get ()] = - eeg_scale * temp_data.last_data[0]; - package[board_descr["default"]["eeg_channels"][1].get ()] = - eeg_scale * temp_data.last_data[1]; - package[board_descr["default"]["eeg_channels"][2].get ()] = - eeg_scale * temp_data.last_data[2]; - package[board_descr["default"]["eeg_channels"][3].get ()] = - eeg_scale * temp_data.last_data[3]; - package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; - package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; - package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; - package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); - push_package (package); - // add second package - package[board_descr["default"]["eeg_channels"][0].get ()] = - eeg_scale * temp_data.last_data[4]; - package[board_descr["default"]["eeg_channels"][1].get ()] = - eeg_scale * temp_data.last_data[5]; - package[board_descr["default"]["eeg_channels"][2].get ()] = - eeg_scale * temp_data.last_data[6]; - package[board_descr["default"]["eeg_channels"][3].get ()] = - eeg_scale * temp_data.last_data[7]; - package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); - push_package (package); } \ No newline at end of file diff --git a/src/board_controller/openbci/ganglion_native_v2.cpp b/src/board_controller/openbci/ganglion_native_v2.cpp new file mode 100644 index 000000000..0ed9fdc91 --- /dev/null +++ b/src/board_controller/openbci/ganglion_native_v2.cpp @@ -0,0 +1,132 @@ +#include "ganglion_native_v2.h" + +#include "custom_cast.h" +#include "timestamp.h" + +GanglionNativeV2::GanglionNativeV2 (struct BrainFlowInputParams params) + : GanglionNative (BoardIds::GANGLION_V2_NATIVE_BOARD, params) +{ +} + +void GanglionNativeV2::decompress (const uint8_t *data, double *package) +{ + int bits_per_num = 0; + unsigned char package_bits[160] = {0}; // 20 * 8 + for (int i = 0; i < 20; i++) + { + uchar_to_bits (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[0] == 0) + { + // shift the last data packet to make room for a newer one + temp_data.last_data[0] = temp_data.last_data[4]; + temp_data.last_data[1] = temp_data.last_data[5]; + temp_data.last_data[2] = temp_data.last_data[6]; + temp_data.last_data[3] = temp_data.last_data[7]; + + // add new packet + temp_data.last_data[4] = (float)cast_24bit_to_int32 (data + 1); + temp_data.last_data[5] = (float)cast_24bit_to_int32 (data + 4); + temp_data.last_data[6] = (float)cast_24bit_to_int32 (data + 7); + temp_data.last_data[7] = (float)cast_24bit_to_int32 (data + 10); + + // scale new packet and insert into result + package[board_descr["default"]["package_num_channel"].get ()] = 0.; + package[board_descr["default"]["eeg_channels"][0].get ()] = + eeg_scale * temp_data.last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = + eeg_scale * temp_data.last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = + eeg_scale * temp_data.last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = + eeg_scale * temp_data.last_data[7]; + package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; + package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; + package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + push_package (package); + return; + } + // 18 bit compression, sends delta from previous value instead of real value! + if (data[0] >= 1 && data[0] <= 100) + { + int last_digit = 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: + temp_data.accel_z = -accel_scale * (char)data[19]; + break; + case 1: + temp_data.accel_y = accel_scale * (char)data[19]; + break; + case 2: + temp_data.accel_x = accel_scale * (char)data[19]; + break; + default: + break; + } + bits_per_num = 18; + } + else if (data[0] >= 101 && 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++) + { + temp_data.last_data[i] = temp_data.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++) + { + temp_data.last_data[i] = temp_data.last_data[i - 4] - delta[i]; + } + + // add first encoded package + package[board_descr["default"]["package_num_channel"].get ()] = data[0]; + package[board_descr["default"]["eeg_channels"][0].get ()] = + eeg_scale * temp_data.last_data[0]; + package[board_descr["default"]["eeg_channels"][1].get ()] = + eeg_scale * temp_data.last_data[1]; + package[board_descr["default"]["eeg_channels"][2].get ()] = + eeg_scale * temp_data.last_data[2]; + package[board_descr["default"]["eeg_channels"][3].get ()] = + eeg_scale * temp_data.last_data[3]; + package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; + package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; + package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + push_package (package); + // add second package + package[board_descr["default"]["eeg_channels"][0].get ()] = + eeg_scale * temp_data.last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = + eeg_scale * temp_data.last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = + eeg_scale * temp_data.last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = + eeg_scale * temp_data.last_data[7]; + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + push_package (package); +} \ No newline at end of file diff --git a/src/board_controller/openbci/ganglion_native_v3.cpp b/src/board_controller/openbci/ganglion_native_v3.cpp new file mode 100644 index 000000000..ea8e5f130 --- /dev/null +++ b/src/board_controller/openbci/ganglion_native_v3.cpp @@ -0,0 +1,88 @@ +#include "ganglion_native_v3.h" + +#include "custom_cast.h" +#include "timestamp.h" + +GanglionNativeV3::GanglionNativeV3 (struct BrainFlowInputParams params) + : GanglionNative (BoardIds::GANGLION_NATIVE_BOARD, params) +{ +} + +void GanglionNativeV3::decompress (const uint8_t *data, double *package) +{ + int bits_per_num = 0; + unsigned char package_bits[160] = {0}; // 20 * 8 + for (int i = 0; i < 20; i++) + { + uchar_to_bits (data[i], package_bits + i * 8); + } + + // 18 bit compression, sends 17 MSBs + sign bit of 24-bit sample + if (data[0] >= 0 && data[0] < 100) + { + int last_digit = 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: + temp_data.accel_z = -accel_scale * (char)data[19]; + break; + case 1: + temp_data.accel_y = accel_scale * (char)data[19]; + break; + case 2: + temp_data.accel_x = accel_scale * (char)data[19]; + break; + default: + break; + } + bits_per_num = 18; + } + else if (data[0] >= 100 && 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) + { + temp_data.last_data[counter] = + (float)(cast_ganglion_bits_to_int32<18> (package_bits + i) << 6); + } + else + { + temp_data.last_data[counter] = + (float)(cast_ganglion_bits_to_int32<19> (package_bits + i) << 5); + } + } + + // add first encoded package + package[board_descr["default"]["package_num_channel"].get ()] = data[0]; + package[board_descr["default"]["eeg_channels"][0].get ()] = + eeg_scale * temp_data.last_data[0]; + package[board_descr["default"]["eeg_channels"][1].get ()] = + eeg_scale * temp_data.last_data[1]; + package[board_descr["default"]["eeg_channels"][2].get ()] = + eeg_scale * temp_data.last_data[2]; + package[board_descr["default"]["eeg_channels"][3].get ()] = + eeg_scale * temp_data.last_data[3]; + package[board_descr["default"]["accel_channels"][0].get ()] = temp_data.accel_x; + package[board_descr["default"]["accel_channels"][1].get ()] = temp_data.accel_y; + package[board_descr["default"]["accel_channels"][2].get ()] = temp_data.accel_z; + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + push_package (package); + // add second package + package[board_descr["default"]["eeg_channels"][0].get ()] = + eeg_scale * temp_data.last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = + eeg_scale * temp_data.last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = + eeg_scale * temp_data.last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = + eeg_scale * temp_data.last_data[7]; + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + push_package (package); +} \ No newline at end of file diff --git a/src/board_controller/openbci/ganglion_v2.cpp b/src/board_controller/openbci/ganglion_v2.cpp new file mode 100644 index 000000000..f99a28bb7 --- /dev/null +++ b/src/board_controller/openbci/ganglion_v2.cpp @@ -0,0 +1,120 @@ +#include "ganglion_v2.h" + +#include "custom_cast.h" + +GanglionV2::GanglionV2 (struct BrainFlowInputParams params) + : Ganglion (BoardIds::GANGLION_V2_BOARD, params) +{ +} + +void GanglionV2::decompress ( + struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, double *package) +{ + 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); + + // scale new packet and insert into result + package[board_descr["default"]["package_num_channel"].get ()] = 0.; + package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; + package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; + package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; + package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; + package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; + push_package (package); + return; + } + // 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]; + } + + // add first encoded package + package[board_descr["default"]["package_num_channel"].get ()] = data->data[0]; + package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[0]; + package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[1]; + package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[2]; + package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[3]; + package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; + package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; + package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; + package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; + push_package (package); + // add second package + package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; + package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; + push_package (package); +} \ No newline at end of file diff --git a/src/board_controller/openbci/ganglion_v3.cpp b/src/board_controller/openbci/ganglion_v3.cpp new file mode 100644 index 000000000..39097c51f --- /dev/null +++ b/src/board_controller/openbci/ganglion_v3.cpp @@ -0,0 +1,78 @@ +#include "ganglion_v3.h" + +#include "custom_cast.h" + +GanglionV3::GanglionV3 (struct BrainFlowInputParams params) + : Ganglion (BoardIds::GANGLION_BOARD, params) +{ +} + +void GanglionV3::decompress ( + struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, double *package) +{ + 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] >= 100 && 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); + } + } + + // add first encoded package + package[board_descr["default"]["package_num_channel"].get ()] = data->data[0]; + package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[0]; + package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[1]; + package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[2]; + package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[3]; + package[board_descr["default"]["accel_channels"][0].get ()] = acceleration[0]; + package[board_descr["default"]["accel_channels"][1].get ()] = acceleration[1]; + package[board_descr["default"]["accel_channels"][2].get ()] = acceleration[2]; + package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; + push_package (package); + // add second package + package[board_descr["default"]["eeg_channels"][0].get ()] = eeg_scale * last_data[4]; + package[board_descr["default"]["eeg_channels"][1].get ()] = eeg_scale * last_data[5]; + package[board_descr["default"]["eeg_channels"][2].get ()] = eeg_scale * last_data[6]; + package[board_descr["default"]["eeg_channels"][3].get ()] = eeg_scale * last_data[7]; + package[board_descr["default"]["timestamp_channel"].get ()] = data->timestamp; + push_package (package); +} \ No newline at end of file diff --git a/src/board_controller/openbci/inc/ganglion.h b/src/board_controller/openbci/inc/ganglion.h index d0e7ef3e9..67c1a65cb 100644 --- a/src/board_controller/openbci/inc/ganglion.h +++ b/src/board_controller/openbci/inc/ganglion.h @@ -20,9 +20,6 @@ class Ganglion : public Board bool is_valid; - double const accel_scale = 0.016f; - double const eeg_scale = (1.2f * 1000000) / (8388607.0f * 1.5f * 51.0f); - std::string start_command; std::string stop_command; @@ -33,7 +30,6 @@ 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 (); @@ -53,9 +49,13 @@ class Ganglion : public Board DLLLoader *dll_loader; +protected: + double const eeg_scale = (1.2f * 1000000) / (8388607.0f * 1.5f * 51.0f); + double const accel_scale = 0.016f; + public: - Ganglion (struct BrainFlowInputParams params); - ~Ganglion (); + Ganglion (BoardIds board_id, struct BrainFlowInputParams params); + virtual ~Ganglion (); int prepare_session (); int start_stream (int buffer_size, const char *streamer_params); @@ -63,8 +63,6 @@ class Ganglion : public Board int release_session (); int config_board (std::string config, std::string &response); - void decompress_firmware_3 (struct GanglionLib::GanglionData *data, float *last_data, - double *acceleration, double *package); - void decompress_firmware_2 (struct GanglionLib::GanglionData *data, float *last_data, - double *acceleration, double *package); + virtual void decompress (struct GanglionLib::GanglionData *data, float *last_data, + double *acceleration, double *package) = 0; }; diff --git a/src/board_controller/openbci/inc/ganglion_native.h b/src/board_controller/openbci/inc/ganglion_native.h index 0f0f15ae8..98bee9199 100644 --- a/src/board_controller/openbci/inc/ganglion_native.h +++ b/src/board_controller/openbci/inc/ganglion_native.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -81,8 +83,8 @@ struct GanglionTempData class GanglionNative : public BLELibBoard { public: - GanglionNative (struct BrainFlowInputParams params); - ~GanglionNative (); + GanglionNative (BoardIds board_id, struct BrainFlowInputParams params); + virtual ~GanglionNative (); int prepare_session (); int start_stream (int buffer_size, const char *streamer_params); @@ -103,7 +105,6 @@ class GanglionNative : public BLELibBoard volatile simpleble_peripheral_t ganglion_peripheral; bool initialized; bool is_streaming; - uint8_t firmware; std::mutex m; std::condition_variable cv; std::pair notified_characteristics; @@ -115,6 +116,5 @@ class GanglionNative : public BLELibBoard double const accel_scale = 0.016f; double const eeg_scale = (1.2f * 1000000) / (8388607.0f * 1.5f * 51.0f); - void decompress_firmware_3 (const uint8_t *data, double *package); - void decompress_firmware_2 (const uint8_t *data, double *package); + virtual void decompress (const uint8_t *data, double *package) = 0; }; diff --git a/src/board_controller/openbci/inc/ganglion_native_v2.h b/src/board_controller/openbci/inc/ganglion_native_v2.h new file mode 100644 index 000000000..a592f7faa --- /dev/null +++ b/src/board_controller/openbci/inc/ganglion_native_v2.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ganglion_native.h" + +#include + +class GanglionNativeV2 : public GanglionNative +{ +public: + GanglionNativeV2 (struct BrainFlowInputParams params); + + void decompress (const uint8_t *data, double *package) override; +}; \ No newline at end of file diff --git a/src/board_controller/openbci/inc/ganglion_native_v3.h b/src/board_controller/openbci/inc/ganglion_native_v3.h new file mode 100644 index 000000000..02ca82354 --- /dev/null +++ b/src/board_controller/openbci/inc/ganglion_native_v3.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ganglion_native.h" +#include + +class GanglionNativeV3 : public GanglionNative +{ +public: + GanglionNativeV3 (struct BrainFlowInputParams params); + + void decompress (const uint8_t *data, double *package) override; +}; \ No newline at end of file diff --git a/src/board_controller/openbci/inc/ganglion_v2.h b/src/board_controller/openbci/inc/ganglion_v2.h new file mode 100644 index 000000000..cdc894e9d --- /dev/null +++ b/src/board_controller/openbci/inc/ganglion_v2.h @@ -0,0 +1,14 @@ +#pragma once + +#include "ganglion.h" + +#include + +class GanglionV2 : public Ganglion +{ +public: + GanglionV2 (struct BrainFlowInputParams params); + + void decompress (struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, + double *package) override; +}; \ No newline at end of file diff --git a/src/board_controller/openbci/inc/ganglion_v3.h b/src/board_controller/openbci/inc/ganglion_v3.h new file mode 100644 index 000000000..163d2d848 --- /dev/null +++ b/src/board_controller/openbci/inc/ganglion_v3.h @@ -0,0 +1,14 @@ +#pragma once + +#include "ganglion.h" + +#include + +class GanglionV3 : public Ganglion +{ +public: + GanglionV3 (struct BrainFlowInputParams params); + + void decompress (struct GanglionLib::GanglionData *data, float *last_data, double *acceleration, + double *package) override; +}; \ No newline at end of file diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index f120e42b3..08b57a3a7 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -94,9 +94,11 @@ enum class BoardIds : int OB3000_24_CHANNELS_BOARD = 63, BIOLISTENER_BOARD = 64, IRONBCI_32_BOARD = 65, + GANGLION_V2_BOARD = 66, + GANGLION_V2_NATIVE_BOARD = 67, // use it to iterate FIRST = PLAYBACK_FILE_BOARD, - LAST = IRONBCI_32_BOARD + LAST = GANGLION_V2_NATIVE_BOARD }; enum class IpProtocolTypes : int From 69928729aee40c8deead62045ed4704a94b1e9dc Mon Sep 17 00:00:00 2001 From: Philip Pitts <84428015+philippitts@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:07:50 -0800 Subject: [PATCH 2/4] Remove instructions for setting firmware version from Ganglion BLE GAP scan callback --- src/board_controller/openbci/ganglion_bglib/callbacks.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/board_controller/openbci/ganglion_bglib/callbacks.cpp b/src/board_controller/openbci/ganglion_bglib/callbacks.cpp index 33594bceb..0da7533cc 100644 --- a/src/board_controller/openbci/ganglion_bglib/callbacks.cpp +++ b/src/board_controller/openbci/ganglion_bglib/callbacks.cpp @@ -75,13 +75,11 @@ void ble_evt_gap_scan_response (const struct ble_msg_gap_scan_response_evt_t *ms if (memcmp (msg->sender.addr, (uint8 *)GanglionLib::connect_addr.addr, sizeof (bd_addr)) == 0) { - GanglionLib::firmware = msg->data.data[0] == 20 && msg->data.data[13] == '3' ? 3 : 2; GanglionLib::exit_code = (int)GanglionLib::STATUS_OK; } else if (strstr (name, "anglion") != NULL) // strcasestr is unavailable for windows { memcpy ((void *)GanglionLib::connect_addr.addr, msg->sender.addr, sizeof (bd_addr)); - GanglionLib::firmware = msg->data.data[0] == 20 && msg->data.data[13] == '3' ? 3 : 2; GanglionLib::exit_code = (int)GanglionLib::STATUS_OK; } } From 669073330eeb89769500e779e08f93f066b3d5bc Mon Sep 17 00:00:00 2001 From: Philip Pitts <84428015+philippitts@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:26:59 -0800 Subject: [PATCH 3/4] Update CMakeLists with new Ganglion board sources --- src/board_controller/build.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake index a6d4f15f5..9ad35ba24 100644 --- a/src/board_controller/build.cmake +++ b/src/board_controller/build.cmake @@ -45,6 +45,10 @@ SET (BOARD_CONTROLLER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/cyton_daisy_wifi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion_native.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion_v2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion_native_v2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion_v3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/ganglion_native_v3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/cyton.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/openbci/cyton_daisy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/board_controller.cpp From 40123da2a23feed43a88549f7cef908974373a43 Mon Sep 17 00:00:00 2001 From: Philip Pitts <84428015+philippitts@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:40:23 -0800 Subject: [PATCH 4/4] Fix config_board interface in GanglionNative --- src/board_controller/openbci/ganglion_native.cpp | 2 +- src/board_controller/openbci/inc/ganglion_native.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/board_controller/openbci/ganglion_native.cpp b/src/board_controller/openbci/ganglion_native.cpp index 35ac0854e..31439974b 100644 --- a/src/board_controller/openbci/ganglion_native.cpp +++ b/src/board_controller/openbci/ganglion_native.cpp @@ -301,7 +301,7 @@ int GanglionNative::release_session () return (int)BrainFlowExitCodes::STATUS_OK; } -int GanglionNative::config_board (std::string config) +int GanglionNative::config_board (std::string config, std::string &response) { if (!initialized) { diff --git a/src/board_controller/openbci/inc/ganglion_native.h b/src/board_controller/openbci/inc/ganglion_native.h index 98bee9199..4cc245505 100644 --- a/src/board_controller/openbci/inc/ganglion_native.h +++ b/src/board_controller/openbci/inc/ganglion_native.h @@ -91,7 +91,6 @@ class GanglionNative : public BLELibBoard int stop_stream (); int release_session (); int config_board (std::string config, std::string &response); - int config_board (std::string config); int send_command (std::string config); void adapter_1_on_scan_start (simpleble_adapter_t adapter);