Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d70e9b5
initial version of BioListener communication over tcp
serhii-matsyshyn Dec 7, 2024
f02871c
enhanced BioListener communication over tcp - custom packet, automati…
serhii-matsyshyn Dec 15, 2024
5d99063
BioListener - corrected conversion to micro volts
serhii-matsyshyn Dec 16, 2024
7a1b0ce
BioListener - refactored code
serhii-matsyshyn Dec 16, 2024
1f56f0d
BioListener - corrected timestamp, AUXILIARY_PRESET with IMU and batt…
serhii-matsyshyn Dec 18, 2024
03375b4
BioListener - ip_port can be modified, package num mismatch (packet l…
serhii-matsyshyn Dec 28, 2024
b7d2dcb
BioListener - simplified channel number change in packet, now templat…
serhii-matsyshyn Jan 3, 2025
400784b
BioListener - emulator
serhii-matsyshyn Jan 3, 2025
4c91bc9
Merge branch 'master' into biolistener
serhii-matsyshyn Jan 3, 2025
1a79c29
BioListener - run workflows
serhii-matsyshyn Jan 3, 2025
a9bae1e
BioListener - compiler independent
serhii-matsyshyn Jan 3, 2025
1db4ef3
BioListener - compiler independent
serhii-matsyshyn Jan 3, 2025
4be29ed
BioListener - run workflows
serhii-matsyshyn Jan 3, 2025
243ac3b
BioListener - emulator
serhii-matsyshyn Jan 3, 2025
721356f
BioListener - docs
serhii-matsyshyn Jan 18, 2025
bf2afcb
BioListener - all channel types
serhii-matsyshyn Jan 18, 2025
5775ab5
Merge branch 'master' into biolistener
serhii-matsyshyn Jan 18, 2025
e641563
Merge branch 'master' into biolistener
serhii-matsyshyn Mar 2, 2025
72b328c
BioListener - merge fixes
serhii-matsyshyn Mar 2, 2025
4553cc8
BioListener - merge fixes
serhii-matsyshyn Mar 2, 2025
1006bad
BioListener - merge fixes
serhii-matsyshyn Mar 2, 2025
56fd180
BioListener - emulator more logs added
serhii-matsyshyn Mar 2, 2025
b341a25
BioListener - emulator more logs added
serhii-matsyshyn Mar 2, 2025
532a3f6
BioListener - emulator
serhii-matsyshyn Mar 2, 2025
9f0c729
BioListener - emulator
serhii-matsyshyn Mar 2, 2025
17f7e3f
BioListener - emulator
serhii-matsyshyn Mar 3, 2025
f3ef60a
BioListener - emulator
serhii-matsyshyn Mar 3, 2025
5450bd8
BioListener - PR corrections - defines moved to the separate file, ad…
serhii-matsyshyn Mar 7, 2025
3c6ea6a
BioListener - PR corrections - defines moved to the separate file, ad…
serhii-matsyshyn Mar 7, 2025
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
2 changes: 2 additions & 0 deletions .github/workflows/run_unix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ jobs:
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/streaming_board_emulator.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/brainflow_get_data.py --board-id -2 --ip-address 225.1.1.1 --ip-port 6677 --master-board -1
- name: Streaming Python Markers
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/streaming_board_emulator.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/markers.py --board-id -2 --ip-address 225.1.1.1 --ip-port 6677 --master-board -1
- name: BioListener Python
run: sudo -H python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/biolistener_emulator.py python3 $GITHUB_WORKSPACE/python_package/examples/tests/brainflow_get_data.py --board-id 64 --ip-address 127.0.0.1 --ip-port 12345
- name: Denoising Python
run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/denoising.py
- name: Serialization Python
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 @@ -186,6 +186,9 @@ jobs:
- 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
- name: BioListener Windows Python Test
run: python %GITHUB_WORKSPACE%\emulator\brainflow_emulator\biolistener_emulator.py python %GITHUB_WORKSPACE%\python_package\examples\tests\brainflow_get_data.py --board-id 64 --ip-address 127.0.0.1 --ip-port 12345
shell: cmd
# Signal Processing Testing
- name: Serialization Rust Test
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public enum BoardIds
OB5000_8_CHANNELS_BOARD = 60,
SYNCHRONI_PENTO_8_CHANNELS_BOARD = 61,
SYNCHRONI_UNO_1_CHANNELS_BOARD = 62,

OB3000_24_CHANNELS_BOARD = 63,
BIOLISTENER_BOARD = 64,
};


Expand Down
41 changes: 41 additions & 0 deletions docs/SupportedBoards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1347,3 +1347,44 @@ Supported platforms:
- Linux
- MacOS
- Devices like Raspberry Pi


BioListener
--------

BioListener
~~~~~~~~~~~~~

.. image:: https://live.staticflickr.com/65535/54273076343_6a7eb99697_k.jpg
:width: 519px
:height: 389px

`BioListener website <https://github.com/serhii-matsyshyn/biolistener/>`_

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

- :code:`BoardIds.BIOLISTENER_BOARD`
- :code:`ip_port`, any local port which is currently free (and is selected on BioListener board), e.g. 12345
- :code:`ip_address`, ip address of the current device, BrainFlow acts as a server. Use 0.0.0.0 to make the server listen on all network interfaces.

Initialization Example:

.. code-block:: python

params = BrainFlowInputParams()
params.ip_port = 12345
params.ip_address = "0.0.0.0"
board = BoardShim(BoardIds.BIOLISTENER_BOARD, params)

Supported platforms:

- Windows
- Linux
- MacOS
- Devices like Raspberry Pi
- Android

Available :ref:`presets-label`:

- :code:`BrainFlowPresets.DEFAULT_PRESET`, it contains EEG (EMG, ECG, EOG) data
- :code:`BrainFlowPresets.AUXILIARY_PRESET`, it contains Gyro, Accel, battery and ESP32 chip temperature data
196 changes: 196 additions & 0 deletions emulator/brainflow_emulator/biolistener_emulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import datetime
import enum
import json
import logging
import random
import socket
import struct
import subprocess
import sys
import threading
import time

from brainflow_emulator.emulate_common import TestFailureError, log_multilines

BIOLISTENER_DATA_CHANNELS_COUNT = 8

BIOLISTENER_DATA_PACKET_DEBUG = 0
BIOLISTENER_DATA_PACKET_BIOSIGNALS = 1
BIOLISTENER_DATA_PACKET_IMU = 2

ADC_USED = 0 # ADS131M08


class DataPacket:
FORMAT_STRING = f'=1B 1I 1B 1I 1B {BIOLISTENER_DATA_CHANNELS_COUNT}I 1B'

def __init__(self, ts, tp, n, s_id, data):
self.header = 0xA0
self.ts = ts
self.type = tp
self.n = n
self.s_id = s_id
self.data = data
self.footer = 0xC0

def pack(self):
return struct.pack(self.FORMAT_STRING, self.header, self.ts, self.type, self.n, self.s_id, *self.data, self.footer)

@classmethod
def unpack(cls, packed_data):
format_string = cls.FORMAT_STRING
unpacked_data = struct.unpack(format_string, packed_data)
return cls(*unpacked_data)

def __repr__(self):
return (f'DataPacket(header={self.header}, ts={self.ts}, type={self.type}, '
f'n={self.n}, s_id={self.s_id}, data={self.data}, footer={self.footer})')


class State(enum.Enum):
wait = 'wait'
stream = 'stream'


def test_socket(cmd_list):
logging.info('Running %s' % ' '.join([str(x) for x in cmd_list]))
process = subprocess.Popen(cmd_list, 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 run_socket_server():
thread = BioListenerEmulator()
thread.start()
return thread


class BioListenerEmulator(threading.Thread):

def __init__(self):
threading.Thread.__init__(self)
self.local_ip = '127.0.0.1'
self.local_port = 12345
self.server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.settimeout(1)
self.state = State.wait.value
self.package_num = 0
self.keep_alive = True
self.connection_established = False
logging.info(f"BioListener emulator started")

@staticmethod
def volts_to_data(ref, voltage, pga_gain, adc_resolution):
resolution = ref / (adc_resolution * pga_gain)

if voltage >= 0: # Positive range
raw_code = voltage / resolution
else: # Negative range
raw_code = (voltage + (ref / pga_gain)) / resolution

raw_code = int(raw_code)
raw_code = max(0, min(0xFFFFFF, raw_code)) # Ensure within 24 bit range

return raw_code

def run(self):
logging.info(f"BioListener emulator connecting to {self.local_ip}:{self.local_port}...")
while self.keep_alive and not self.connection_established:
try:
self.server_socket.connect((self.local_ip, self.local_port))
self.connection_established = True
break
except Exception as err:
logging.warning(f"Error connecting to {self.local_ip}:{self.local_port}: {err}")
# A failed connect may leave the socket unusable.
try:
self.server_socket.close()
except Exception:
pass
# Recreate the socket with the same options.
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.settimeout(1)
time.sleep(0.1)

if self.connection_established:
logging.info(f"BioListener emulator connected to {self.local_ip}:{self.local_port}")
else:
logging.error(f"BioListener emulator failed to connect to {self.local_ip}:{self.local_port}")
return

started_at = time.time()
while self.keep_alive:
new_data_packet = DataPacket(
ts=int((time.time() - started_at) * 1000),
tp=BIOLISTENER_DATA_PACKET_BIOSIGNALS,
n=self.package_num,
s_id=ADC_USED,
data=[
self.volts_to_data(
ref=2500000.0,
voltage=random.uniform(-1000, 1000),
pga_gain=8,
adc_resolution=16777216.0
) for _ in range(BIOLISTENER_DATA_CHANNELS_COUNT)
]
)
self.package_num += 1

try:
data = self.server_socket.recv(1024)
message = data.decode('utf-8').strip()

if message:
for message_part in message.split("\n"):
logging.info(f"BioListener received command: {message_part}")
json_str = json.loads(message_part)
if json_str["command"] in (1, 2, 3, 4):
logging.info("Command ignored - simulator supports only start and stop stream command")
elif json_str["command"] == 5:
logging.info("Start stream command received")
self.state = State.stream.value
elif json_str["command"] == 6:
logging.info("Stop stream command received")
self.state = State.wait.value
else:
logging.warning(f"Unknown command: {json_str['command']}")
except TimeoutError:
pass
except socket.timeout:
pass
except Exception as err:
logging.error(f"Error in recv thread: {err}")

try:
if self.state == State.stream.value:
self.server_socket.sendall(new_data_packet.pack())
except ConnectionResetError:
logging.error(f"Connection lost")
except Exception as e:
logging.error(f"Error: {e}")


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

try:
test_socket(cmd_list)
finally:
server_thread.keep_alive = False
server_thread.join()


if __name__ == '__main__':
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO)
main(sys.argv[1:])
5 changes: 3 additions & 2 deletions java_package/brainflow/src/main/java/brainflow/BoardIds.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ public enum BoardIds
SYNCHRONI_OCTO_8_CHANNELS_BOARD(59),
OB5000_8_CHANNELS_BOARD(60),
SYNCHRONI_PENTO_8_CHANNELS_BOARD(61),
SYNCHRONI_UNO_1_CHANNELS_BOARD(62);

SYNCHRONI_UNO_1_CHANNELS_BOARD(62),
OB3000_24_CHANNELS_BOARD(63),
BIOLISTENER_BOARD(64);

private final int board_id;
private static final Map<Integer, BoardIds> bi_map = new HashMap<Integer, BoardIds> ();
Expand Down
5 changes: 2 additions & 3 deletions julia_package/brainflow/src/board_shim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ export BrainFlowInputParams
EXPLORE_PLUS_8_CHAN_BOARD = 54
EXPLORE_PLUS_32_CHAN_BOARD = 55
PIEEG_BOARD = 56

NEUROPAWN_KNIGHT_BOARD = 57
SYNCHRONI_TRIO_3_CHANNELS_BOARD = 58
SYNCHRONI_OCTO_8_CHANNELS_BOARD = 59
OB5000_8_CHANNELS_BOARD = 60
SYNCHRONI_PENTO_8_CHANNELS_BOARD = 61
SYNCHRONI_UNO_1_CHANNELS_BOARD = 62


OB3000_24_CHANNELS_BOARD = 63
BIOLISTENER_BOARD = 64

end

Expand Down
4 changes: 2 additions & 2 deletions matlab_package/brainflow/BoardIds.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@
EXPLORE_PLUS_8_CHAN_BOARD(54)
EXPLORE_PLUS_32_CHAN_BOARD(55)
PIEEG_BOARD(56)

NEUROPAWN_KNIGHT_BOARD(57)
SYNCHRONI_TRIO_3_CHANNELS_BOARD(58)
SYNCHRONI_OCTO_8_CHANNELS_BOARD(59)
OB5000_8_CHANNELS_BOARD(60)
SYNCHRONI_PENTO_8_CHANNELS_BOARD(61)
SYNCHRONI_UNO_1_CHANNELS_BOARD(62)

OB3000_24_CHANNELS_BOARD(63)
BIOLISTENER_BOARD(64)
end
end
7 changes: 4 additions & 3 deletions nodejs_package/brainflow/brainflow.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,16 @@ export enum BoardIds {
EXPLORE_PLUS_8_CHAN_BOARD = 54,
EXPLORE_PLUS_32_CHAN_BOARD = 55,
PIEEG_BOARD = 56,

NEUROPAWN_KNIGHT_BOARD = 57,
SYNCHRONI_TRIO_3_CHANNELS_BOARD = 58,
SYNCHRONI_OCTO_CHANNELS_BOARD = 59,
OB5000_8_CHANNELS_BOARD = 60,
SYNCHRONI_PENTO_8_CHANNELS_BOARD = 61,
SYNCHRONI_UNO_1_CHANNELS_BOARD = 62

SYNCHRONI_UNO_1_CHANNELS_BOARD = 62,
OB3000_24_CHANNELS_BOARD = 63,
BIOLISTENER_BOARD = 64
}

export enum IpProtocolTypes {
NO_IP_PROTOCOL = 0,
UDP = 1,
Expand Down
3 changes: 2 additions & 1 deletion python_package/brainflow/board_shim.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class BoardIds(enum.IntEnum):
OB5000_8_CHANNELS_BOARD = 60 #:
SYNCHRONI_PENTO_8_CHANNELS_BOARD = 61 #:
SYNCHRONI_UNO_1_CHANNELS_BOARD = 62 #:

OB3000_24_CHANNELS_BOARD = 63 #:
BIOLISTENER_BOARD = 64 #:


class IpProtocolTypes(enum.IntEnum):
Expand Down
6 changes: 4 additions & 2 deletions rust_package/brainflow/src/ffi/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl BoardIds {
pub const FIRST: BoardIds = BoardIds::PlaybackFileBoard;
}
impl BoardIds {
pub const LAST: BoardIds = BoardIds::NeuropawnKnightBoard;
pub const LAST: BoardIds = BoardIds::BiolistenerBoard;
}
#[repr(i32)]
#[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)]
Expand Down Expand Up @@ -100,7 +100,9 @@ pub enum BoardIds {
SynchroniOcto8ChannelsBoard = 59,
OB50008CHannelsBoard= 60 ,
SynchroniPento8ChannelsBoard = 61,
SynchroniUno1ChannelsBoard = 62
SynchroniUno1ChannelsBoard = 62,
OB300024ChannelsBoard = 63,
BiolistenerBoard = 64
}
#[repr(i32)]
#[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)]
Expand Down
Loading
Loading