Skip to content

Commit

Permalink
NeuroPawn bug fix + emulation + docs (#748)
Browse files Browse the repository at this point in the history
add neuropawn board
  • Loading branch information
Kevin-Xue01 authored Sep 26, 2024
1 parent 75d55cd commit 75db782
Show file tree
Hide file tree
Showing 19 changed files with 634 additions and 7 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/run_unix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ jobs:
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/installed/lib
- name: Cyton Daisy Python
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/cyton_linux.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/brainflow_get_data.py --board-id 2 --serial-port
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/cyton_linux.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/brainflow_get_data.py --board-id 2 --serial-port
- name: KnightBoard Python
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/knightboard_linux.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/brainflow_get_data.py --board-id 57 --serial-port
- name: Cyton Daisy Python Markers
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/cyton_linux.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/markers.py --board-id 2 --serial-port
- name: Galea Cpp
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/run_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ jobs:
- name: FreeEEG32 Python Test
run: python %GITHUB_WORKSPACE%\emulator\brainflow_emulator\freeeeg32_windows.py python %GITHUB_WORKSPACE%\python_package\examples\tests\brainflow_get_data.py --board-id 17 --serial-port
shell: cmd
- name: KnightBoard Windows Python Test
run: python %GITHUB_WORKSPACE%\emulator\brainflow_emulator\knightboard_windows.py python %GITHUB_WORKSPACE%\python_package\examples\tests\brainflow_get_data.py --board-id 57 --serial-port
shell: cmd
# Signal Processing Testing
- name: Serialization Rust Test
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,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
};


Expand Down
34 changes: 34 additions & 0 deletions docs/SupportedBoards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1305,3 +1305,37 @@ Supported platforms:
**Note**: Ensure that you have the necessary permissions to access the serial port on your operating system. For Unix-like systems, you may need to configure permissions for the serial port or run with sudo.
**To use this board you need to compile BrainFlow from the source code right on your Raspbery Pi device with flag --build-periphery(build.py) or with -DBUILD_PERIPHERY=ON(CMake) and install desired bindings using local libraries.**
NeuroPawn
--------
Knight Board
~~~~~~~~~~~~~
.. image:: https://drive.google.com/file/d/192dUfIXKmOqcTIBr7PYJJ8VUdWCuzeIv/view?usp=sharing
:width: 400px
:height: 225px
Visit us `here <https://www.neuropawn.tech/>`_
To create such board you need to specify the following board ID and fields of BrainFlowInputParams object:
- :code:`BoardIds.NEUROPAWN_KNIGHT_BOARD`
- :code:`serial_port`, e.g. COM3, /dev/tty.*

Initialization Example:

.. code-block:: python
params = BrainFlowInputParams()
params.serial_port = "COM3"
board = BoardShim(BoardIds.NEUROPAWN_KNIGHT_BOARD, params)
**On Unix-like systems you may need to configure permissions for serial port or run with sudo.**

Supported platforms:

- Windows
- Linux
- MacOS
- Devices like Raspberry Pi
52 changes: 52 additions & 0 deletions emulator/brainflow_emulator/knightboard_emulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import logging
import threading
import time
from random import randint

class Listener(threading.Thread):

def __init__(self, port, write, read):
# for windows write and read are methods from Serial object, for linux - os.read/write it doesnt work otherwise
threading.Thread.__init__(self)
self.port = port
self.writer_process = None
self.write = write
self.read = read
self.need_stop = False

def run(self):
self.writer_process = KnightBoardWriter(self.port, 0.005, self.write)
self.writer_process.daemon = True
self.writer_process.start()
time.sleep(10)
self.writer_process.need_data = False
self.writer_process.join()


class KnightBoardWriter(threading.Thread):

def __init__(self, port, delay, write):
threading.Thread.__init__(self)
self.port = port
self.write = write
self.delay = delay
self.package_size = 21
self.package_num = 0
self.need_data = True

def run(self):
while self.need_data:
if self.package_num % 256 == 0:
self.package_num = 0

package = list()
package.append(0xA0)
package.append(self.package_num)
for i in range(2, self.package_size - 1):
package.append(randint(0, 255))
package.append(0xC0)
logging.info(bytes(package))
self.write(self.port, bytes(package))

self.package_num = self.package_num + 1
time.sleep(self.delay)
53 changes: 53 additions & 0 deletions emulator/brainflow_emulator/knightboard_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import logging
import os
import pty
import subprocess
import sys

from brainflow_emulator.emulate_common import TestFailureError, log_multilines
from brainflow_emulator.knightboard_emulator import Listener


def write(port, data):
return os.write(port, data)


def read(port, num_bytes):
return os.read(port, num_bytes)


def get_ports_pty():
master, slave = pty.openpty()
s_name = os.ttyname(slave)
return master, slave, s_name


def test_serial(cmd_list, master, slave, s_name):
listen_thread = Listener(master, write, read)
listen_thread.daemon = True
listen_thread.start()

cmd_to_run = cmd_list + [s_name]
logging.info('Running %s' % ' '.join([str(x) for x in cmd_to_run]))
process = subprocess.Popen(cmd_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()

log_multilines(logging.info, stdout)
log_multilines(logging.info, stderr)

if process.returncode != 0:
raise TestFailureError('Test failed with exit code %s' % str(process.returncode), process.returncode)

return stdout, stderr


def main(cmd_list):
if not cmd_list:
raise Exception('No command to execute')
master, slave, s_name = get_ports_pty()
test_serial(cmd_list, master, slave, s_name)


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
main(sys.argv[1:])
100 changes: 100 additions & 0 deletions emulator/brainflow_emulator/knightboard_windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import logging
import os
import subprocess
import sys
import time

import pkg_resources
from brainflow_emulator.emulate_common import TestFailureError, log_multilines
from brainflow_emulator.knightboard_emulator import Listener
from serial import Serial


def write(port, data):
return port.write(data)


def read(port, num_bytes):
return port.read(num_bytes)


def get_isntaller():
return pkg_resources.resource_filename(__name__, os.path.join('com0com', 'setup_com0com_W7_x64_signed.exe'))


def install_com0com():
this_directory = os.path.abspath(os.path.dirname(__file__))
directory = os.path.join(this_directory, 'com0com')
if not os.path.exists(directory):
os.makedirs(directory)
cmds = [get_isntaller(), '/NCRC', '/S', '/D=%s' % directory]
logging.info('running %s' % ' '.join(cmds))
p = subprocess.Popen(cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if p.returncode != 0:
logging.error('stdout is %s' % out)
logging.error('stderr is %s' % err)
raise Exception('com0com installation failure')
logging.info('Sleeping a few second, it doesnt work in appveyour without it')
time.sleep(10)
return directory


def get_ports_windows():
directory = install_com0com()
# remove ports from previous run if any
p = subprocess.Popen([os.path.join(directory, 'setupc.exe'), 'remove', '0'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=directory)
stdout, stderr = p.communicate()
logging.info('remove stdout is %s' % stdout)
logging.info('remove stderr is %s' % stderr)

m_name = 'COM14'
s_name = 'COM15'

p = subprocess.Popen(
[os.path.join(directory, 'setupc.exe'), 'install', 'PortName=%s' % m_name, 'PortName=%s' % s_name],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=directory)
stdout, stderr = p.communicate()
logging.info('install stdout is %s' % stdout)
logging.info('install stderr is %s' % stderr)

if p.returncode != 0:
raise Exception('com0com failure')
logging.info('Sleeping a few second, it doesnt work in appveyour without it')
time.sleep(10)
return m_name, s_name


def test_serial(cmd_list, m_name, s_name):
master = Serial('\\\\.\\%s' % m_name, timeout=0)
listen_thread = Listener(master, write, read)
listen_thread.daemon = True
listen_thread.start()

cmd_to_run = cmd_list + [s_name]
logging.info('Running %s' % ' '.join([str(x) for x in cmd_to_run]))
process = subprocess.Popen(cmd_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()

log_multilines(logging.info, stdout)
log_multilines(logging.info, stderr)

master.close()
if process.returncode != 0:
raise TestFailureError('Test failed with exit code %s' % str(process.returncode), process.returncode)

return stdout, stderr


def main(cmd_list):
if not cmd_list:
raise Exception('No command to execute')

m_name, s_name = get_ports_windows()
test_serial(cmd_list, m_name, s_name)


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
main(sys.argv[1:])
3 changes: 2 additions & 1 deletion java_package/brainflow/src/main/java/brainflow/BoardIds.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Integer, BoardIds> bi_map = new HashMap<Integer, BoardIds> ();
Expand Down
1 change: 1 addition & 0 deletions julia_package/brainflow/src/board_shim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions matlab_package/brainflow/BoardIds.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion nodejs_package/brainflow/brainflow.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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 @@ -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):
Expand Down
1 change: 1 addition & 0 deletions rust_package/brainflow/src/ffi/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
8 changes: 6 additions & 2 deletions src/board_controller/board_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -55,8 +57,6 @@
#include "synthetic_board.h"
#include "unicorn_board.h"

#include "json.hpp"

using json = nlohmann::json;


Expand Down Expand Up @@ -281,6 +281,10 @@ int prepare_session (int board_id, const char *json_brainflow_input_params)
case BoardIds::PIEEG_BOARD:
board = std::shared_ptr<Board> (new PIEEGBoard (board_id, params));
break;
case BoardIds::NEUROPAWN_KNIGHT_BOARD:
board =
std::shared_ptr<Board> (new Knight ((int)BoardIds::NEUROPAWN_KNIGHT_BOARD, params));
break;
default:
return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR;
}
Expand Down
14 changes: 13 additions & 1 deletion src/board_controller/brainflow_boards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ BrainFlowBoards::BrainFlowBoards()
{"53", json::object()},
{"54", json::object()},
{"55", json::object()},
{"56", json::object()}
{"56", json::object()},
{"57", json::object()}
}
}};

Expand Down Expand Up @@ -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;
Loading

0 comments on commit 75db782

Please sign in to comment.