TeslasuitDocumentation
Frameworks

Troubleshooting

Solutions for common issues when developing and running RapidKit applications.


Installation Issues#

ModuleNotFoundError: No module named 'RapidKit'#

Cause: Package not installed, or wrong Python environment.

Fix:

# Activate your virtual environment first, then:
pip install -e .   # from the project root

Verify: python -c "import RapidKit; print('OK')

ModuleNotFoundError: No module named 'teslasuit_sdk'#

Cause: teslasuit_sdk/ directory is not on the Python path.

Fix: Either use pip install -e . (which adds the project root to the path), or set PYTHONPATH manually:

# PowerShell
$env:PYTHONPATH = "C:\path\to\fes_framework"

ModuleNotFoundError: No module named 'pylsl'#

Fix:

pip install pylsl

pip install PyQt5 fails#

Try pinning a stable version:

pip install PyQt5==5.15.10

Hardware Connection Issues#

Application hangs on startup / hardware init timeout#

Cause: Teslasuit not detected.

Checklist:

  1. Is Teslasuit Control Center open and running?
  2. Is the suit powered on (LED indicators lit)?
  3. Are the suit and your computer on the same WiFi network?
  4. Does Control Center show the suit as connected (green indicator)?

Fix: Restart Control Center and re-power the suit. Try increasing the hardware init delay:

launch(MyStrategy, hardware_init_delay=10.0)

TimeoutError: LSLInlet: Stream 'X' not found within 5.0s#

Cause: External LSL stream not discovered.

Fix:

  • Verify the external device is actively publishing (check with LabRecorder).
  • Ensure both devices are on the same network / localhost.
  • Increase the timeout:
    ext.add("StreamName", timeout=15.0)

Calibration Issues#

Calibration returns success=False#

Cause: Suit not fully streaming, or SDK timeout.

Fix:

  1. Ensure the suit is streaming (check Control Center).
  2. Wait a few seconds after connecting before triggering calibration.
  3. Retry calibration once.

Strategy / Runtime Issues#

AttributeError: 'NoneType' object has no attribute 'X' in process()#

Cause: Accessing self.params before it is set (first cycle).

Fix: Guard with if self.params is not None: or initialise in setup():

def setup(self, muscles=None, config=None) -> None:
    super().setup(muscles, config)
    # Do not access self.joints or self.contacts here — they are not set yet

Stimulation not activating#

Checklist:

  1. Is IsMuted set to False on the EMSParamData?
  2. Is UtilityMessage.FesIsActive set to True?
  3. Are Amplitude, PulseWidth, and Period all non-zero?
  4. Is the strategy actually being called? Add a print in process() to verify.
  5. Is the condition in process() evaluating to True? Print the input values.

process() not being called#

Cause: Engine not running, or strategy not passed correctly.

Fix: Ensure ControlStrategyBase is the last in the MRO for multiple-inheritance strategies:

class MyStrategy(MyMixin, ControlStrategyBase):  # ControlStrategyBase last
    ...

Strategy runs but joint angles are all 0.0#

Joint-angle collection runs by default — self.joints.* should refresh every cycle out of the box. If you see a flat-line plot, check these in order:

  1. Did something disable biomech collection? The most common cause. DataStreamer.set_biomech_collection(False) (or the equivalent UtilityMessage.BiomechanicalDataCollectionIsActive = False) skips the SDK IK call entirely, leaving self.joints.* at its zero- initialised values. Search your app for those calls and re-enable them, or simply leave the default. If you intentionally disabled it for performance, re-enable it whenever you do need joint angles:

    self.data_streamer.set_biomech_collection(True)
  2. Mocap not streaming. Verify the suit is streaming in Teslasuit Control Center.

  3. Calibration not performed. Call engine.calibration.calibrate() before engine.run(). (Without calibration the IK solver runs but the angles can be inaccurate.)

  4. DataStreamer.run_cycle() raising silently. Check the log for exceptions.


GUI Issues#

GUI appears but shows no data / plots are flat#

Cause: SharedRingBuffer not created, or name mismatch between processes.

Fix:

  1. Backend creates the buffer in on_start() — ensure this hook is called.
  2. Buffer name must be identical in backend and GUI processes.
  3. The GUI attaches with create=False — it must attach after the backend creates it. Increase hardware_init_delay if needed.

GUI controls don't affect strategy behaviour#

Cause: ControlMessage subclass not wired to the engine.

Fix: Override __init__ in your engine subclass to replace the default message:

class MyEngine(ClosedLoopEngine):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.control_message = MyControlMessage()   # replace default

And pass the same subclass to QueueHandler in the GUI:

handler = QueueHandler(..., control_message=MyControlMessage())

RuntimeError: QApplication must be created before QPaintDevice#

Cause: GUI components instantiated outside of gui_runner.

Fix: Create QApplication inside gui_runner() (not at import time).


LSL Issues#

No LSL outlets appear in LabRecorder#

Cause: LSL not enabled, or LabRecorder and the application are on different network interfaces.

Fix:

  1. Confirm lsl_enabled=True is passed to launch() or ClosedLoopEngine.
  2. Check LabRecorder's "Update" button — outlets are created at startup.
  3. Try running LabRecorder on the same machine as the application first.

LSL outlet data looks wrong / duplicated channels#

Cause: Multiple engine instances running simultaneously.

Fix: Use a unique source_id per instance:

engine = ClosedLoopEngine(..., source_id="my_app_session_1")

Performance Issues#

Control loop running slower than 100 Hz#

Cause: Heavy computation in process(), or DataStreamer.process() override taking too long.

Fix:

  1. Profile your process() — avoid loops over large arrays, file I/O, or blocking calls.
  2. Move non-critical processing to on_cycle_complete() (still in the loop, but post-stimulation).
  3. Use cycle_count % N to run expensive logic less frequently:
    def on_cycle_complete(self) -> None:
        if self.cycle_count % 10 == 0:    # every 10 cycles = 10 Hz
            self._update_statistics()

GUI freezing or lagging#

Cause: GUI doing too much work in the update timer callback.

Fix: Keep the 30 Hz timer callback lightweight — only read buffer and update plots. Move any heavy processing to a background QThread.