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