TeslasuitDocumentation
Frameworks

FAQ

Frequently asked questions, organised by topic. For specific errors, see Troubleshooting. For concept deep-dives, see the Concepts pages.


Source, license and support#

Is RapidKit open source?#

Yes. RapidKit is released under the MIT license, copyright Deep Divers Ltd. See LICENSE.

Where is the source?#

On GitHub: github.com/teslasuit/RapidKit. The current version is 0.1.0 (alpha).

Is it on PyPI?#

Not yet. Install it from source for now: clone the repository and run pip install -e .. See Installation.

How do I contribute?#

See CONTRIBUTING.md. In short: branch off develop as feature/<short-description> and open the pull request against develop. main holds releases, develop holds pre-releases.

Where can I get help?#

For questions about RapidKit or the repository, contact Teslasuit support at support.teslasuit.io.


Getting started#

What's the smallest possible application?#

About 10 lines:

from teslasuit_rapidkit.control.strategy_base import ControlStrategyBase
from teslasuit_rapidkit.orchestrator import launch

class NoOp(ControlStrategyBase):
    def process(self) -> None:
        pass

if __name__ == "__main__":
    launch(NoOp)

This connects to the suit, runs the closed loop, and shuts down cleanly on Ctrl-C. No stimulation. See Step 1.

Do I need a GUI to run an application?#

No. Headless mode is fully supported. Just don't pass a gui_runner to launch(). The main process waits on the backend until Ctrl-C.

Can I run without Teslasuit hardware?#

No. SuitHandler.__init__() calls device_manager.get_or_wait_last_device_attached(), which blocks until a device appears. There is no mock / simulator path in the framework.

Why is the loop running at 100 Hz?#

Because the Teslasuit SDK's get_*_on_ready calls return at that rate. The framework's sample_rate parameter is metadata only — it labels LSL streams and informs diagnostics, but it does not regulate execution. See Design principle 1.


Strategy#

How do I add a new muscle?#

Two edits, in lockstep:

  1. Add the muscle to teslasuit_rapidkit/config/muscle_map_4R.json with its bone index and channel slice.
  2. Add a matching field to EmsData in teslasuit_rapidkit/data/types.py.

The framework will pick up the new muscle automatically; your strategy can write self.ems_output.<new_muscle> and the stimulator will route it.

What if my strategy needs to track state between cycles?#

Initialise the state in setup() (or __init__() after super().__init__()), update it in process():

class MyStrategy(ControlStrategyBase):
    def setup(self, muscles=None, suit=None, config=None):
        super().setup(muscles, suit, config)
        self._step_count = 0
        self._last_phase = None

    def process(self):
        if self.contacts.right_foot_contact != self._last_phase:
            self._step_count += 1
        self._last_phase = self.contacts.right_foot_contact

Can I have multiple strategies and switch between them?#

Not within one engine. The engine holds a single control_strategy reference. Patterns to consider:

  • Phase-based dispatch inside one strategy: branch on a state variable to call different methods.
  • Wrapper strategy: write a strategy that owns multiple sub-strategies and delegates to one of them based on state.
  • Multiple engine instances: usually overkill, but possible if the use cases are completely separate.

How do I implement a custom signal filter?#

Subclass DataStreamer and override process():

class FilteredStreamer(DataStreamer):
    def process(self):
        # Low-pass filter on right knee
        self.biomechanical_data.KneeFlexExtR = my_filter(
            self.biomechanical_data.KneeFlexExtR
        )

Pass it to the engine: data_streamer=FilteredStreamer(suit_handler). See DataStreamer.


Stimulation#

Why is my stimulation not firing?#

In order of likelihood:

  1. IsMuted=True. Defaults are muted; check your assignment.
  2. UtilityMessage.FesIsActive=False. Global kill switch.
  3. Amplitude / pulse width is 0. Defaults are 0; you have to set them.
  4. Strategy condition never fires. Print self.contacts.right_foot_contact etc. to confirm the input is what you expect.
  5. Hardware not enabled. Some Teslasuit Control Center versions need an explicit "stimulation enable" toggle.

See Troubleshooting → Stimulation not activating.

What's a safe starting amplitude?#

20–30%. Start there, increase by 5 per session, stop immediately if the user reports discomfort. Never run on someone you can't observe.

How do I ramp amplitude smoothly?#

Don't use time.sleep. Compute the next amplitude per cycle and write it:

def process(self):
    target = 50
    self._amp = min(self._amp + 1, target)        # +1 per cycle = ~5s ramp
    self.ems_output.quadriceps_right = EMSParamData(
        IsMuted=False, Amplitude=self._amp,
        PulseWidth=120, Period=20.0,
    )

Note: changing Amplitude between cycles destroys and re-creates the looped playable; for very smooth ramps consider keeping parameters constant and modulating IsMuted (PWM-like).


Calibration#

Should every application calibrate?#

If your strategy reads self.joints (any joint angle), yes. Calibration sets the I-pose reference; without it, joint angles drift and any threshold-based decision becomes unreliable.

If your strategy only reads self.contacts (foot contacts) or nothing at all, you can skip calibration.

How do I tune the quality threshold?#

self.calibration.QUALITY_THRESHOLD = 0.85    # stricter

Default is 0.7. For development iteration, lower it. For clinical sessions, push it higher.

Why does calibration sometimes fail?#

Most often the user wasn't actually in I-pose, or a suit segment is loose. quality.issues and quality.missing_sensors will tell you which.


LSL#

Why don't my LSL streams appear?#

  1. lsl_enabled=True? Default is False.
  2. Firewall. LSL uses UDP multicast.
  3. Network interface. LabRecorder and the application must be on the same network. Try same-machine first.
  4. Click Update in LabRecorder after starting the app — streams aren't auto-discovered while LabRecorder is idle.

Can I disable individual outlets?#

Not currently. The 7 framework outlets are all-or-nothing via lsl_enabled. Filter on the LabRecorder side by ticking only the streams you want.

How do I run two applications without their LSL streams clashing?#

Pass distinct source_id strings to the engine:

engine = ClosedLoopEngine(control_strategy=..., source_id="app_session_1")

Each outlet uses {source_id}_<suffix>, so different source_ids keep recordings separate.


Multiprocessing and GUI#

Why two processes?#

Python's GIL prevents true parallel execution. PyQt5 blocks regularly. Running the engine in the same process as the GUI risks GUI redraws delaying stimulation pulses. See Design principle 5.

My GUI shows no data — why?#

Most often the SharedRingBuffer name differs between processes. Check that both sides use the same name:

# backend
buffer = SharedRingBuffer(name="my_app_buffer", ...)
# GUI
adapter = DataAdapter(buffer_name="my_app_buffer", ...)

Also check that the backend creates the buffer in on_start() before the GUI tries to attach.

Can I use Tkinter / Electron / a web GUI instead of PyQt5?#

Yes. The framework's GUI integration is just multiprocessing.Queue + SharedRingBuffer. Use any GUI tech that can:

  • run in a separate process (or thread, if you accept the trade-offs),
  • read from multiprocessing.Queue,
  • read numpy structured arrays from shared memory.

Most non-Python GUIs (Electron, web frontend) connect via WebSocket or LSL — write a small Python adapter that bridges the two.


Production#

Can I package this as a standalone .exe?#

Yes. examples/elbow_flexion/main.spec is a PyInstaller spec that bundles the framework, the Teslasuit SDK and the GUI into a single Windows binary. Use it as the template for packaging your own application.

Is this ready for clinical use?#

No. The framework is for research and supervised demonstration. There is no medical-device certification, no electrical-isolation guarantee beyond Teslasuit's hardware, no regulatory pathway. See Safety.

How do I report a bug?#

Open an issue on github.com/teslasuit/RapidKit. Describe the problem, the expected behaviour, steps to reproduce, and whether it requires specific hardware.

  • Security vulnerabilities: do not open a public issue. Report them privately as described in SECURITY.md.
  • Hardware or stimulation safety matters: contact support@teslasuit.io, with a subject line noting that it is a hardware or safety matter.

Architecture and design#

Why is process() argument-less?#

So that adding new inputs (LSL inlets, PPG data, custom signals) doesn't break existing strategy signatures. Inputs are slot-based. See Design principle 2.

Why does ControlMessage start empty?#

Because the framework doesn't know what your application's parameters are. Subclassing it is intentional — it lets the LSL streamer auto-discover your fields and lets the IPC system pickle them generically.

Why is UtilityMessage not user-extensible?#

Because its fields are framework concerns (FES kill switch, calibration loop status). Adding application-specific flags to it would require the framework to know about them. Application flags go on your ControlMessage subclass.

What's the difference between Stimulator and LibraryStimulator?#

Stimulator handles the 20 standard muscles defined by EmsData (via MuscleMap). LibraryStimulator handles user-defined custom playables stored in a HapticLibrary subclass. Both run after every process(), in series. See Messaging — HapticLibrary.


See also#