diff --git a/csharp_package/brainflow/brainflow/board_controller_library.cs b/csharp_package/brainflow/brainflow/board_controller_library.cs index ca545b3aa..dbcbae142 100644 --- a/csharp_package/brainflow/brainflow/board_controller_library.cs +++ b/csharp_package/brainflow/brainflow/board_controller_library.cs @@ -116,6 +116,7 @@ public enum BoardIds EXPLORE_PLUS_8_CHAN_BOARD = 54, EXPLORE_PLUS_32_CHAN_BOARD = 55, PIEEG_BOARD = 56 + NEUROPAWN_KNIGHT_BOARD = 57 }; diff --git a/java_package/brainflow/src/main/java/brainflow/BoardIds.java b/java_package/brainflow/src/main/java/brainflow/BoardIds.java index 6377c20af..a460d98bc 100644 --- a/java_package/brainflow/src/main/java/brainflow/BoardIds.java +++ b/java_package/brainflow/src/main/java/brainflow/BoardIds.java @@ -65,7 +65,8 @@ public enum BoardIds AAVAA_V3_BOARD(53), EXPLORE_PLUS_8_CHAN_BOARD(54), EXPLORE_PLUS_32_CHAN_BOARD(55), - PIEEG_BOARD(56); + PIEEG_BOARD(56), + NEUROPAWN_KNIGHT_BOARD(57); 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 c21b94c83..c455c2fbd 100644 --- a/julia_package/brainflow/src/board_shim.jl +++ b/julia_package/brainflow/src/board_shim.jl @@ -61,6 +61,7 @@ export BrainFlowInputParams EXPLORE_PLUS_8_CHAN_BOARD = 54 EXPLORE_PLUS_32_CHAN_BOARD = 55 PIEEG_BOARD = 56 + NEUROPAWN_KNIGHT_BOARD = 57 end diff --git a/matlab_package/brainflow/BoardIds.m b/matlab_package/brainflow/BoardIds.m index ceb601a86..4c4d69a90 100644 --- a/matlab_package/brainflow/BoardIds.m +++ b/matlab_package/brainflow/BoardIds.m @@ -59,5 +59,6 @@ EXPLORE_PLUS_8_CHAN_BOARD(54) EXPLORE_PLUS_32_CHAN_BOARD(55) PIEEG_BOARD(56) + NEUROPAWN_KNIGHT_BOARD(57) end end \ No newline at end of file diff --git a/nodejs_package/brainflow/brainflow.types.ts b/nodejs_package/brainflow/brainflow.types.ts index c95ec7d1c..b0730b561 100644 --- a/nodejs_package/brainflow/brainflow.types.ts +++ b/nodejs_package/brainflow/brainflow.types.ts @@ -68,7 +68,8 @@ export enum BoardIds { ANT_NEURO_EE_511_BOARD = 51, EXPLORE_PLUS_8_CHAN_BOARD = 54, EXPLORE_PLUS_32_CHAN_BOARD = 55, - PIEEG_BOARD = 56 + PIEEG_BOARD = 56, + NEUROPAWN_KNIGHT_BOARD = 57 } export enum IpProtocolTypes { diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py index ced6ec9d5..0b8f5707b 100644 --- a/python_package/brainflow/board_shim.py +++ b/python_package/brainflow/board_shim.py @@ -74,6 +74,7 @@ class BoardIds(enum.IntEnum): EXPLORE_PLUS_8_CHAN_BOARD = 54 #: EXPLORE_PLUS_32_CHAN_BOARD = 55 #: PIEEG_BOARD = 56 #: + NEUROPAWN_KNIGHT_BOARD = 57 #: class IpProtocolTypes(enum.IntEnum): diff --git a/rust_package/brainflow/src/ffi/constants.rs b/rust_package/brainflow/src/ffi/constants.rs index 6e8d43c8d..1d295216d 100644 --- a/rust_package/brainflow/src/ffi/constants.rs +++ b/rust_package/brainflow/src/ffi/constants.rs @@ -94,6 +94,7 @@ pub enum BoardIds { AavaaV3Board = 53, ExplorePlus8ChanBoard = 54, ExplorePlus32ChanBoard = 55, + NeuroPawnKnightBoard = 57 } #[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 623499463..cb55e2e88 100644 --- a/src/board_controller/board_controller.cpp +++ b/src/board_controller/board_controller.cpp @@ -45,6 +45,8 @@ #include "ganglion_wifi.h" #include "gforce_dual.h" #include "gforce_pro.h" +#include "json.hpp" +#include "knight.h" #include "muse.h" #include "muse_bled.h" #include "notion_osc.h" @@ -55,8 +57,6 @@ #include "synthetic_board.h" #include "unicorn_board.h" -#include "json.hpp" - using json = nlohmann::json; @@ -281,6 +281,10 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) case BoardIds::PIEEG_BOARD: board = std::shared_ptr (new PIEEGBoard (board_id, params)); break; + case BoardIds::NEUROPAWN_KNIGHT_BOARD: + board = + std::shared_ptr (new Knight ((int)BoardIds::NEUROPAWN_KNIGHT_BOARD, 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 f22ec341c..884e2efb5 100644 --- a/src/board_controller/brainflow_boards.cpp +++ b/src/board_controller/brainflow_boards.cpp @@ -74,7 +74,8 @@ BrainFlowBoards::BrainFlowBoards() {"53", json::object()}, {"54", json::object()}, {"55", json::object()}, - {"56", json::object()} + {"56", json::object()}, + {"57", json::object()} } }}; @@ -1095,6 +1096,17 @@ BrainFlowBoards::BrainFlowBoards() {"eeg_channels", {1, 2, 3, 4, 5, 6, 7, 8}}, {"eeg_names", "Fp1,Fp2,C3,C4,P7,P8,O1,O2"} }; + brainflow_boards_json["boards"]["57"]["default"] = + { + {"name", "Knight"}, + {"sampling_rate", 125}, + {"timestamp_channel", 11}, + {"marker_channel",12}, + {"package_num_channel", 0}, + {"num_rows", 13}, + {"eeg_channels", {1, 2, 3, 4, 5, 6, 7, 8}}, + {"other_channels", {9, 10}} + }; } BrainFlowBoards boards_struct; diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake index aeeedbaa7..9cb994390 100644 --- a/src/board_controller/build.cmake +++ b/src/board_controller/build.cmake @@ -85,6 +85,7 @@ SET (BOARD_CONTROLLER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ntl/ntl_wifi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/aavaa/aavaa_v3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/pieeg_board.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/knight.cpp ) include (${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ant_neuro/build.cmake) @@ -142,6 +143,7 @@ target_include_directories ( ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ntl/inc ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/aavaa/inc ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/pieeg/inc + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/neuropawn/inc ) target_compile_definitions(${BOARD_CONTROLLER_NAME} PRIVATE NOMINMAX BRAINFLOW_VERSION=${BRAINFLOW_VERSION}) diff --git a/src/board_controller/neuropawn/inc/knight.h b/src/board_controller/neuropawn/inc/knight.h new file mode 100644 index 000000000..7edd88356 --- /dev/null +++ b/src/board_controller/neuropawn/inc/knight.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "board.h" +#include "board_controller.h" +#include "serial.h" + +class Knight : public Board +{ + +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/knight.cpp b/src/board_controller/neuropawn/knight.cpp new file mode 100644 index 000000000..016f7e85f --- /dev/null +++ b/src/board_controller/neuropawn/knight.cpp @@ -0,0 +1,328 @@ +#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; + if (board_id == (int)BoardIds::NEUROPAWN_KNIGHT_BOARD) + { + min_package_size = 21; + } +} + +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 () +{ + 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 () +{ + /* + [0] 1 Byte: Start byte + [1] 2 Byte: Sample Number + [2-3] 3-4 Bytes: EXG channel 1 + [4-5] 5-6 Bytes: Data value for EXG channel 2 + [6-7] 7-8 Bytes: Data value for EXG channel 3 + [8-9] 9-10 Bytes: Data value for EXG channel 4 + [10-11] 11-12 Bytes: Data value for EXG channel 5 + [12-13] 13-14 Bytes: Data value for EXG channel 6 + [14-15] 15-16 Bytes: Data value for EXG channel 7 + [16-17] 17-18 Bytes: Data value for EXG channel 8 + [18] 19 Byte: Data LOFF STATP + [19] 20 Byte: Data LOFF STATN + [20] 21 Byte: End byte + */ + + int res; + unsigned char b[20] = {0}; + float eeg_scale = 4 / float ((pow (2, 23) - 1)) / 12 * 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; + } + bool first_package_received = false; + + 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 + res = serial->read_from_serial_port (b, 1); + if (res != 1) + { + safe_logger (spdlog::level::debug, "unable to read 1 byte, {}"); + continue; + } + if (b[0] != Knight::start_byte) + { + continue; + } + + int remaining_bytes = 20; + int pos = 0; + while ((remaining_bytes > 0) && (keep_alive)) + { + res = serial->read_from_serial_port (b + pos, remaining_bytes); + remaining_bytes -= res; + pos += res; + } + + if (!keep_alive) + { + break; + } + + if (b[19] != Knight::end_byte) + { + safe_logger (spdlog::level::warn, "Wrong end byte {}", b[19]); + continue; + } + + // package number CHANGE TO 1 if not working + package[board_descr["default"]["package_num_channel"].get ()] = (double)b[0]; + + // exg data retrieval + for (unsigned int i = 0; i < eeg_channels.size (); i++) + { + package[eeg_channels[i]] = + eeg_scale * cast_16bit_to_int32 (b + 1 + 2 * i); // CHANGE TO 2+2*i if not working + } + + // other channel data retrieval + package[other_channels[0]] = (double)b[17]; // LOFF STATP + package[other_channels[1]] = (double)b[18]; // LOFF STATN + + // time stamp channel + package[board_descr["default"]["timestamp_channel"].get ()] = get_timestamp (); + + 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); +#ifndef _WIN32 + return (int)BrainFlowExitCodes::SET_PORT_ERROR; +#endif + } + res = serial->set_custom_baudrate (115200); + if (res < 0) + { + safe_logger (spdlog::level::err, "Unable to set custom baud rate, res is {}", res); +#ifndef _WIN32 + // Setting the baudrate may return an error on Windows for some serial drivers. + // We do not throw an exception, because it will still work with USB. + // Optical connection will fail, though. + return (int)BrainFlowExitCodes::SET_PORT_ERROR; +#endif + } + 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/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index 26034af5b..4dfea5ddf 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -88,6 +88,7 @@ enum class BoardIds : int EXPLORE_PLUS_8_CHAN_BOARD = 54, EXPLORE_PLUS_32_CHAN_BOARD = 55, PIEEG_BOARD = 56, + NEUROPAWN_KNIGHT_BOARD = 57, // use it to iterate FIRST = PLAYBACK_FILE_BOARD, LAST = PIEEG_BOARD