Building Your Own LSL Implementation
If you’re writing your own program or script and want to publish a Knight
Board LSL stream directly — rather than running the standalone app — you
only need pylsl on top of
your serial connection. The outline below mirrors what knight_lsl_gui.py
does internally.
pip install pylsl pyserial numpy1. Open the Serial Connection and Configure Channels
Section titled “1. Open the Serial Connection and Configure Channels”Connect at 115200 baud, then send chon_/rldadd_ commands with pauses
between them, exactly as described in Command Set.
import serial, time
ser = serial.Serial("COM3", 115200, timeout=1)
for ch in range(1, 9): ser.write(f"chon_{ch}_12".encode("ascii")) time.sleep(2) ser.write(f"rldadd_{ch}".encode("ascii")) time.sleep(2)2. Parse Incoming Packets
Section titled “2. Parse Incoming Packets”Read and decode the raw serial frames as described in
Knight Board Data Format. Watch for the
0xA0 start byte and 0xC0 end byte, and remember that an IMU board
sends a longer frame (57 bytes total) than the standard board (21 bytes
total) — you can tell them apart by the frame length once packets start
arriving.
import numpy as np
NUM_CHANNELS = 8GAIN = 12SCALE = 4.0 / (2**15 - 1) / GAIN * 1_000_000.0 # µV per count
def read_eeg_sample(ser): if ser.read(1) != b"\xA0": return None payload = ser.read(20) # counter + 8×EXG + 2 LOFF + end byte if payload[19] != 0xC0: return None sample = np.zeros(NUM_CHANNELS, dtype=np.float64) for i in range(NUM_CHANNELS): hi, lo = payload[1 + 2 * i], payload[2 + 2 * i] raw = (hi << 8) | lo if raw & 0x8000: raw -= 0x10000 sample[i] = raw * SCALE return sample3. Create the LSL Outlet
Section titled “3. Create the LSL Outlet”Describe the stream once — name, type, channel count, sample rate, and per-channel metadata — then push one sample per packet.
from pylsl import StreamInfo, StreamOutlet
info = StreamInfo( name="NeuroPawnKnight", type="EEG", channel_count=NUM_CHANNELS, nominal_srate=125, channel_format="float32", source_id="neuropawn_knight",)channels = info.desc().append_child("channels")for i in range(NUM_CHANNELS): ch = channels.append_child("channel") ch.append_child_value("label", f"EXG{i + 1}") ch.append_child_value("unit", "microvolts") ch.append_child_value("type", "EEG")
outlet = StreamOutlet(info)
while True: sample = read_eeg_sample(ser) if sample is not None: outlet.push_sample(sample.astype(np.float32).tolist())4. Read the Stream Elsewhere
Section titled “4. Read the Stream Elsewhere”Any other script — on the same machine or over the network — can pick up
the stream with pylsl’s inlet API:
from pylsl import resolve_streams, StreamInlet
streams = resolve_streams()inlet = StreamInlet(next(s for s in streams if s.name() == "NeuroPawnKnight"))
while True: sample, timestamp = inlet.pull_sample() print(timestamp, sample)