cognionics 0.0.1

Rust client for Cognionics / CGX EEG headsets over USB/FTDI serial
Documentation

cognionics

Rust library and terminal UI for streaming EEG data from Cognionics / CGX headsets over USB serial (FTDI dongle).

Supported Hardware

Model EEG ch ExG ACC Default Rate Channels
Quick-20 20 4 500 Hz 26 total
Quick-20r-v1 20 1 500 Hz 26 total
Quick-20m 20 1 500 Hz 26 total
Quick-20r 20 2 500 Hz 27 total
Quick-32r 30 2 500 Hz 37 total
Quick-8r 8+ref 1 500 Hz 15 total
AIM-2 11 500 Hz 13 total
Dev Kit 8 500 Hz 13 total
Patch-v1 2 3 250 Hz 10 total
Patch-v2 2 2 250 Hz 8 total

Device model is auto-detected from the USB descriptor string. All models use the same serial protocol — 24-bit ADC samples at 3 bytes per channel, with sync-byte framing over FTDI USB serial.

Quick Start

CLI

# List available devices and supported models
cargo run -- --list

# Auto-detect and stream from the first CGX device found
cargo run

# Specify a model explicitly
cargo run -- --model quick-20r

# Specify a serial port
cargo run -- --port /dev/ttyUSB0

# Enable impedance check
cargo run -- --impedance

TUI (Terminal UI)

# Real-time waveform viewer (auto-detect device)
cargo run --bin tui

# Simulate a Quick-20r (no hardware needed)
cargo run --bin tui -- --simulate

# Simulate a specific model
cargo run --bin tui -- --simulate --model quick-32r

TUI Keys:

Key Action
1 EEG-only view
2 All channels view
+ / - Zoom out / in (µV scale)
a Auto-scale to fit signal
v Toggle smooth overlay
p / r Pause / Resume
c Clear buffers
q / Esc Quit

Library Usage

use cognionics::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = CgxClient::new(CgxClientConfig::default());
    let (mut rx, handle) = client.start().await?;

    println!("Connected: {} ({} channels at {} Hz)",
        handle.model(),
        handle.num_signal_channels(),
        handle.sampling_rate());

    while let Some(event) = rx.recv().await {
        match event {
            CgxEvent::Sample(s) => {
                println!("pkt={} ch[0]={:.2} µV", s.packet_counter, s.channels[0]);
            }
            CgxEvent::Disconnected => break,
            _ => {}
        }
    }
    Ok(())
}

Cargo.toml

[dependencies]
# Full build (includes TUI + virtual device):
cognionics = "0.1.0"

# Library only — no TUI, no virtual device:
cognionics = { version = "0.1.0", default-features = false }

# Library + virtual device for testing (no TUI):
cognionics = { version = "0.1.0", default-features = false, features = ["virtual_device"] }

Features

Feature Default Description
tui ratatui terminal UI binary
virtual_device Mock device module for testing without hardware

Architecture

USB Dongle ──(FTDI serial)──▸ CgxReader (background OS thread)
                                  │
                                  ▼
                             byte stream → frame sync → 24-bit decode
                                  │
                                  ▼
                             tokio::mpsc::Sender<CgxEvent>
                                  │
                                  ▼
                             user's mpsc::Receiver<CgxEvent>

The reader runs on a dedicated OS thread (not a tokio task) because serial port I/O is blocking. Events are forwarded to the async world through a tokio mpsc channel.

Events

The CgxEvent enum carries all data from the device:

Variant Description
Sample(CgxSample) One multi-channel sample with channels (µV), packet_counter, trigger, timestamp
Connected(String) Connection established; carries device descriptor
Disconnected Device disconnected or stream ended
PacketLoss { lost, prev_counter, curr_counter } Gap detected in packet counter sequence
Error(String) Non-fatal error (e.g. data gap timeout)
Info(String) Informational log message

Protocol Details

CGX headsets transmit a continuous byte stream over FTDI USB serial:

  • Framing: Each sample starts with sync byte 0xFF followed by overhead bytes (status/battery), then 3 bytes per channel (24-bit big-endian signed ADC)
  • Channels: Last two channels are always Packet Counter (8-bit, wrapping 0–255) and TRIGGER
  • ADC scale: raw × 3.880510727564494e-10 volts/LSB
  • Impedance: 125 Hz carrier wave toggled via DTR; scale factor 0.0390625 ohms/LSB

Linux Permissions

On Linux you may need a udev rule to access the USB port without root:

echo 'KERNEL=="ttyUSB[0-9]*",MODE="0666"' | sudo tee /etc/udev/rules.d/50-cgx.rules
sudo udevadm control --reload-rules
# Unplug and re-plug the dongle

Module Overview

Module Purpose
prelude One-line glob import of all commonly needed types
cgx_client Device scanning, serial connection, and CgxHandle API
types All event and data types (CgxEvent, CgxSample, etc.)
devices Channel layouts, baud rates, and device configs for all models
parse 24-bit ADC decoders, frame sync, and packet loss detection
virtual_device (feature) Mock device, fault injection, build_frame(), start_virtual()

License

MIT