Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NeuroPawn bug fix + emulation + docs #748

Merged
merged 9 commits into from
Sep 26, 2024
Merged
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
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
Loading