Skip to content

Unicorn Hybrid Black integration — research + proposal

Status: Path A (BrainFlow) VALIDATED on hardware 2026-07-07 — streams 8-ch @ 250 Hz, clean link, with no g.tec software install and no paid API (BrainFlow bundles Unicorn.dll). Selectable in the toolbox as a board, with a fixed built-in montage (Fz C3 Cz C4 Pz PO7 Oz PO8) the Design tab auto-selects + locks when the device is chosen, and a GUI Detect-list entry (Bluetooth scan) validated 2026-07-08 (open question #4 below → resolved). Remaining: reuse for impedance is open. Aaron owns a Unicorn Hybrid Black + the paid Python API key, but the goal — future users must not have to buy the Python API — is met by this path.

Validation result (2026-07-07, serial UN-2024.10.06)

python -m ssvep.runtime.openbci --board unicorn --check --debug

use dyn lib: …\brainflow\lib\Unicorn.dll   → Library is loaded
Use device with id UN-2024.10.06
OK: 1251 samples in 5.0s (~250.2 Hz vs nominal 250), 8 EEG channels.  reliability: 0 dropped — CLEAN
So the only prerequisites are brainflow (already a dep) + the headset paired at the Windows level. The pairing was the whole battle — see below.

Pairing runbook (the part that isn't obvious)

  • The Unicorn is not paired through Windows "Add device"; pair it via the Unicorn Suite → "My Unicorn" panel, and complete the Windows pairing pop-up it triggers (no PIN is requested for this device). A mere in-app connect does not leave a persistent pairing, and BrainFlow's discovery only sees Windows-paired devices → the symptom is [error] Unicorn not found on every attempt.
  • Use the g.tec CSR8510 dongle ("Generic Bluetooth Radio" in Device Manager), disable the internal Bluetooth, and turn off power-saving on the dongle radio (Device Manager → Properties → Power Management). Headset blue-flashing = advertising.
  • Once paired, BrainFlow auto-discovers the single device; pass --serial-port UN-YYYY.MM.XX only to pick among several. The board-specific failure checklist in openbci._checklist("unicorn") now encodes all of this.
  • Pairing persists across a headset power-cycle (verified 2026-07-07 — powered the Unicorn off/on and it streamed again with the Suite closed, no re-pair). So the Unicorn Suite is a one-time pairing tool; it is never needed to run a session.

How to test Path A (Aaron, on the Windows acquisition PC)

  1. Ensure g.tec's free Unicorn.dll is present where BrainFlow can load it (it ships with the free Unicorn Suite / C API). Pair the headset over Bluetooth (see "Bluetooth adapter" below).
  2. Bounded connectivity check, mirroring how we validated the Cyton boards:
    python -m ssvep.runtime.openbci --board unicorn --check
    # or, to pin one of several units by serial number:
    python -m ssvep.runtime.openbci --board unicorn --serial-port UN-YYYY.MM.XX --check
    
    Expect 8 EEG ch @ 250 Hz, montage Fz C3 Cz C4 Pz PO7 Oz PO8, and the same dropped-sample / effective-rate reliability line we get for OpenBCI (the Unicorn has a usable packet counter). A green check means the whole existing bridge/retry/live-view stack works with the Unicorn as-is.
  3. If the check fails, the likely cause is BrainFlow-on-Windows Unicorn support or the DLL location — that's exactly open question #1, and the point where we'd fall back to Path C/D.

Goal & constraints

  • No purchase in the happy path. A lab that has a Unicorn but not the g.tec Python API must be able to run this toolbox with it. (The C API, .NET API, Recorder, and LSL interface come free with the device; only the Python API is a paid add-on — confirmed by Aaron, consistent with the sources below.)
  • License hygiene (CLAUDE.md locked decision). Avoid strong copyleft (GPL). This rules out vendoring robertoostenveld/unicorn2xx (GPL-3.0), though we may read the public Bluetooth protocol spec it's built against and implement independently (a data format is not copyrightable).
  • Keep the array-API + LSL spine. Everything downstream already consumes either a plain (n_ch, n_times) array + sfreq (decode/spectral/metrics) or an LSL EEG stream (RunRecorder.preflight resolves EEG by type). Any Unicorn path should terminate in one of those two seams and leave the rest of the toolbox untouched — exactly how OpenBCI plugs in today via runtime/openbci.py's BrainFlow→LSL bridge.

The device, briefly

8 EEG channels @ 250 Hz, plus 3-axis accel (and gyro/battery/counter/validation), streamed over Bluetooth. On Windows a paired Unicorn presents as a Bluetooth serial COM port (device name like UN-YYYY.MM.XX) — i.e. it looks like a serial device, distinct from the OpenBCI FTDI dongle and from the generic "Standard Serial over Bluetooth link" ports our discovery.py already rejects. The raw link is a fixed 45-byte packet protocol, publicly documented (see unicorn2xx / unicorn-bi).

Candidate integration paths

# Path New code Purchase License Notes
B UnicornLSL → LSL (g.tec's free LSL app streams; we just record the stream) ~none none (free w/ device) proprietary app, but only run, not linked Works today. Manual: operator launches a Windows GUI app per session. No impedance/board-control through LSL.
A BrainFlow UNICORN_BOARD (add to the board map; reuse the existing bridge) tiny none (needs free Unicorn.dll) BrainFlow MIT + proprietary (free) lib Smallest change if BrainFlow's Unicorn works on Windows — the docs only promise Ubuntu/RPi. Must verify.
C Free C API via ctypes (thin wrapper over Unicorn.dll/.lib + a unicorn_to_lsl bridge mirroring openbci.stream_to_lsl) moderate none (free C API, bundled) proprietary (free) lib, our wrapper is ours Fully integrated, g.tec maintains device handling. Needs the free C API installed (it ships to Documents\gtec\Unicorn Suite\…\Unicorn CAPI).
D Pure-Python protocol reader (implement the documented 45-byte BT packet over pyserial, + unicorn_to_lsl) most none fully ours (no proprietary bits) Maximally independent & cross-platform; depends on nothing g.tec-proprietary. We own connection reliability. unicorn2xx proves feasibility (but is GPL — implement from the spec, don't copy).

All four avoid the paid Python API, so all satisfy the core requirement. They differ in effort, how "seamless" the operator experience is, and how much proprietary/free-but-closed surface they pull in.

Recommendation — staged, cheapest-proof-first

  1. Phase 0 — validate end-to-end now, zero toolbox code (Path B). Install the free Unicorn Suite, run UnicornLSL, and point the existing recorder at that LSL EEG stream (set the stream name; RunRecorder already resolves + verifies it against the manifest). This confirms the whole pipeline (markers, XDF, decode, report) works with Unicorn data and gives an always-available fallback for any lab that has the Suite. Also useful as ground truth to validate Phases A/C/D against.
  2. Phase 1 — try the near-free integration (Path A). Drop the free Unicorn.dll where BrainFlow can load it and try BoardIds.UNICORN_BOARD on the Windows acquisition PC. If it streams, Unicorn support is ~5 lines: add "unicorn": BoardIds.UNICORN_BOARD to openbci._board_id, teach _make_board that it's a Bluetooth/serial-number device, and it inherits the entire existing bridge, retry, drop-metric, and live-view stack. This is the ideal outcome — verify first because BrainFlow's Unicorn support is documented for Linux, not Windows.
  3. Phase 2 — the purchase-free default if Phase 1 is unreliable. Build a first-class in-toolbox driver terminating in a unicorn_to_lsl bridge (same shape as openbci.stream_to_lsl, so the runtime is unchanged). Choose:
  4. Path C (free C API via ctypes) if we want g.tec to own device/BLE handling and are fine with an optional proprietary-but-free dependency (same posture as BrainFlow itself — a lazy import, never redistributed; the user installs the free Suite).
  5. Path D (pure-Python protocol) if we want zero proprietary surface and full cross-platform control, at the cost of owning the packet parser and connection robustness. Best fit for "future users install nothing extra" and for the toolbox's bare-numpy/pyserial ethos.

Lean C first (less code, g.tec-maintained), with D as the fully-independent option if the free C API's terms or platform coverage ever become a problem.

How it plugs into what we just built

  • Discovery (runtime/discovery.py). A paired Unicorn does not present as a COM port (unlike the FTDI dongle); it enumerates as a Windows Bluetooth device whose friendly name is its serial number. classify_bluetooth_device / discover_unicorn detect it as a third device class (board="unicorn", transport="bluetooth", target=the serial), and discover_devices folds it into the same Detect pick-list as the dongles/shields. The "target" is the Unicorn serial number, carried through the shared serial_port slot into BrainFlow's serial_number.
  • Provenance (Provenance.device). The Unicorn serial number (printed on the device / from the C API GetDeviceInformation) is the stable per-unit identity — exactly the slot we just added.
  • Board/rate. Unicorn is fixed 8-ch @ 250 Hz; the manifest's acquisition.board gains a unicorn value and sfreq_hz = 250. The board_mismatch warning already covers designed-vs-actual.

Bluetooth adapter (a real reliability lever)

g.tec recommends disabling the PC's internal Bluetooth and using their supplied Logitech BT dongle — the internal radio is the usual cause of dropped/laggy Unicorn links (the Unicorn analogue of the WiFi-shield packet loss we already measured). Decisions:

  • For now, test with the Logitech dongle so Bluetooth isn't a confound while we validate Path A. This is an OS-level setting (disable the internal BT adapter in Windows Device Manager, plug in the dongle) — no toolbox code changes: BrainFlow/Unicorn.dll just use whatever Windows BLE adapter is active.
  • Goal: not require the dongle. Whether the internal radio is good enough is an empirical question we can already answer — our dropped_from_counter reliability metric quantifies link loss per session, so we can bench internal-BT vs dongle the same way we compared the WiFi shields (3–6% loss on a bad link vs 0% clean) and set an evidence-based recommendation rather than a guess.
  • Provenance idea (later): optionally record which adapter was used, since it materially affects link quality — but adapter introspection on Windows is fiddly; defer unless it proves necessary.

Compliance

  • An ethics amendment is required. The Unicorn is not covered by this deployment's approved protocol (same situation as the OpenBCI Cyton). Adding it is a hardware/method change → flag and amend before any collection (docs/REB_AMENDMENTS.md, CLAUDE.md §4). No participant data on the Unicorn until amended.
  • License. Our code stays permissive. The free C API (Unicorn.dll) is proprietary-but-free — treat it like BrainFlow's own third-party board libs: an optional dependency the user installs, never committed/redistributed here. Do not vendor unicorn2xx (GPL-3.0).
  • New deps. Path A adds nothing (BrainFlow already in the tree). Path C adds only stdlib ctypes. Path D uses pyserial (already present via BrainFlow). None needs Aaron's dep sign-off beyond the proprietary Unicorn.dll (Path A/C), which isn't a Python package.

Hardware findings (2026-07-07, first bring-up)

  • BrainFlow's Windows Unicorn path works and needs no g.tec software install. With --debug, the bounded check logs use dyn lib: …\brainflow\lib\Unicorn.dllLibrary is loaded — i.e. the brainflow wheel already bundles Unicorn.dll. So Path A's only prerequisites are brainflow (already a dep) + an OS-paired headset. This resolves open questions #1 and #2 and makes Path C (a separate ctypes wrapper over a user-installed C API) largely unnecessary — keep C/D only as the "drop the g.tec binary entirely" fallback.
  • The remaining blocker is OS-level Bluetooth pairing. BrainFlow discovery returns Unicorn not found because the headset was only selected inside the Unicorn Suite (an in-app BLE connect), not paired in Windows Bluetooth settings. The g.tec discovery scans Windows-paired devices, so the headset must be added via Settings → Bluetooth (UN-YYYY.MM.XX) on the Logitech dongle's radio. Not a firewall issue (the library is running and scanning — it just finds nothing paired).
  • Open: whether the Windows pairing persists across power-cycles or must be re-done per session (some BLE devices re-advertise); confirm once paired. If it doesn't persist, that's a workflow note, not a code blocker.

Open questions (still to verify)

  1. ~~Does the Windows pairing persist across headset power-cycles?~~ Resolved: yes (2026-07-07).
  2. Link reliability (dropped_from_counter) on the g.tec CSR8510 dongle vs the internal Bluetooth — the "do we actually need the dongle" question. Bench both once with electrodes on a head. (First checks on the dongle: 0 dropped / clean.)
  3. Impedance: the Unicorn has its own electrode-quality readout — is it reachable through BrainFlow so we can reuse the impedance-check UI, or is that OpenBCI-lead-off-only? (The Cyton lead-off config_board path does not apply to the Unicorn.)
  4. ~~GUI Detect: our discovery.py can't yet enumerate a paired Unicorn.~~ Resolved (2026-07-08). discovery.discover_unicorn() enumerates paired Unicorns by their Bluetooth friendly name (UN-YYYY.MM.XX, which is the BrainFlow serial_number) via Get-PnpDevice, using the Windows PnP Present flag to put currently-connected units first. Wired into discover_devices() so the Set-up-Session-tab Detect button lists them alongside OpenBCI dongles/shields; confirmed on hardware against 3 paired headsets (2 connected, 1 idle). The serial_number flows through the existing serial_port slot into openbci._make_board's Unicorn branch. (Detection is independent of the Unicorn Suite; run a session with the Suite closed — the headset is single-client.)

Sources

  • BrainFlow supported boards (Unicorn = UNICORN_BOARD, 8ch/250Hz/BLE, libunicorn): https://brainflow.readthedocs.io/en/stable/SupportedBoards.html
  • unicorn-bi org (free C/.NET APIs, Recorder, LSL): https://github.com/unicorn-bi
  • Free Windows APIs (C/.NET/Python docs; C API ships Unicorn.lib/unicorn.h): https://github.com/unicorn-bi/Unicorn-Hybrid-Black-Windows-APIs
  • Unicorn Suite contents / LSL interface included: https://www.gtec.at/product/unicorn-suite/
  • robertoostenveld/unicorn2xx — GPL-3.0 CLI streamer proving the documented BT protocol path (Unicorn2lsl etc.): https://github.com/robertoostenveld/unicorn2xx
  • Oostenveld review (Unicorn as BT COM port, UnicornLSL, protocol notes): https://robertoostenveld.nl/unicorn-hybrid-black/
  • FieldTrip realtime Unicorn (UnicornLSL is Windows-only): https://www.fieldtriptoolbox.org/development/realtime/unicorn/