Python
The Python example repository maintains the source, native library setup, and run instructions.
Ubuntu / Linux Python
The public 1.x Linux example shares the python/ package root with macOS and Windows. Its entrypoint is linux/example.py; a separate python/ubuntu/ copy is no longer used. Linux uses Bleak/BlueZ and the native SDK C parser, targeting Ubuntu 22.04, Python 3.10+, and architecture-specific x86_64 or aarch64 libraries.
Obtain the complete Python package containing both linux/ and zenlite/, then run from python/ or the delivery package root:
sudo apt-get install python3-venv bluez libuv1 unzip
bash download-libs.sh
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r linux/requirements.txt
python linux/example.py DEVICE_NAME --pairing-id ubuntu-host-01 --pairUse --pair only for a device in first-time pairing mode; omit it on subsequent connections and retain the same pairing ID. The example waits for pairing/configuration responses and fails if no EEG callback arrives.
The earlier ZenLite 1.5.2 Linux libraries passed native loading, CFFI callback registration, and pairing-command generation on x86_64 and aarch64 on October 13, 2026. The current downloader installs ZenLite 1.5.5 for the host architecture and verifies both archive and library SHA-256 values. Use bash download-libs.sh all to prepare both architectures. Check the native library version when obtaining a package.
See linux/README.md for native library layout and runtime setup. Docker/Colima can check native loading, while physical Bluetooth pairing and EEG acceptance require an Ubuntu host and device. IMU/PPG workflows have not completed device validation.
On Windows, install the Microsoft Visual C++ 2015–2022 x64 Redistributable. The desktop downloader includes libuv and TensorFlow Lite runtime DLLs. The macOS downloader installs the published ZenLite 1.5.5 arm64/x86_64 mac-compat.zip native bundle, requires macOS 12.3 or later, and verifies its pinned SHA-256.
🚀 Getting Started
Hardware Connection Guide
Before starting your development, please check out our FAQ for details on turning the device on/off, understanding the LED indicators, and device pairing procedures across all platforms: Device Setup & FAQ.
System Requirements
The Python SDK is designed with cross-platform compatibility in mind. Ensure your host machine meets the following targets:
- Python Version: Requires Python 3.10 ~ 3.12 (Compatibility with older or newer versions has not been verified).
- Supported OS:
- macOS 12.3 or higher.
- Windows 10 build 10.0.15063 or higher.
- Ubuntu 22.04 (x86_64/aarch64, using the
python/linux/example.pyentrypoint).
Windows Environment Debugging Tips
Due to fragmentation of built-in Bluetooth modules on Windows, your network card's LMP (Link Manager Protocol) must be 11.x (Bluetooth 5.2) or higher. Otherwise, scanning and connections will fail. If your current PC does not natively support this, use a dedicated USB Bluetooth 5.0/5.2 adapter.
Installation & Integration
Integrating the Python SDK is straightforward. Simply configure the dependencies and use it directly.
Configure Dependencies
We recommend working inside a virtual environment (conda is preferred):
bash# Conda (Recommended) conda create -n my_env python=3.10 conda activate my_env # Or using standard venv python3 -m venv venv source venv/bin/activate # On Windows, use `venv\Scripts\activate`Install the necessary libraries:
bashpip3 install bleak>=0.20.0 # The core cross-platform BLE client pip3 install numpy>=1.21.0 # For fast array and math computations
(Note: If you plan on building a desktop GUI or data visualization charts, you might also want to install PyQt5 and pyqtgraph. Check out our open-source examples for a complete GUI setup).
Core API Reference
ZenLiteSDK (Class)
Global SDK configurations, logging, and device discovery. (The name ZenLite acts as a historical identifier to maintain backwards compatibility):
class ZenLiteSDK:
@staticmethod
def start_scan(callback):
"""Start scanning for nearby devices"""
pass
@staticmethod
def stop_scan():
"""Stop the scanning process"""
pass
@staticmethod
def set_log_level(level):
"""Set log level"""
passZenLiteDevice (Class)
Device proxy object to manage device status and send commands.
class ZenLiteDevice:
# --- Properties ---
uuid: str # Physical BLE address/UUID (OS-abstracted)
name: str # Bluetooth broadcast name
rssi: float # Signal strength (dBm)
in_pairing_mode: bool # Indicates if device is in pairing mode
connectivity: Connectivity # Connection state enum
battery_level: int # Battery percentage (0-100%)
hardware_revision: str # Hardware version
firmware_revision: str # Firmware version
# --- Connection Triggers ---
def set_listener(self, listener):
"""Set listener for data and state callbacks."""
pass
def connect(self):
"""Connect to the device."""
pass
def disconnect(self):
"""Disconnect the device."""
pass
def zl_pair(self, in_pairing_mode, callback):
"""Pair the device."""
pass
# --- Data Channel Constraints ---
def zl_config_afe(self, sample_rate, callback):
"""Set EEG sample rate."""
pass
def zl_config_imu(self, sample_rate, mode, callback):
"""Set IMU sample rate and mode."""
pass
def zl_config_ppg(self, report_rate, mode, callback):
"""Set PPG report rate and mode."""
pass
# --- Device Settings ---
def zl_set_device_name(self, name, callback):
"""Set Bluetooth broadcast name."""
pass
def zl_set_sleep_idle_time(self, seconds, callback):
"""Set device idle (not worn) auto-shutdown time (seconds)."""
passZenLiteDeviceListener (Class)
Override methods in this class to receive device events and data callbacks:
class ZenLiteDeviceListener:
# --- System and Connection Events ---
def on_connectivity_change(self, connectivity): pass
def on_device_info_ready(self, device_info): pass
def on_contact_state_change(self, contact_state): pass
def on_orientation_change(self, orientation): pass
def on_battery_level_change(self, battery_level): pass
def on_error(self, error): pass
# --- Sensor Data ---
def on_eeg_data(self, eeg_data): pass
def on_raw_eeg_data(self, raw_eeg): pass
def on_imu_data(self, imu_data): pass
def on_ppg_data(self, ppg_data): pass
# --- Algorithm Indicators ---
def on_brain_wave(self, brain_wave): pass
def on_attention(self, attention, weighted_attention): pass
def on_meditation(self, meditation, calmness, awareness): pass
def on_sleep_stage(self, stage, confidence, drowsiness): passData Models
EEGData (Brainwave Data)
class EEGData:
sequence_num: int # Sequence number
sample_rate: float # Sample rate
eeg_data: list[float] # Microvolts arrayBrainWave (Frequency Band Indicators)
class BrainWave:
delta: float # 0.5-4 Hz
theta: float # 4-8 Hz
alpha: float # 8-13 Hz
low_beta: float # 13-17 Hz
high_beta: float # 17-30 Hz
gamma: float # 30-50 HzIMUData (Inertial Measurement Unit Data)
class IMUData:
acc_data: ACCData # Accelerometer data
gyro_data: GyroData # Gyroscope data
euler_angle_data: EulerAngleData # Euler Angle data
sample_rate: float # Sample rate
head: ImuPoseHead # Head posture
body: ImuPoseBody # Body posture
class ACCData:
x, y, z: list[float] # Accelerometer vectors
class GyroData:
x, y, z: list[float] # Gyroscope vectors
class EulerAngleData:
yaw, pitch, roll: list[float] # Yaw, pitch, roll (-180~180)PPGData (Optical Heart Rate Data)
class PPGData:
sequence_num: int # Sequence number
sample_rate: float # Sample rate
raw_data: list[PPGRawData] # Raw optical readings
algo_data: list[PPGAlgoData] # Analysis indicators
respiratory_rate: float # Respiration rate
respiratory_curve: list[float] # Respiration curve
respiratory_state: RespiratoryState
class PPGAlgoData:
hr: float # Heart Rate (bpm)
hr_conf: int # HR Confidence (0-100%)
rr: float # Respiration rate
spo2: float # Blood Oxygen Saturation
spo2_conf: int # SpO2 Confidence (0-100%)
hrv: float # Heart Rate Variability
stress: float # Stress index
class PPGRawData:
green1_count, green2_count: int # Green light counts
ir_count, red_count: int # Infrared/Red countsEnums & Constants
Connectivity and Physical Contact
class Connectivity(IntEnum):
connecting = 0
connected = 1
disconnecting = 2
disconnected = 3
class ContactState(IntEnum):
unknown = 0
off = 1 # Not worn correctly
eeg = 2 # Good EEG contact
all = 3 # Both EEG and PPG have good contact
class Orientation(IntEnum):
unknown = 0
upward = 1 # Worn normally
downward = 2 # Worn upside downStates
class SleepStage(IntEnum):
unknown = -1
awake = 0 # Awake
rem = 1 # REM sleep
light = 2 # Light sleep
deep = 3 # Deep sleep
class RespiratoryState(IntEnum):
rest = 0 # Resting
inward = 1 # Inhaling
outward = 2 # ExhalingStream Configuration
class EEGSampleRate(IntEnum):
# Pass `1` to turn OFF the EEG stream
sr128 = 2 # 128 Hz
sr256 = 3 # 256 Hz
class IMUSampleRate(IntEnum):
# Pass `1` to turn OFF the IMU stream
sr25, sr50, sr100, sr200, sr400, sr800 = 2, 3, 4, 5, 6, 7 # (Hz)
class IMUMode(IntEnum):
# Pass `0` to set NONE
acc = 1 # Accelerometer only
gyro = 2 # Gyroscope only
acc_gyro = 3 # Both
euler = 4 # Computed Euler Angles
class PPGReportRate(IntEnum):
# Usually sr1 (param: 2) is used; HRV analysis may need sr50 (param: 5)
none, off, sr1, sr5, sr25, sr50, sr100 = 0, 1, 2, 3, 4, 5, 6
class PPGMode(IntEnum):
# Usually you'd select `algo = 2` to obtain human-readable outputs.
raw_data = 1 # Only optical arrays
algo = 2 # Algorithm indicators (HR, SpO2, HRV, etc.)
spo2, hr, hrv = 3, 4, 5 # Specific single-target computationsTroubleshooting & Best Practices
Connection Issues
If you're having trouble discovering or connecting to the device, be sure to check the global troubleshooting section: 👉 Device Setup & FAQ
Python Development Gotchas
- Thread Safety and GUI Updates: All data callbacks (e.g.,
on_eeg_data) run on background threads, so you must NEVER update UI components directly from callbacks. Doing so will cause instant crashes or deadlocks.- Solution: Always use thread-safe dispatch mechanisms provided by your framework (such as PyQt's
Signal.emit), or use Python's built-inqueue.Queueproducer-consumer model to pass data back to the main thread securely.
- Solution: Always use thread-safe dispatch mechanisms provided by your framework (such as PyQt's
- Memory Management: The hardware streams data at high frequency (especially 256 Hz EEG arrays). If you append all incoming data to a standard Python
list()without truncation, memory usage will grow unbounded. Use fixed-length Ring Buffers ornumpyrolling arrays instead.
📖 Complete Integration Example
We provide open-source reference implementations, including both a command-line data sampling example and a PyQt desktop app with real-time visualization.
See the full source code for asynchronous thread safety, data buffering, and lifecycle handling:
