System Architecture
Overview#
RapidKit uses a multi-process architecture to separate real-time data processing from the user interface. This design ensures that the critical control loop maintains consistent timing while the GUI can update at a different rate.
Architecture Diagram#
Process Architecture#
Main Process (main.py)#
The main process serves as the entry point and process manager:
- Initialization: Sets up Python environment and imports
- Queue Creation: Creates multiprocessing queues for IPC:
control_queue: GUI → Backend (stimulation parameters)utility_queue: GUI ↔ Backend (system state flags)
- Process Launch: Starts backend and GUI as separate processes
- Cleanup: Handles graceful shutdown of both processes
Backend Process (ClosedLoopEngine)#
The backend runs in a separate process to ensure real-time performance:
Main Loop (~100 Hz, hardware-paced):
DataStreamer.collect_data()— pull latest frames from Teslasuit SDK (blocking)DataStreamer.process()— optional user override for custom sensor processingDataStreamer.distribute_data()— push toSharedRingBuffer+LSLStreamerControlStrategyBase.run_strategy()— update strategy inputs, callprocess(), apply EMS outputQueueHandler.check_queues()— handle incoming messages from GUI
Key Components:
- SuitHandler: Teslasuit connection, haptic/EMS subsystem, muscle map
- DataStreamer: Three-phase collect → process → distribute cycle
- Data Types: Python dataclasses populated by
DataStreamereach cycle - ControlStrategyBase: User-implemented
process()method — reads inputs, writesems_output - Stimulator: Translates
EmsData→ Teslasuit SDK EMS calls (usesMuscleMap) - LSLStreamer: Streams 7 LSL outlets to external lab equipment
- SharedRingBuffer: Circular shared-memory buffer for GUI data feed
GUI Process (MainWindow)#
The GUI runs in the main process (PyQt5 requirement):
Update Loop (30 Hz):
- Read latest data from shared memory buffer
- Update visualizations (plots, graphs, statistics)
- Handle user input (parameter changes, mode switches)
- Send control/utility messages to backend
Key Components:
- MainWindow: Main application window with tabs
- DataHandler: Manages shared memory buffer connection
- OverviewTab: Main control interface with leg panels
- SensorDataTab: Bilateral sensor data visualisations
- GaitStatisticsTab: Gait phase statistics
Data Architecture#
Data Flow Hierarchy#
TeslaSuit Hardware
↓ (via WiFi / Teslasuit Control Center)
SuitHandler (fes_framework/io/suit_handler.py)
↓ (raw ctypes frames from SDK)
DataStreamer (fes_framework/io/data_streamer.py)
↓ (parsed Python dataclasses)
Data Types (fes_framework/data/types.py)
↓ (typed inputs to ControlStrategyBase)
ControlStrategyBase.process()
↓ (EmsData written to self.ems_output)
Stimulator (fes_framework/io/stimulator.py)
↓ (SDK EMS channel calls)
TeslaSuit Hardware ← stimulation appliedSuitHandler#
Location: fes_framework/io/suit_handler.py
Purpose: Thin interface layer to Teslasuit hardware
Responsibilities:
- Connects to Teslasuit via Teslasuit Control Center (WiFi)
- Manages SDK connection lifecycle
- Exposes mocap, haptic, and PPG subsystems
- Holds the
MuscleMapfor channel lookup - Starts/stops data streaming
DataStreamer#
Location: fes_framework/io/data_streamer.py
Purpose: Three-phase sensor data cycle: collect → process → distribute
Responsibilities:
collect_data(): pulls raw ctypes frames from SuitHandler SDK; parses into dataclassesprocess(): user-overridable hook for custom sensor processing (default: no-op)distribute_data(): pushes populated dataclasses toSharedRingBufferandLSLStreamer- Populates four data types each cycle:
BiomechanicalData(29 joint angles)ProcessedData(skeleton bone positions/rotations)StepDetectorData(foot contact states)RawData(IMU sensor readings)
Data Structures (Dataclasses)#
Location: fes_framework/data/types.py
Purpose: Typed, organized data containers used throughout the system
Key Dataclasses:
BiomechanicalData#
- 29 joint angles (pelvis, hips, knees, ankles, shoulders, elbows, wrists)
- All angles in degrees
- Organized by anatomical joint
ProcessedData#
- 20 skeleton bone positions and rotations
- 3D position + quaternion rotation per bone
- Organized by bone name
RawData#
- Raw IMU sensor data from 20 body segments
- Accelerometer, gyroscope, orientation per sensor
- Organized by body segment name
StepDetectorData#
- Left and right foot contact states (boolean)
- Organized for easy access
EmsData#
- Per-muscle stimulation parameters (20 muscle groups)
- Nested structure: EmsData → EMSParamData per muscle
- Organized by muscle group name
ControlMessage#
- Empty base dataclass — subclass to define application-specific fields
- Per-muscle stimulation parameters (defined in subclass, e.g. WalkingControlMessage)
- Timing parameters (defined in subclass, e.g. stance/swing windows)
- Organized for control strategy consumption
UtilityMessage#
- FES enable state (FesIsActive)
- Recording state
- Calibration state
- Step detection mode (TS API, VU, model-based)
- Folder path
- Organized for system state management
Data Structure Flow:
- Initialized in
ClosedLoopEngineusing factory functions frominit_utils.py - Passed to
DataStreamerat construction - Populated in-place by
DataStreamer.collect_data()each cycle - Used by all backend components
- Written to
SharedRingBufferandLSLStreamerviaDataStreamer.distribute_data()
Inter-Process Communication (IPC)#
Shared Memory Ring Buffer#
Purpose: High-performance, zero-copy data streaming from backend to GUI
Implementation:
- Location:
fes_framework/ipc/buffer.py - Type: Circular buffer in shared memory
- Capacity: configurable (default 500 frames; application can set higher for longer plot history)
- Data Structure: Application-defined numpy dtype (set by the
ClosedLoopEnginesubclass)
Data Flow:
Backend (Writer) → Shared Memory → GUI (Reader)
Frame Structure:
- Timestamp (float64)
- 29 biomechanical joint angles (float64)
- Step detection flags (uint8)
- EMS parameters per muscle (3×float32 per muscle)
Message Queues#
Purpose: Command and control messages from GUI to backend
Control Queue (control_queue):
- Application-specific stimulation parameters (defined by ControlMessage subclass)
- Muscle stimulation parameters (frequency, amplitude, pulse width, timing)
- Sent when user changes parameters in GUI
Utility Queue (utility_queue):
- Recording start/stop
- Calibration requests
- Step detection mode selection
- Folder path for data storage
Implementation:
- Location:
fes_framework/ipc/queue_handler.py - Type:
multiprocessing.Queue - Non-blocking reads in backend loop
Component Relationships#
Backend Data Flow#
Component Dependencies#
- SuitHandler: Independent, wraps SDK hardware interface
- DataStreamer: Depends on SuitHandler (reads raw frames)
- Data Types: Independent dataclasses, populated by DataStreamer
- ControlStrategyBase: Reads Data Types; writes
EmsDatato Stimulator - Stimulator: Depends on
EmsDataand SuitHandler - LSLStreamer: Reads all populated Data Types
- SharedRingBuffer: Reads populated Data Types (via DataStreamer)
LSL Streaming Architecture#
LSLStreamer provides 7 LSL outlets, all disabled by default (enabled via
lsl_enabled=True in ClosedLoopEngine or orchestrator.launch()):
| Stream name | Channels | Content |
|---|---|---|
TS_Biomechanics | 29 | Joint angles (degrees) |
TS_StepDetector | 2 | Left/right foot contact (bool) |
TS_EMSParameters | 80 | EMS params for 20 muscles × 4 fields |
AppData_ControlMessage | variable | Runtime strategy parameters |
AppData_UtilityMessage | variable | System state flags |
TS_BonePosition | 140 | Skeleton bone positions/rotations |
TS_RawData | variable | Raw IMU sensor readings |
All streams share the same LSL source ID and are synchronized via LSL's global clock.
Error Handling and Resilience#
Backend Resilience#
- Continues operation if GUI disconnects
- Handles TeslaSuit connection errors gracefully (via Control Center)
- Validates data before processing
- Logs errors without crashing
- Reconnects to TeslaSuit via Control Center on connection loss
GUI Resilience#
- Handles backend disconnection (shows mock data)
- Retries shared memory connection
- Validates user input before sending
- Graceful degradation if data unavailable
Process Management#
- Backend process can be restarted independently
- GUI cleanup on window close
- Proper resource cleanup on shutdown
- Timeout handling for process termination
Performance Considerations#
Backend Loop Timing#
- Target: 10 ms per iteration (100 Hz)
- Critical path: Data acquisition → Data organization → Step detection → Control → Stimulation
- LSL streaming and shared memory writes are non-blocking
GUI Update Rate#
- Target: 33 ms per iteration (30 Hz)
- Only active tab updates continuously
- Biomechanics tab updates only when visible (performance optimization)
Memory Management#
- Shared memory buffer: ~10 seconds of data
- Circular buffer prevents memory growth
- Old data automatically overwritten
- Dataclasses are updated in-place (no copying)
CPU Usage#
- Backend: Single-threaded, CPU-bound
- GUI: Event-driven, mostly idle
- ML model: Can use GPU if available
Security Considerations#
- No network exposure (LSL is local network only)
- No user authentication (local application)
- File system access limited to data directory
- No external API calls
Related documentation#
- Concepts overview — concept-level tour with the same diagram
- IPC and processes — multi-process model and queue details
- LSL streaming — outlets and inlets in detail
- API Reference — complete class/method signatures
