diff --git a/csharp_package/brainflow/brainflow/board_controller_library.cs b/csharp_package/brainflow/brainflow/board_controller_library.cs index b7ca38a5d..c363db037 100644 --- a/csharp_package/brainflow/brainflow/board_controller_library.cs +++ b/csharp_package/brainflow/brainflow/board_controller_library.cs @@ -121,7 +121,8 @@ public enum BoardIds SYNCHRONI_UNO_1_CHANNELS_BOARD = 62, OB3000_24_CHANNELS_BOARD = 63, BIOLISTENER_BOARD = 64, - IRONBCI_32_BOARD = 65 + IRONBCI_32_BOARD = 65, + NEUROPAWN_KNIGHT_BOARD_IMU = 66 }; diff --git a/docs/SupportedBoards.rst b/docs/SupportedBoards.rst index 465730371..5cafde9e3 100644 --- a/docs/SupportedBoards.rst +++ b/docs/SupportedBoards.rst @@ -1434,6 +1434,8 @@ Initialization Example: params = BrainFlowInputParams() params.serial_port = "COM3" + params.other_info = '{"gain": 6}' # optional: set gain to allowed values: 1, 2, 3, 4, 6, 8, 12 (default) + board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params) **On Unix-like systems you may need to configure permissions for serial port or run with sudo.** @@ -1447,6 +1449,41 @@ Supported platforms: - MacOS - Devices like Raspberry Pi +Knight IMU Board +~~~~~~~~~~~~~~~~~ + +.. image:: https://live.staticflickr.com/65535/54061606098_e223ab04a6_w.jpg + :width: 400px + :height: 274px + +`NeuroPawn website `_ + +To create such board you need to specify the following board ID and fields of BrainFlowInputParams object: + +- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU` +- :code:`serial_port`, e.g. COM3, /dev/tty.* + +Initialization Example: + +.. code-block:: python + + params = BrainFlowInputParams() + params.serial_port = "COM3" + params.other_info = '{"gain": 6}' # optional: set gain to allowed values: 1, 2, 3, 4, 6, 8, 12 (default) + + board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU, params) + +**On Unix-like systems you may need to configure permissions for serial port or run with sudo.** + +**On MacOS there are two serial ports for each device: /dev/tty..... and /dev/cu..... You HAVE to specify /dev/cu.....** + +Supported platforms: + +- Windows +- Linux +- MacOS +- Devices like Raspberry Pi + BioListener -------- diff --git a/java_package/brainflow/src/main/java/brainflow/BoardIds.java b/java_package/brainflow/src/main/java/brainflow/BoardIds.java index 5766f38e2..97f3b5684 100644 --- a/java_package/brainflow/src/main/java/brainflow/BoardIds.java +++ b/java_package/brainflow/src/main/java/brainflow/BoardIds.java @@ -71,7 +71,8 @@ public enum BoardIds SYNCHRONI_UNO_1_CHANNELS_BOARD(62), OB3000_24_CHANNELS_BOARD(63), BIOLISTENER_BOARD(64), - IRONBCI_32_BOARD(65); + IRONBCI_32_BOARD(65), + NEUROPAWN_KNIGHT_BOARD_IMU(66); private final int board_id; private static final Map bi_map = new HashMap (); diff --git a/julia_package/brainflow/src/board_shim.jl b/julia_package/brainflow/src/board_shim.jl index 4964cf4bf..cbc9c4081 100644 --- a/julia_package/brainflow/src/board_shim.jl +++ b/julia_package/brainflow/src/board_shim.jl @@ -67,6 +67,7 @@ export BrainFlowInputParams OB3000_24_CHANNELS_BOARD = 63 BIOLISTENER_BOARD = 64 IRONBCI_32_BOARD = 65 + NEUROPAWN_KNIGHT_BOARD_IMU = 66 end diff --git a/matlab_package/brainflow/BoardIds.m b/matlab_package/brainflow/BoardIds.m index c1a13ae0a..0234835f1 100644 --- a/matlab_package/brainflow/BoardIds.m +++ b/matlab_package/brainflow/BoardIds.m @@ -65,5 +65,6 @@ OB3000_24_CHANNELS_BOARD(63) BIOLISTENER_BOARD(64) IRONBCI_32_BOARD(65) + NEUROPAWN_KNIGHT_BOARD_IMU(66) end end \ No newline at end of file diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py index c3c61888e..faf57da7e 100644 --- a/python_package/brainflow/board_shim.py +++ b/python_package/brainflow/board_shim.py @@ -80,6 +80,7 @@ class BoardIds(enum.IntEnum): OB3000_24_CHANNELS_BOARD = 63 #: BIOLISTENER_BOARD = 64 #: IRONBCI_32_BOARD = 65 #: + NEUROPAWN_KNIGHT_BOARD_IMU = 66 #: class IpProtocolTypes(enum.IntEnum): diff --git a/rust_package/brainflow/src/ffi/constants.rs b/rust_package/brainflow/src/ffi/constants.rs index 3c0008221..0719e3331 100644 --- a/rust_package/brainflow/src/ffi/constants.rs +++ b/rust_package/brainflow/src/ffi/constants.rs @@ -32,7 +32,7 @@ impl BoardIds { pub const FIRST: BoardIds = BoardIds::PlaybackFileBoard; } impl BoardIds { - pub const LAST: BoardIds = BoardIds::Ironbci32Board; + pub const LAST: BoardIds = BoardIds::NeuropawnKnightBoardImu; } #[repr(i32)] #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -101,6 +101,7 @@ pub enum BoardIds { Ob300024ChannelsBoard = 63, BiolistenerBoard = 64, Ironbci32Board = 65, + NeuropawnKnightBoardImu = 66, } #[repr(i32)] #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)] diff --git a/src/board_controller/board_controller.cpp b/src/board_controller/board_controller.cpp index f39d1fa57..3e52792af 100644 --- a/src/board_controller/board_controller.cpp +++ b/src/board_controller/board_controller.cpp @@ -45,6 +45,7 @@ #include "gforce_pro.h" #include "json.hpp" #include "knight.h" +#include "knight_imu.h" #include "muse.h" #include "muse_bled.h" #include "notion_osc.h" @@ -299,6 +300,10 @@ 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::NEUROPAWN_KNIGHT_BOARD_IMU: + board = std::shared_ptr ( + new KnightIMU ((int)BoardIds::NEUROPAWN_KNIGHT_BOARD_IMU, params)); + break; default: return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR; } diff --git a/src/board_controller/brainflow_boards.cpp b/src/board_controller/brainflow_boards.cpp index 69e042b5b..5d86d6a59 100644 --- a/src/board_controller/brainflow_boards.cpp +++ b/src/board_controller/brainflow_boards.cpp @@ -84,6 +84,7 @@ BrainFlowBoards::BrainFlowBoards() {"63", json::object()}, {"64", json::object()}, {"65", json::object()}, + {"66", json::object()} } }}; @@ -1147,6 +1148,17 @@ BrainFlowBoards::BrainFlowBoards() {"emg_channels", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}}, {"ecg_channels", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}} }; + brainflow_boards_json["boards"]["66"]["default"] = + { + {"name", "KnightIMU"}, + {"sampling_rate", 125}, + {"timestamp_channel", 20}, + {"marker_channel", 21}, + {"package_num_channel", 0}, + {"num_rows", 22}, + {"eeg_channels", {1, 2, 3, 4, 5, 6, 7, 8}}, + {"other_channels", {9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}} + }; } BrainFlowBoards boards_struct; diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake index a6d4f15f5..886ebfb1a 100644 --- a/src/board_controller/build.cmake +++ b/src/board_controller/build.cmake @@ -84,6 +84,8 @@ SET (BOARD_CONTROLLER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/pieeg_board.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/synchroni/synchroni_board.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knight.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knight_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knight_imu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/biolistener/biolistener.cpp ) diff --git a/src/board_controller/neuropawn/inc/knight.h b/src/board_controller/neuropawn/inc/knight.h index 7edd88356..93d948f9b 100644 --- a/src/board_controller/neuropawn/inc/knight.h +++ b/src/board_controller/neuropawn/inc/knight.h @@ -1,40 +1,13 @@ #pragma once -#include +#include "knight_base.h" -#include "board.h" -#include "board_controller.h" -#include "serial.h" - -class Knight : public Board +class Knight : public KnightBase { protected: - volatile bool keep_alive; - bool initialized; - bool is_streaming; - std::thread streaming_thread; - Serial *serial; - - int min_package_size; - - virtual int send_to_board (const char *msg); - virtual int send_to_board (const char *msg, std::string &response); - virtual std::string read_serial_response (); - int open_port (); - int set_port_settings (); void read_thread (); public: Knight (int board_id, struct BrainFlowInputParams params); - ~Knight (); - - int prepare_session (); - int start_stream (int buffer_size, const char *streamer_params); - int stop_stream (); - int release_session (); - int config_board (std::string config, std::string &response); - - static constexpr int start_byte = 0xA0; - static constexpr int end_byte = 0xC0; }; \ No newline at end of file diff --git a/src/board_controller/neuropawn/inc/knight_base.h b/src/board_controller/neuropawn/inc/knight_base.h new file mode 100644 index 000000000..d085272a0 --- /dev/null +++ b/src/board_controller/neuropawn/inc/knight_base.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include "board.h" +#include "board_controller.h" +#include "serial.h" + +class KnightBase : public Board +{ + +protected: + volatile bool keep_alive; + bool initialized; + bool is_streaming; + std::thread streaming_thread; + Serial *serial; + + int min_package_size; + int gain; + + virtual int send_to_board (const char *msg); + virtual int send_to_board (const char *msg, std::string &response); + virtual std::string read_serial_response (); + virtual int open_port (); + virtual int set_port_settings (); + virtual void read_thread () = 0; + +private: + static const std::set allowed_gains; + +public: + KnightBase (int board_id, struct BrainFlowInputParams params); + virtual ~KnightBase (); + + virtual int prepare_session (); + virtual int start_stream (int buffer_size, const char *streamer_params); + virtual int stop_stream (); + virtual int release_session (); + virtual int config_board (std::string config, std::string &response); + + static constexpr int start_byte = 0xA0; + static constexpr int end_byte = 0xC0; +}; diff --git a/src/board_controller/neuropawn/inc/knight_imu.h b/src/board_controller/neuropawn/inc/knight_imu.h new file mode 100644 index 000000000..70b0c2c39 --- /dev/null +++ b/src/board_controller/neuropawn/inc/knight_imu.h @@ -0,0 +1,13 @@ +#pragma once + +#include "knight_base.h" + +class KnightIMU : public KnightBase +{ + +protected: + void read_thread (); + +public: + KnightIMU (int board_id, struct BrainFlowInputParams params); +}; \ No newline at end of file diff --git a/src/board_controller/neuropawn/knight.cpp b/src/board_controller/neuropawn/knight.cpp index 137db4113..77f4b084f 100644 --- a/src/board_controller/neuropawn/knight.cpp +++ b/src/board_controller/neuropawn/knight.cpp @@ -1,119 +1,12 @@ #include -#include #include #include "custom_cast.h" #include "knight.h" -#include "serial.h" #include "timestamp.h" -constexpr int Knight::start_byte; -constexpr int Knight::end_byte; - -Knight::Knight (int board_id, struct BrainFlowInputParams params) : Board (board_id, params) -{ - serial = NULL; - is_streaming = false; - keep_alive = false; - initialized = false; -} - -Knight::~Knight () -{ - skip_logs = true; - release_session (); -} - -int Knight::prepare_session () -{ - if (initialized) - { - safe_logger (spdlog::level::info, "Session already prepared"); - return (int)BrainFlowExitCodes::STATUS_OK; - } - if (params.serial_port.empty ()) - { - safe_logger (spdlog::level::err, "serial port is empty"); - return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; - } - serial = Serial::create (params.serial_port.c_str (), this); - int port_open = open_port (); - if (port_open != (int)BrainFlowExitCodes::STATUS_OK) - { - delete serial; - serial = NULL; - return port_open; - } - - int set_settings = set_port_settings (); - if (set_settings != (int)BrainFlowExitCodes::STATUS_OK) - { - delete serial; - serial = NULL; - return set_settings; - } - - initialized = true; - return (int)BrainFlowExitCodes::STATUS_OK; -} - -int Knight::start_stream (int buffer_size, const char *streamer_params) -{ - if (is_streaming) - { - safe_logger (spdlog::level::err, "Streaming thread already running"); - return (int)BrainFlowExitCodes::STREAM_ALREADY_RUN_ERROR; - } - int res = prepare_for_acquisition (buffer_size, streamer_params); - if (res != (int)BrainFlowExitCodes::STATUS_OK) - { - return res; - } - - serial->flush_buffer (); - - keep_alive = true; - streaming_thread = std::thread ([this] { this->read_thread (); }); - is_streaming = true; - return (int)BrainFlowExitCodes::STATUS_OK; -} - -int Knight::stop_stream () +Knight::Knight (int board_id, struct BrainFlowInputParams params) : KnightBase (board_id, params) { - if (is_streaming) - { - keep_alive = false; - is_streaming = false; - if (streaming_thread.joinable ()) - { - streaming_thread.join (); - } - return (int)BrainFlowExitCodes::STATUS_OK; - } - else - { - return (int)BrainFlowExitCodes::STREAM_THREAD_IS_NOT_RUNNING; - } -} - -int Knight::release_session () -{ - if (initialized) - { - if (is_streaming) - { - stop_stream (); - } - free_packages (); - initialized = false; - } - if (serial) - { - serial->close_serial_port (); - delete serial; - serial = NULL; - } - return (int)BrainFlowExitCodes::STATUS_OK; } void Knight::read_thread () @@ -136,7 +29,7 @@ void Knight::read_thread () int res; unsigned char b[20] = {0}; - float eeg_scale = 4 / float ((pow (2, 23) - 1)) / 12 * 1000000.; + float eeg_scale = 4 / float ((pow (2, 15) - 1)) / gain * 1000000.; int num_rows = board_descr["default"]["num_rows"]; double *package = new double[num_rows]; for (int i = 0; i < num_rows; i++) @@ -157,7 +50,7 @@ void Knight::read_thread () safe_logger (spdlog::level::debug, "unable to read 1 byte, {}"); continue; } - if (b[0] != Knight::start_byte) + if (b[0] != KnightBase::start_byte) { continue; } @@ -176,7 +69,7 @@ void Knight::read_thread () break; } - if (b[19] != Knight::end_byte) + if (b[19] != KnightBase::end_byte) { safe_logger (spdlog::level::warn, "Wrong end byte {}", b[19]); continue; @@ -202,116 +95,4 @@ void Knight::read_thread () push_package (package); } delete[] package; -} - -int Knight::open_port () -{ - if (serial->is_port_open ()) - { - safe_logger (spdlog::level::err, "port {} already open", serial->get_port_name ()); - return (int)BrainFlowExitCodes::PORT_ALREADY_OPEN_ERROR; - } - - safe_logger (spdlog::level::info, "openning port {}", serial->get_port_name ()); - int res = serial->open_serial_port (); - if (res < 0) - { - return (int)BrainFlowExitCodes::UNABLE_TO_OPEN_PORT_ERROR; - } - safe_logger (spdlog::level::trace, "port {} is open", serial->get_port_name ()); - return (int)BrainFlowExitCodes::STATUS_OK; -} - -int Knight::set_port_settings () -{ - int res = serial->set_serial_port_settings (1000, false); - if (res < 0) - { - safe_logger (spdlog::level::err, "Unable to set port settings, res is {}", res); - return (int)BrainFlowExitCodes::SET_PORT_ERROR; - } - res = serial->set_custom_baudrate (115200); - if (res < 0) - { - safe_logger (spdlog::level::err, "Unable to set custom baud rate, res is {}", res); - return (int)BrainFlowExitCodes::SET_PORT_ERROR; - } - safe_logger (spdlog::level::trace, "set port settings"); - return (int)BrainFlowExitCodes::STATUS_OK; -} - -int Knight::config_board (std::string config, std::string &response) -{ - if (!initialized) - { - return (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; - } - int res = (int)BrainFlowExitCodes::STATUS_OK; - if (is_streaming) - { - safe_logger (spdlog::level::warn, - "You are changing board params during streaming, it may lead to sync mismatch between " - "data acquisition thread and device"); - res = send_to_board (config.c_str ()); - } - else - { - // read response if streaming is not running - res = send_to_board (config.c_str (), response); - } - - return res; -} - -int Knight::send_to_board (const char *msg) -{ - int length = (int)strlen (msg); - safe_logger (spdlog::level::debug, "sending {} to the board", msg); - int res = serial->send_to_serial_port ((const void *)msg, length); - if (res != length) - { - return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; - } - - return (int)BrainFlowExitCodes::STATUS_OK; -} - -int Knight::send_to_board (const char *msg, std::string &response) -{ - int length = (int)strlen (msg); - safe_logger (spdlog::level::debug, "sending {} to the board", msg); - int res = serial->send_to_serial_port ((const void *)msg, length); - if (res != length) - { - response = ""; - return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; - } - response = read_serial_response (); - - return (int)BrainFlowExitCodes::STATUS_OK; -} - -std::string Knight::read_serial_response () -{ - constexpr int max_tmp_size = 4096; - unsigned char tmp_array[max_tmp_size]; - unsigned char tmp; - int tmp_id = 0; - while (serial->read_from_serial_port (&tmp, 1) == 1) - { - if (tmp_id < max_tmp_size) - { - tmp_array[tmp_id] = tmp; - tmp_id++; - } - else - { - serial->flush_buffer (); - break; - } - } - tmp_id = (tmp_id == max_tmp_size) ? tmp_id - 1 : tmp_id; - tmp_array[tmp_id] = '\0'; - - return std::string ((const char *)tmp_array); } \ No newline at end of file diff --git a/src/board_controller/neuropawn/knight_base.cpp b/src/board_controller/neuropawn/knight_base.cpp new file mode 100644 index 000000000..fe689d726 --- /dev/null +++ b/src/board_controller/neuropawn/knight_base.cpp @@ -0,0 +1,275 @@ +#include +#include +#include + +#include "custom_cast.h" +#include "json.hpp" +#include "knight_base.h" +#include "serial.h" +#include "timestamp.h" + +using json = nlohmann::json; + +constexpr int KnightBase::start_byte; +constexpr int KnightBase::end_byte; +const std::set KnightBase::allowed_gains = {1, 2, 3, 4, 6, 8, 12}; + +KnightBase::KnightBase (int board_id, struct BrainFlowInputParams params) : Board (board_id, params) +{ + serial = NULL; + is_streaming = false; + keep_alive = false; + initialized = false; + gain = 12; // default gain value +} + +KnightBase::~KnightBase () +{ + skip_logs = true; + release_session (); +} + +int KnightBase::prepare_session () +{ + if (initialized) + { + safe_logger (spdlog::level::info, "Session already prepared"); + return (int)BrainFlowExitCodes::STATUS_OK; + } + if (params.serial_port.empty ()) + { + safe_logger (spdlog::level::err, "serial port is empty"); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + + // Parse gain from other_info if provided + if (!params.other_info.empty ()) + { + try + { + json j = json::parse (params.other_info); + if (j.contains ("gain")) + { + int parsed_gain = j["gain"]; + // Validate gain is one of allowed values + if (allowed_gains.count (parsed_gain)) + { + gain = parsed_gain; + safe_logger (spdlog::level::info, "Knight board gain set to {}", gain); + } + else + { + safe_logger ( + spdlog::level::err, "Invalid gain value {} in other_info", parsed_gain); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + } + else + { + safe_logger (spdlog::level::info, "No gain field in other_info, using default 12"); + } + } + catch (json::parse_error &e) + { + safe_logger (spdlog::level::err, "Failed to parse JSON from other_info: {}", e.what ()); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + catch (json::exception &e) + { + safe_logger ( + spdlog::level::err, "JSON exception while parsing other_info: {}", e.what ()); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + } + + serial = Serial::create (params.serial_port.c_str (), this); + int port_open = open_port (); + if (port_open != (int)BrainFlowExitCodes::STATUS_OK) + { + delete serial; + serial = NULL; + return port_open; + } + + int set_settings = set_port_settings (); + if (set_settings != (int)BrainFlowExitCodes::STATUS_OK) + { + delete serial; + serial = NULL; + return set_settings; + } + + initialized = true; + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::start_stream (int buffer_size, const char *streamer_params) +{ + if (is_streaming) + { + safe_logger (spdlog::level::err, "Streaming thread already running"); + return (int)BrainFlowExitCodes::STREAM_ALREADY_RUN_ERROR; + } + int res = prepare_for_acquisition (buffer_size, streamer_params); + if (res != (int)BrainFlowExitCodes::STATUS_OK) + { + return res; + } + + serial->flush_buffer (); + + keep_alive = true; + streaming_thread = std::thread ([this] { this->read_thread (); }); + is_streaming = true; + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::stop_stream () +{ + if (is_streaming) + { + keep_alive = false; + is_streaming = false; + if (streaming_thread.joinable ()) + { + streaming_thread.join (); + } + return (int)BrainFlowExitCodes::STATUS_OK; + } + else + { + return (int)BrainFlowExitCodes::STREAM_THREAD_IS_NOT_RUNNING; + } +} + +int KnightBase::release_session () +{ + if (initialized) + { + if (is_streaming) + { + stop_stream (); + } + free_packages (); + initialized = false; + } + if (serial) + { + serial->close_serial_port (); + delete serial; + serial = NULL; + } + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::open_port () +{ + if (serial->is_port_open ()) + { + safe_logger (spdlog::level::err, "port {} already open", serial->get_port_name ()); + return (int)BrainFlowExitCodes::PORT_ALREADY_OPEN_ERROR; + } + + safe_logger (spdlog::level::info, "openning port {}", serial->get_port_name ()); + int res = serial->open_serial_port (); + if (res < 0) + { + return (int)BrainFlowExitCodes::UNABLE_TO_OPEN_PORT_ERROR; + } + safe_logger (spdlog::level::trace, "port {} is open", serial->get_port_name ()); + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::set_port_settings () +{ + int res = serial->set_serial_port_settings (1000, false); + if (res < 0) + { + safe_logger (spdlog::level::err, "Unable to set port settings, res is {}", res); + return (int)BrainFlowExitCodes::SET_PORT_ERROR; + } + res = serial->set_custom_baudrate (115200); + if (res < 0) + { + safe_logger (spdlog::level::err, "Unable to set custom baud rate, res is {}", res); + return (int)BrainFlowExitCodes::SET_PORT_ERROR; + } + safe_logger (spdlog::level::trace, "set port settings"); + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::config_board (std::string config, std::string &response) +{ + if (!initialized) + { + return (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; + } + int res = (int)BrainFlowExitCodes::STATUS_OK; + if (is_streaming) + { + safe_logger (spdlog::level::warn, + "You are changing board params during streaming, it may lead to sync mismatch between " + "data acquisition thread and device"); + res = send_to_board (config.c_str ()); + } + else + { + // read response if streaming is not running + res = send_to_board (config.c_str (), response); + } + + return res; +} + +int KnightBase::send_to_board (const char *msg) +{ + int length = (int)strlen (msg); + safe_logger (spdlog::level::debug, "sending {} to the board", msg); + int res = serial->send_to_serial_port ((const void *)msg, length); + if (res != length) + { + return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; + } + + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int KnightBase::send_to_board (const char *msg, std::string &response) +{ + int length = (int)strlen (msg); + safe_logger (spdlog::level::debug, "sending {} to the board", msg); + int res = serial->send_to_serial_port ((const void *)msg, length); + if (res != length) + { + response = ""; + return (int)BrainFlowExitCodes::BOARD_WRITE_ERROR; + } + response = read_serial_response (); + + return (int)BrainFlowExitCodes::STATUS_OK; +} + +std::string KnightBase::read_serial_response () +{ + constexpr int max_tmp_size = 4096; + unsigned char tmp_array[max_tmp_size]; + unsigned char tmp; + int tmp_id = 0; + while (serial->read_from_serial_port (&tmp, 1) == 1) + { + if (tmp_id < max_tmp_size) + { + tmp_array[tmp_id] = tmp; + tmp_id++; + } + else + { + serial->flush_buffer (); + break; + } + } + tmp_id = (tmp_id == max_tmp_size) ? tmp_id - 1 : tmp_id; + tmp_array[tmp_id] = '\0'; + + return std::string ((const char *)tmp_array); +} diff --git a/src/board_controller/neuropawn/knight_imu.cpp b/src/board_controller/neuropawn/knight_imu.cpp new file mode 100644 index 000000000..70673ee34 --- /dev/null +++ b/src/board_controller/neuropawn/knight_imu.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +#include "custom_cast.h" +#include "knight_imu.h" +#include "timestamp.h" + +KnightIMU::KnightIMU (int board_id, struct BrainFlowInputParams params) + : KnightBase (board_id, params) +{ +} + +void KnightIMU::read_thread () +{ + /* + Frame format (Arduino): + [0] 1 Byte : START (0xA0) + [1] 1 Byte : counter + [2..17]16 Bytes: 8x EXG int16 (little-endian) + [18] 1 Byte : LOFF STATP + [19] 1 Byte : LOFF STATN + [20..55]36 Bytes: 9x float32 IMU, little-endian: ax,ay,az,gx,gy,gz,mx,my,mz + [56] 1 Byte : END (0xC0) + */ + + int res; + constexpr int exg_channels_count = 8; + constexpr int imu_channels_count = 9; + constexpr int loff_bytes = 2; + constexpr int frame_payload_size = 1 /*counter*/ + (exg_channels_count * 2) + loff_bytes + + (imu_channels_count * 4) + 1 /*end*/; + + unsigned char b[frame_payload_size] = {0}; + + float eeg_scale = 4 / float ((pow (2, 15) - 1)) / gain * 1000000.; + int num_rows = board_descr["default"]["num_rows"]; + double *package = new double[num_rows]; + for (int i = 0; i < num_rows; i++) + { + package[i] = 0.0; + } + + std::vector eeg_channels = board_descr["default"]["eeg_channels"]; + std::vector other_channels = board_descr["default"]["other_channels"]; + + while (keep_alive) + { + // checking the start byte + unsigned char start = 0; + res = serial->read_from_serial_port (&start, 1); + if (res != 1) + { + safe_logger (spdlog::level::debug, "unable to read 1 byte, {}"); + continue; + } + if (start != KnightBase::start_byte) + { + continue; + } + + int remaining_bytes = frame_payload_size; + int pos = 0; + while ((remaining_bytes > 0) && (keep_alive)) + { + res = serial->read_from_serial_port (b + pos, remaining_bytes); + if (res > 0) + { + remaining_bytes -= res; + pos += res; + } + } + + if (!keep_alive) + { + break; + } + + if (b[frame_payload_size - 1] != KnightBase::end_byte) + { + safe_logger (spdlog::level::warn, "Wrong end byte {}", b[frame_payload_size - 1]); + continue; + } + + // package number / counter + package[board_descr["default"]["package_num_channel"].get ()] = (double)b[0]; + + // exg data retrieval + const int exg_offset = 1; + for (unsigned int i = 0; i < eeg_channels.size () && i < exg_channels_count; i++) + { + package[eeg_channels[i]] = eeg_scale * cast_16bit_to_int32 (b + exg_offset + 2 * i); + } + + // other channel data retrieval (keep old behavior) + const int loff_offset = exg_offset + (exg_channels_count * 2); + package[other_channels[0]] = (double)b[loff_offset]; // LOFF STATP + package[other_channels[1]] = (double)b[loff_offset + 1]; // LOFF STATN + + // IMU float32, little-endian + const int imu_offset = loff_offset + loff_bytes; + for (int i = 0; i < imu_channels_count && (2 + i) < (int)other_channels.size (); i++) + { + uint32_t u = (uint32_t)b[imu_offset + 4 * i] | + ((uint32_t)b[imu_offset + 4 * i + 1] << 8) | + ((uint32_t)b[imu_offset + 4 * i + 2] << 16) | + ((uint32_t)b[imu_offset + 4 * i + 3] << 24); + float f = 0.0f; + static_assert (sizeof (float) == 4, "float must be 4 bytes"); + std::memcpy (&f, &u, sizeof (f)); + package[other_channels[2 + i]] = (double)f; + } + + // time stamp channel + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + + push_package (package); + } + delete[] package; +} \ No newline at end of file diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index f120e42b3..f6883168c 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -94,6 +94,7 @@ enum class BoardIds : int OB3000_24_CHANNELS_BOARD = 63, BIOLISTENER_BOARD = 64, IRONBCI_32_BOARD = 65, + NEUROPAWN_KNIGHT_BOARD_IMU = 66, // use it to iterate FIRST = PLAYBACK_FILE_BOARD, LAST = IRONBCI_32_BOARD