ESP32 Support
Secure LSL includes a protocol-compatible implementation for ESP32 microcontrollers, enabling WiFi-connected embedded devices to participate in encrypted LSL lab networks.
Overview
liblsl-ESP32 is a clean-room C reimplementation of the LSL wire protocol for ESP32, with full secureLSL encryption support. It is not a port of desktop liblsl; it reimplements the protocol from scratch using ESP-IDF native APIs.
Scope
liblsl-ESP32 provides the communication layer for streaming data over WiFi using the LSL protocol. While the ESP32 includes built-in ADC peripherals, this implementation focuses on the networking and protocol stack rather than signal acquisition. For biosignal applications (EEG, EMG, ECG), the ESP32 typically serves as a wireless bridge: a dedicated ADC IC (e.g., ADS1299, ADS1294) handles acquisition with the precision, noise floor, and simultaneous sampling required for research-grade recordings, while the ESP32 handles WiFi, LSL protocol, and encryption. This separation follows established practice in wireless biosignal systems.
The current implementation uses 802.11 WiFi, but the protocol and encryption layers are transport-agnostic (standard BSD sockets). Developers can substitute alternative low-latency transports including Ethernet (SPI PHY), Bluetooth, or ESP-NOW, reusing the LSL protocol and secureLSL encryption modules. Note that LSL is designed for low-latency local network environments; high-latency transports are not suitable.
Why a Reimplementation?
Desktop liblsl is ~50,000+ lines of C++ coupled to Boost, pugixml, and C++ features (exceptions, RTTI) that are impractical on a device with 520KB SRAM. The LSL wire protocol is simple (UDP discovery, TCP streamfeed, binary samples), making a clean C reimplementation (~4,000 lines) both smaller and more maintainable.
Features
- Full LSL protocol: UDP multicast discovery + TCP data streaming (v1.10)
- Bidirectional: both outlet (push) and inlet (pull)
- secureLSL encryption: ChaCha20-Poly1305 authenticated encryption, X25519 key exchange (from Ed25519 identity keys)
- Desktop interop: verified with pylsl, LabRecorder, and desktop secureLSL
- Real-time: sustains up to 1000 Hz with near-zero packet loss
- Lightweight: ~200KB SRAM footprint, 300KB+ free for application
Hardware Requirements
| Requirement | Minimum | Tested |
|---|---|---|
| MCU | ESP32 (Xtensa LX6) | ESP32-WROOM-32 |
| SRAM | 520KB | ESP32-DevKitC v4 |
| Flash | 2MB+ | 4MB |
| WiFi | 802.11 b/g/n | 2.4GHz |
Quick Start
Prerequisites
- ESP-IDF v5.5+
- ESP32 development board
- WiFi network shared with desktop
- For encrypted streaming: desktop Secure LSL (see Installation)
1. Flash the secure outlet example
cd liblsl-ESP32/examples/secure_outlet
idf.py menuconfig
# Set WiFi credentials and secureLSL keypair
idf.py build
idf.py -p /dev/cu.usbserial-XXXX flash monitor
2. Receive on desktop with Secure LSL
Ensure you have built Secure LSL with security enabled (see Installation), then:
3. Or use the unencrypted outlet
cd liblsl-ESP32/examples/basic_outlet
idf.py menuconfig # Set WiFi
idf.py build && idf.py -p PORT flash monitor
import pylsl
streams = pylsl.resolve_byprop('name', 'ESP32Test', timeout=10)
inlet = pylsl.StreamInlet(streams[0])
sample, ts = inlet.pull_sample()
Security Setup
The ESP32 uses the same shared keypair model as desktop Secure LSL. All devices in a lab must share the same Ed25519 keypair.
All devices must share the same keypair
The ESP32 and desktop must have identical Ed25519 keypairs. Mismatched keys result in a 403 connection rejection (unanimous security enforcement).
Key Provisioning
The recommended workflow is to generate keys on the desktop using lsl-keygen, then import them to the ESP32:
#include "lsl_esp32.h"
#include "nvs_flash.h"
nvs_flash_init();
// Import the desktop keypair (recommended)
lsl_esp32_import_keypair("BASE64_PUBLIC_KEY", "BASE64_PRIVATE_KEY");
// Enable encryption for all subsequent outlets/inlets
lsl_esp32_enable_security();
Alternatively, generate a new keypair on the ESP32 and distribute it to all devices:
lsl_esp32_generate_keypair();
// Export public key for distribution to desktop and other devices
char pubkey[LSL_ESP32_KEY_BASE64_SIZE];
lsl_esp32_export_pubkey(pubkey, sizeof(pubkey));
// Import the full keypair to desktop via lsl_api.cfg
No passphrase support on ESP32
The ESP32 stores raw (unencrypted) Ed25519 keys in NVS. It does not support passphrase-protected keys (encrypted_private_key). When configuring the desktop lsl_api.cfg for ESP32 interop, use the private_key field (unencrypted format, generated with lsl-keygen --insecure) rather than the default encrypted_private_key.
Desktop Configuration
The desktop must have the matching keypair in ~/.lsl_api/lsl_api.cfg:
For key extraction and distribution details, see the ESP32 Security Guide.
API Overview
// Stream info
lsl_esp32_stream_info_t info = lsl_esp32_create_streaminfo(
"MyStream", "EEG", 8, 250.0, LSL_ESP32_FMT_FLOAT32, "source_id");
// Outlet (push)
lsl_esp32_outlet_t outlet = lsl_esp32_create_outlet(info, 0, 360);
lsl_esp32_push_sample_f(outlet, data, 0.0);
// Inlet (pull)
lsl_esp32_stream_info_t found;
lsl_esp32_resolve_stream("name", "DesktopStream", 10.0, &found);
lsl_esp32_inlet_t inlet = lsl_esp32_create_inlet(found);
lsl_esp32_inlet_pull_sample_f(inlet, buf, buf_len, ×tamp, 5.0);
// Security
lsl_esp32_generate_keypair();
lsl_esp32_import_keypair(base64_pub, base64_priv);
lsl_esp32_export_pubkey(out, out_len);
lsl_esp32_has_keypair();
lsl_esp32_enable_security();
Full API: lsl_esp32.h on GitHub
Performance
Benchmarked on ESP32-DevKitC v4 over WiFi (802.11n, RSSI -36 dBm):
| Config | Rate | Packet Loss | Encryption Cost |
|---|---|---|---|
| 8ch float32 | 250 Hz | 0% | 2 KB heap (push async) |
| 8ch float32 | 500 Hz | 0% | 2 KB heap (push async) |
| 8ch float32 | 1000 Hz | 0.02% | 2 KB heap (push async) |
| 64ch float32 | 250 Hz | 0% | 2 KB heap (push async) |
Encryption (ChaCha20-Poly1305) runs asynchronously on core 1 in the TCP feed task, while the application pushes to a lock-free ring buffer on core 0. The encryption overhead is not observable on the application push path; the 2 KB heap overhead for security sessions is the only measurable cost.
Protocol Compatibility
| Feature | Desktop liblsl | liblsl-ESP32 |
|---|---|---|
| Protocol version | 1.00 + 1.10 | 1.10 only |
| IP version | IPv4 + IPv6 | IPv4 only |
| Channel formats | All | float32, double64, int32, int16, int8 |
| secureLSL encryption | Yes | Yes (wire-compatible) |
| Max connections | Unlimited | 3 concurrent |
| Max channels | Unlimited | 128 |
Examples
| Example | Description |
|---|---|
basic_outlet |
Unencrypted 8-channel sine wave outlet |
basic_inlet |
Unencrypted stream receiver |
secure_outlet |
Encrypted outlet with key provisioning |
secure_inlet |
Encrypted receiver |
Documentation
For detailed documentation, see the liblsl-ESP32 repository:
- Architecture -- protocol layers, threading, memory
- Security Guide -- key provisioning, setup, troubleshooting
- Benchmarks -- methodology and full results
- Changelog -- version history