cyton-dongle
Connect and stream from OpenBCI Cyton/Daisy via USB dongle, including first-time radio channel pairing
Best use case
cyton-dongle is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Connect and stream from OpenBCI Cyton/Daisy via USB dongle, including first-time radio channel pairing
Teams using cyton-dongle should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cyton-dongle/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How cyton-dongle Compares
| Feature / Agent | cyton-dongle | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Connect and stream from OpenBCI Cyton/Daisy via USB dongle, including first-time radio channel pairing
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Cyton Dongle
USB wireless receiver (RFD22301/RFDuino) for OpenBCI Cyton 8/16-channel EEG board.
## Hardware
- **Dongle**: FTDI FT231X USB-UART → RFDuino 2.4 GHz radio
- **Serial**: 115200 baud, 8N1
- **Device**: `/dev/cu.usbserial-*` (macOS) or `/dev/ttyUSB*` (Linux)
- **Sample Rate**: 250 Hz
- **Channels**: 8 (Cyton) or 16 (Cyton + Daisy)
- **Packet**: 33 bytes (0xA0 start, 24 bytes channel data, 6 bytes aux, 1 byte counter, 0xC0 stop)
## First-Time Pairing (Critical)
A new dongle and board are typically on **different radio channels**. The standard `0xF0 0x01` channel-set command requires both sides to handshake — it **fails when they're on different channels**.
Use `0xF0 0x02` (CHANNEL_SET_OVERRIDE) to force the dongle to each channel without requiring board response, then check system status:
```python
import serial, time
ser = serial.Serial('/dev/cu.usbserial-XXXXX', 115200, timeout=2)
time.sleep(2)
for chan in range(26):
ser.reset_input_buffer()
ser.write(bytes([0xF0, 0x02, chan])) # override dongle (no handshake)
time.sleep(0.5)
ser.read(ser.in_waiting or 512)
ser.reset_input_buffer()
ser.write(bytes([0xF0, 0x07])) # system status query
time.sleep(0.5)
resp = ser.read(ser.in_waiting or 512).decode('utf-8', errors='ignore')
if 'System is Up' in resp:
print(f'FOUND BOARD ON CHANNEL {chan}')
break
else:
print(f'Ch {chan}: Down')
ser.close()
```
## Radio Commands (0xF0 prefix)
| Bytes | Command | Notes |
|-------|---------|-------|
| `0xF0 0x00` | CHANNEL_GET | Returns current dongle channel |
| `0xF0 0x01 <ch>` | CHANNEL_SET | Coordinated change, **requires board online** |
| `0xF0 0x02 <ch>` | CHANNEL_OVERRIDE | **Dongle-only, no handshake** — use for pairing |
| `0xF0 0x03` | POLL_TIME_GET | Current poll time |
| `0xF0 0x04 <t>` | POLL_TIME_SET | Set poll time |
| `0xF0 0x05` | BAUD_DEFAULT | 115200 |
| `0xF0 0x06` | BAUD_FAST | 230400 |
| `0xF0 0x07` | SYS_STATUS | "System is Up" or "System is Down" |
| `0xF0 0x0A` | BAUD_HYPER | 921600 |
Channels are 0-25. Default for new boards is usually 1.
## Serial Commands
| Cmd | Action |
|-----|--------|
| `v` | Firmware version + board info |
| `b` | Start binary streaming |
| `s` | Stop streaming |
| `C` | Enable Daisy (16ch mode) |
| `D` | Query Daisy module |
| `?` | Print ADS1299 registers |
| `1`-`8` | Default channel settings (ch 1-8) |
| `!`-`*` | Default channel settings (ch 9-16, Daisy) |
| `d` | Reset all channel defaults |
## Parsing Binary Packets
```python
SCALE_UV = 4.5 / (24 * (2**23 - 1)) * 1e6 # ~0.02235 uV/count
def parse_24bit(b0, b1, b2):
val = (b0 << 16) | (b1 << 8) | b2
return val - 0x1000000 if val >= 0x800000 else val
```
**33-byte packet**: `0xA0 | sample_num | 8×3-byte channels | 6-byte aux | 0xC0`
With Daisy: odd sample numbers = channels 1-8, even = channels 9-16.
## Streaming and Channel Quality Check
```python
import serial, time, math
ser = serial.Serial('/dev/cu.usbserial-XXXXX', 115200, timeout=5)
time.sleep(2)
ser.reset_input_buffer()
# Override to known channel
ser.write(bytes([0xF0, 0x02, CHANNEL]))
time.sleep(1)
ser.read(ser.in_waiting or 512)
# Reset board
ser.write(b'v')
time.sleep(3)
ser.read(ser.in_waiting or 4096)
ser.reset_input_buffer()
SCALE_UV = 4.5 / (24 * (2**23 - 1)) * 1e6
def p24(b0, b1, b2):
v = (b0 << 16) | (b1 << 8) | b2
return v - 0x1000000 if v >= 0x800000 else v
# Start stream
ser.write(b'b')
time.sleep(1.5)
ser.read(ser.in_waiting or 2048) # drain text
samples = {i: [] for i in range(16)}
t0 = time.time()
while (time.time() - t0) < 4:
avail = ser.in_waiting
if not avail:
time.sleep(0.01)
continue
buf = ser.read(avail)
i = 0
while i < len(buf) - 32:
if buf[i] == 0xA0 and buf[i+32] == 0xC0:
sn = buf[i+1]
is_daisy = (sn % 2 == 0)
for ch in range(8):
off = i + 2 + ch * 3
raw = p24(buf[off], buf[off+1], buf[off+2])
samples[ch + (8 if is_daisy else 0)].append(raw * SCALE_UV)
i += 33
else:
i += 1
ser.write(b's')
ser.close()
# Assess quality
for ch in range(16):
vals = samples[ch]
if len(vals) < 10:
print(f'Ch {ch+1}: NO DATA')
continue
mean = sum(vals) / len(vals)
std = math.sqrt(sum((v - mean)**2 for v in vals) / len(vals))
if abs(mean) > 187000: q = 'RAILED'
elif std < 1: q = 'FLAT'
elif std > 200: q = 'BAD CONTACT'
elif std > 100: q = 'NOISY'
elif std < 50: q = 'CLEAN'
else: q = 'OK'
print(f'Ch {ch+1}: {q} (std={std:.1f} uV)')
```
## Ultracortex Mark IV 16ch Montage (10-20)
| Ch | Position | Ch | Position |
|----|----------|----|----------|
| 1 | Fp1 | 9 | F7 |
| 2 | Fp2 | 10 | F8 |
| 3 | C3 | 11 | F3 |
| 4 | C4 | 12 | F4 |
| 5 | P7 | 13 | T7 |
| 6 | P8 | 14 | T8 |
| 7 | O1 | 15 | P3 |
| 8 | O2 | 16 | P4 |
## Daisy Module (16ch)
The Daisy stacks on top of the Cyton, adding a second ADS1299 for channels 9-16.
**Verifying Daisy**:
- `v` should report: `On Daisy ADS1299 Device ID: 0x3E`
- `D` returns Daisy firmware version (e.g., `060110`)
- `C` enables 16ch mode, returns `16`
- `c` (lowercase) disables Daisy, returns `daisy removed`
**Daisy interleaving**: In 16ch mode, the board alternates packets:
- **Odd sample numbers** (1,3,5...): channels 1-8 (main board)
- **Even sample numbers** (2,4,6...): channels 9-16 (Daisy)
Expect ~1:1 ratio of main:daisy packets. If Daisy packets are missing or all-zero, check that the Daisy board is firmly seated on the Cyton header pins.
## ADS1299 Registers
Query with `?`. Key registers per channel:
| Register | Default | Meaning |
|----------|---------|---------|
| `0x68` | Normal input, gain 24x, powered on |
| `0xE8` | Powered down (bit 7 set) |
| `0x60` | Normal input, gain 24x, SRB2 off |
- `BIAS_SENSP = 0xFF`: All channels feeding bias drive (good)
- `CONFIG1 = 0xB6`: 250 Hz sample rate, daisy mode
- `CONFIG3 = 0xEC`: Internal reference, bias enabled
## Electrode Quality Thresholds
| Std Dev (uV) | Status | Meaning |
|--------------|--------|---------|
| < 1 | FLAT | Shorted to reference or no contact |
| < 50 | CLEAN | Good signal, usable for all analysis |
| 50-100 | OK | Usable for most band power analysis |
| 100-200 | NOISY | May work for gross features (eye blinks) |
| > 200 | BAD CONTACT | Electrode touching but loose |
| mean ±187500 | RAILED | Not touching skin, pinned to ADC rail |
## Session Persistence
The dongle **does not persist** the channel override across serial sessions. Every time you open a new serial connection, you must re-send `0xF0 0x02 <channel>`. Keep the serial port open for the duration of your recording, or store the known channel and re-override on connect.
The board also goes to **sleep after extended idle** with no streaming. Toggle the power switch OFF→PC to wake it, then re-scan.
## Dongle Switch Position
The dongle has a small switch with two positions:
| Position | Mode | Use |
|----------|------|-----|
| **GPIO_6** | Normal operation | **Use this for data streaming** |
| **Reset** | Bootloader/programming | Firmware upload only |
If the switch is on "Reset", commands may partially work (radio config, `v`, `?`) but **binary streaming will fail** — the RFDuino stays in bootloader mode and cannot relay continuous data. This is easy to miss because single-shot commands still get responses.
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| "Device failed to poll Host" | Channel mismatch | Use `0xF0 0x02` override scan (see above) |
| "System is Down" | Board off or wrong channel | Check power, scan channels |
| Channel stuck on set | `0x01` needs board handshake | Use `0x02` override instead |
| RAILED at ±187500 uV | Electrode not connected | Check pin seating and wire |
| FLAT near 0 | Shorted to ref or no contact | Apply gel, press electrode |
| FLAT at exactly 0.0 | Daisy wires not plugged in | Check header pin connections |
| High noise (>200 uV std) | Poor electrode contact | Tighten cap, add paste |
| 0 packets after `b` | Dongle switch on "Reset" | Set switch to **GPIO_6** position |
| Commands work, stream doesn't | Dongle in bootloader mode | Check switch is GPIO_6, not Reset |
| Daisy ch all zero | Daisy not seated or `C` not sent | Reseat Daisy, send `C` before `b` |
| All channels railed one side | Cap too loose / wrong size | Tighten straps, try gel electrodes |
| Commands work but stream doesn't | Board slept during idle | Toggle OFF→PC, re-pair |
## Firmware Source
- Dongle: `github.com/OpenBCI/OpenBCI_Radios`
- Board: `github.com/OpenBCI/OpenBCI_32bit_Library`Related Skills
zx-calculus
Coecke's ZX-calculus for quantum circuit reasoning via string diagrams with Z-spiders (green) and X-spiders (red)
zulip-cogen
Zulip Cogen Skill 🐸⚡
zls-integration
zls-integration skill
zig
zig skill
zig-syrup-bci
Multimodal BCI pipeline in Zig: DSI-24 EEG, fNIRS mBLL, eye tracking IVT, LSL sync, EDF read/write, GF(3) conservation
zig-programming
zig-programming skill
zeroth-bot
Zeroth Bot - 3D-printed open-source humanoid robot platform for sim-to-real and RL research. Affordable entry point for humanoid robotics.
xlsx
Comprehensive spreadsheet creation, editing, and analysis with support
wycheproof
Google's Wycheproof test vectors for cryptographic implementation testing.
Writing Hookify Rules
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
worldmat-tidar
worldmat-tidar
worlding
Gay.jl world_ pattern: persistent composable state builders with GF(3) conservation, Möbius invertibility, and Narya verification