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
3 changes: 3 additions & 0 deletions .github/workflows/run_unix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ jobs:
# start testing
- name: Run unit tests
run: $GITHUB_WORKSPACE/build/tests/brainflow_tests
- name: BITalino serial emulator
if: runner.os == 'Linux'
run: python3 $GITHUB_WORKSPACE/emulator/brainflow_emulator/bitalino_serial_test.py $GITHUB_WORKSPACE/installed/lib/libBoardController.so
- name: Synthetic C# Test MacOS
if: runner.os == 'macOS'
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ public enum BoardIds
IRONBCI_32_BOARD = 65,
NEUROPAWN_KNIGHT_BOARD_IMU = 66,
MUSE_S_ATHENA_BOARD = 67,
SHIMMER3_BOARD = 68
SHIMMER3_BOARD = 68,
BITALINO_BOARD = 70
};


Expand Down
44 changes: 44 additions & 0 deletions docs/SupportedBoards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1581,3 +1581,47 @@ Common sensor bits:
- :code:`0x000008`, EXG2 24-bit
- :code:`0x000004`, GSR, exposed as EDA
- :code:`0x002000`, battery

BITalino serial / Bluetooth Classic
-----------------------------------

``BoardIds.BITALINO_BOARD`` (70) acquires all six analog channels at 1000 Hz
using the original BITalino / BITalino (r)evolution serial protocol. Set
``serial_port`` to a USB or operating-system-paired Bluetooth Classic RFCOMM
port (115200 baud). BLE-only models are not supported. ``timeout`` is the
handshake and first-sample timeout in seconds (default 5).

The six ``analog_channels`` contain **raw ADC counts**, not volts or microvolts:
A1-A4 are 10-bit and A5-A6 are 6-bit. The attached sensor determines the physical
quantity and transfer function; apply the appropriate sensor calibration before
signal analysis. The four ``other_channels`` contain D1-D4 as 0/1. The package
number is the device's four-bit sequence counter, wrapping every 16 samples.
Timestamps use host receive time, not a synchronized device clock.

Set ``other_info = "simulated"`` to request the device firmware's simulated
signal mode. This still needs a connected BITalino. ``config_board("get_version")``
returns the firmware identification cached during preparation. Other sampling
rates, channel subsets, output control and battery configuration are not exposed
by this initial fixed-profile driver.

.. code-block:: python

from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds

params = BrainFlowInputParams()
params.serial_port = "/dev/rfcomm0" # or a paired COM port on Windows
board = BoardShim(BoardIds.BITALINO_BOARD, params)
board.prepare_session()
try:
board.start_stream()
# Read raw samples with get_board_data().
board.stop_stream()
finally:
board.release_session()

The parser checks CRC4 and requires two consecutive valid frames to acquire or
recover alignment. CRC4 has limited error-detection strength. Tests use golden
protocol vectors and a pseudo-terminal device emulator. Physical-device and
Bluetooth serial validation remain outstanding.

Protocol reference: https://github.com/BITalinoWorld/cpp-api
104 changes: 104 additions & 0 deletions emulator/brainflow_emulator/bitalino_serial_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Exercise the native driver with a POSIX pseudo-terminal, no device required.

Usage: python3 bitalino_serial_test.py /path/to/libBoardController.so
"""
import ctypes
import json
import os
import pty
import select
import sys
import threading
import time


def main(library):
lib = ctypes.CDLL(library)
lib.prepare_session.argtypes = [ctypes.c_int, ctypes.c_char_p]
lib.start_stream.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p]
lib.stop_stream.argtypes = lib.prepare_session.argtypes
lib.release_session.argtypes = lib.prepare_session.argtypes
lib.get_current_board_data.argtypes = [ctypes.c_int, ctypes.c_int,
ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_int),
ctypes.c_int, ctypes.c_char_p]
master, slave = pty.openpty()
params = json.dumps(dict(serial_port=os.ttyname(slave), mac_address='',
ip_address='', ip_address_aux='', ip_address_anc='', ip_port=0,
ip_port_aux=0, ip_port_anc=0, ip_protocol=0, other_info='', timeout=1,
serial_number='', file='', file_aux='', file_anc='', master_board=-100)).encode()
stop = threading.Event()
active = threading.Event()

commands = []
failures = []

def device():
table = [0, 3, 6, 5, 12, 15, 10, 9, 11, 8, 13, 14, 7, 4, 1, 2]
streaming = False
seq = 0
try:
while not stop.is_set():
readable, _, _ = select.select([master], [], [], 0.002)
if readable:
for command in os.read(master, 64):
commands.append(command)
if command == 0:
streaming = False
elif command == 7:
os.write(master, b'BITalino_v5.0\n')
elif command in (0xfd, 0xfe):
assert len(commands) >= 2 and commands[-2] == 0xc3
streaming = True
seq = 0
else:
assert command == 0xc3
if streaming and active.is_set():
packet = [209, 175, 106, 85, 0, 254, 175, seq << 4]
crc = 0
for value in packet[:-1]:
crc = table[crc] ^ (value >> 4)
crc = table[crc] ^ (value & 15)
packet[-1] |= table[table[crc] ^ seq]
os.write(master, bytes(packet[:3]))
os.write(master, bytes(packet[3:]))
seq = (seq + 1) % 16
except Exception as exc:
failures.append(exc)

worker = threading.Thread(target=device)
worker.start()
try:
assert lib.prepare_session(70, params) == 0
# No raw samples: start must fail, and its worker must be joined.
assert lib.start_stream(1024, b'', 70, params) == 18
active.set()
for _ in range(2):
assert lib.start_stream(1024, b'', 70, params) == 0
assert lib.start_stream(1024, b'', 70, params) == 8
time.sleep(0.05)
data = (ctypes.c_double * (13 * 1024))()
count = ctypes.c_int()
assert lib.get_current_board_data(1024, 0, data, ctypes.byref(count), 70, params) == 0
assert count.value > 1
expected = [1023, 512, 341, 682, 63, 17, 1, 0, 1, 0]
for channel, value in enumerate(expected, 1):
assert all(data[channel * count.value + i] == value for i in range(count.value))
assert lib.stop_stream(70, params) == 0
assert lib.stop_stream(70, params) == 11
assert lib.release_session(70, params) == 0
# Opening the port again after release must work.
assert lib.prepare_session(70, params) == 0
assert lib.start_stream(1024, b'', 70, params) == 0
finally:
lib.release_session(70, params)
stop.set()
worker.join()
os.close(master)
os.close(slave)
assert not failures, failures
assert commands[:2] == [0, 7]
print('BITalino serial lifecycle passed')


if __name__ == '__main__':
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 @@ -74,7 +74,8 @@ public enum BoardIds
IRONBCI_32_BOARD(65),
NEUROPAWN_KNIGHT_BOARD_IMU(66),
MUSE_S_ATHENA_BOARD(67),
SHIMMER3_BOARD(68);
SHIMMER3_BOARD(68),
BITALINO_BOARD(70);

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 @@ -70,6 +70,7 @@ export BrainFlowInputParams
NEUROPAWN_KNIGHT_BOARD_IMU = 66
MUSE_S_ATHENA_BOARD = 67
SHIMMER3_BOARD = 68
BITALINO_BOARD = 70

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 @@ -68,5 +68,6 @@
NEUROPAWN_KNIGHT_BOARD_IMU(66)
MUSE_S_ATHENA_BOARD(67)
SHIMMER3_BOARD(68)
BITALINO_BOARD(70)
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 @@ -77,7 +77,8 @@ export enum BoardIds {
IRONBCI_32_BOARD = 65,
NEUROPAWN_KNIGHT_BOARD_IMU = 66,
MUSE_S_ATHENA_BOARD = 67,
SHIMMER3_BOARD = 68
SHIMMER3_BOARD = 68,
BITALINO_BOARD = 70
}

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 @@ -83,6 +83,7 @@ class BoardIds(enum.IntEnum):
NEUROPAWN_KNIGHT_BOARD_IMU = 66 #:
MUSE_S_ATHENA_BOARD = 67 #:
SHIMMER3_BOARD = 68 #:
BITALINO_BOARD = 70 #:


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 @@ -104,6 +104,7 @@ pub enum BoardIds {
NeuropawnKnightBoardImu = 66,
MuseSAthenaBoard = 67,
Shimmer3Board = 68,
BitalinoBoard = 70,
}
#[repr(i32)]
#[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)]
Expand Down
Loading
Loading