Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,107 @@ It provides a uniform SDK to work with biosensors with a primary focus on neuroi
* Powerful CI/CD system which runs integrations tests for each commit automatically using BrainFlow's Emulator
* Simplified process to add new boards and methods

## NeuroPawn Knight Board Docs

### [Python] Brainflow Simple Setup
```
import brainflow as bf
import time

from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds

class KnightBoard:
def __init__(self, serial_port: str, num_channels: int):
"""Initialize and configure the Knight Board."""
self.params = BrainFlowInputParams()
self.params.serial_port = serial_port
self.num_channels = num_channels

# Initialize board
self.board_shim = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD.value, self.params)
self.board_id = self.board_shim.get_board_id()
self.eeg_channels = self.board_shim.get_exg_channels(self.board_id)
self.sampling_rate = self.board_shim.get_sampling_rate(self.board_id)

def start_stream(self, buffer_size: int = 450000):
"""Start the data stream from the board."""
self.board_shim.prepare_session()
self.board_shim.start_stream(buffer_size)
print("Stream started.")
time.sleep(2)
for x in range(1, self.num_channels + 1):
time.sleep(0.5)
cmd = f"chon_{x}_12"
self.board_shim.config_board(cmd)
print(f"sending {cmd}")
time.sleep(1)
rld = f"rldadd_{x}"
self.board_shim.config_board(rld)
print(f"sending {rld}")
time.sleep(0.5)

def stop_stream(self):
"""Stop the data stream and release resources."""
self.board_shim.stop_stream()
self.board_shim.release_session()
print("Stream stopped and session released.")

Knight_board = KnightBoard("COM3", 8)
Knight_board.start_stream()

while True:
data = Knight_board.board_shim.get_board_data()
# do stuff with data

if keyboard.is_pressed('q'):
Knight_board.stop_stream()
break
```
### BrainFlow Configuration Commands

To configure the NeuroPawn Knight board with BrainFlow, pass the commands as strings into:

board_shim.config_board(<command>)

#### Command 1: Enable EEG Channel / Set Gain
**Purpose**: Enables a specified channel with a specified gain, starting data acquisition on that channel. If the channel is already enabled, it will remain enabled, but will still update its gain.

f"chon_{channel}_{gain_value}"

##### Parameters:

- channel: The channel number to start the data acquisition. Replace this with the actual number of the channel you want to configure. One-indexed.

- gain: Specifies the gain value for the channel to be enabled. Allowable gain values are: [1, 2, 3, 4, 6, 8, 12 (recommended)]. The gain value controls the amplification level of the EEG signal on the specified channel.

#### Command 2: Disable EEG Channel
**Purpose**: Disables a specified channel, stopping data acquisition on that channel.

f"choff_{channel_number}"

##### Parameters:

- channel_number: The channel number to **stop** the data acquisition. This is appended to *'choff'* to construct the configuration command. One-indexed.

#### Command 3: Toggle on RLD
**Purpose**: Toogle **on** right leg drive for the specified channel.

f"rldadd_{channel_number}"

##### Parameters:

- channel_number: The channel number to toggle **on** the right leg drive. This number is converted to a string and appended to *'rldadd'* to create the configuration command. One-indexed.


### Command 4: Toggle off RLD
**Purpose**: Toogle **off** right leg drive for the specified channel.

f"rldremove_{channel}"

#### Parameters:

- channel_number: The channel number to toggle **off** the right leg drive. This number is converted to a string and appended to *'rldremove'* to create the configuration command. One-indexed.

## Resources

* [***BrainFlow Docs, Dev and User guides and other information***](https://brainflow.readthedocs.io)
Expand Down
7 changes: 5 additions & 2 deletions docs/SupportedBoards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1425,7 +1425,7 @@ Knight Board

To create such board you need to specify the following board ID and fields of BrainFlowInputParams object:

- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD`
- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD` or :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU`
- :code:`serial_port`, e.g. COM3, /dev/tty.*

Initialization Example:
Expand All @@ -1434,7 +1434,10 @@ Initialization Example:

params = BrainFlowInputParams()
params.serial_port = "COM3"
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params)
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) # standard Knight Board
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD_IMU, params) # Knight Board IMU

**On Unix-like systems you may need to configure permissions for serial port or run with sudo.**

Expand Down
1 change: 1 addition & 0 deletions python_package/brainflow/board_shim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 5 additions & 0 deletions src/board_controller/board_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "gforce_pro.h"
#include "json.hpp"
#include "knight.h"
#include "knightimu.h"
#include "muse.h"
#include "muse_bled.h"
#include "notion_osc.h"
Expand Down Expand Up @@ -299,6 +300,10 @@ int prepare_session (int board_id, const char *json_brainflow_input_params)
case BoardIds::BIOLISTENER_BOARD:
board = std::shared_ptr<Board> (new BioListener<8> (board_id, params));
break;
case BoardIds::NEUROPAWN_KNIGHT_BOARD_IMU:
board = std::shared_ptr<Board> (
new KnightIMU ((int)BoardIds::NEUROPAWN_KNIGHT_BOARD_IMU, params));
break;
default:
return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR;
}
Expand Down
12 changes: 12 additions & 0 deletions src/board_controller/brainflow_boards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ BrainFlowBoards::BrainFlowBoards()
{"63", json::object()},
{"64", json::object()},
{"65", json::object()},
{"66", json::object()}
}
}};

Expand Down Expand Up @@ -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;
1 change: 1 addition & 0 deletions src/board_controller/build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ 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/knightimu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/biolistener/biolistener.cpp
)

Expand Down
5 changes: 5 additions & 0 deletions src/board_controller/neuropawn/inc/knight.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <set>
#include <thread>

#include "board.h"
Expand All @@ -17,6 +18,7 @@ class Knight : public Board
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);
Expand All @@ -25,6 +27,9 @@ class Knight : public Board
int set_port_settings ();
void read_thread ();

private:
static const std::set<int> allowed_gains;

public:
Knight (int board_id, struct BrainFlowInputParams params);
~Knight ();
Expand Down
45 changes: 45 additions & 0 deletions src/board_controller/neuropawn/inc/knightimu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <set>
#include <thread>

#include "board.h"
#include "board_controller.h"
#include "serial.h"

class KnightIMU : 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 ();
int open_port ();
int set_port_settings ();
void read_thread ();

private:
static const std::set<int> allowed_gains;

public:
KnightIMU (int board_id, struct BrainFlowInputParams params);
~KnightIMU ();

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;
};
45 changes: 44 additions & 1 deletion src/board_controller/neuropawn/knight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,62 @@
#include <vector>

#include "custom_cast.h"
#include "json.hpp"
#include "knight.h"
#include "serial.h"
#include "timestamp.h"

using json = nlohmann::json;

constexpr int Knight::start_byte;
constexpr int Knight::end_byte;
const std::set<int> Knight::allowed_gains = {1, 2, 3, 4, 6, 8, 12};

Knight::Knight (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

// 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::warn,
"Invalid gain value {} in other_info, using default 12", parsed_gain);
}
}
else
{
safe_logger (spdlog::level::info, "No gain field in other_info, using default 12");
}
}
catch (json::parse_error &e)
{
safe_logger (spdlog::level::warn,
"Failed to parse JSON from other_info: {}, using default gain 12", e.what ());
}
catch (json::exception &e)
{
safe_logger (spdlog::level::warn,
"JSON exception while parsing other_info: {}, using default gain 12", e.what ());
}
}
}

Knight::~Knight ()
Expand Down Expand Up @@ -136,7 +179,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++)
Expand Down
Loading
Loading