TeslasuitDocumentation
Frameworks

Step 1 — First strategy

Goal: get a closed-loop running with the smallest possible custom strategy. No stimulation yet. You'll use: ControlStrategyBase, orchestrator.launch() Builds on: a clean install (verified with python -c "import RapidKit; print('OK')") Anchored example: examples/atomic/minimal_closed_loop.py


What you're adding#

Everything. This is where you go from "the framework is installed" to "I have a closed-loop control system running." The strategy you write here doesn't make the suit do anything — it just counts cycles and prints periodically — but it exercises the full framework: hardware connection, data acquisition, the per-cycle pipeline, IPC scaffolding, and clean shutdown.

Two ingredients: a ControlStrategyBase subclass (your code) and a call to orchestrator.launch() (the framework's standard launcher). Subsequent steps will build on this exact shape.


Code (full, copy-pasteable)#

Save as app/strategy.py and app/main.py in your project:

# app/strategy.py
from fes_framework.control.strategy_base import ControlStrategyBase


class MyStrategy(ControlStrategyBase):
    """Smallest possible strategy: count cycles, emit no stimulation."""

    def __init__(self) -> None:
        super().__init__()
        self._cycles = 0

    def process(self) -> None:
        # Every cycle, the framework has populated:
        #   self.joints, self.contacts, self.params, self.external_data, self.muscles
        # We don't read them yet. We don't write to self.ems_output, so no
        # stimulation is emitted.
        self._cycles += 1
        if self._cycles % 100 == 0:        # ~once per second at 100 Hz
            print(f"[MyStrategy] cycle {self._cycles}")
# app/main.py
from fes_framework.orchestrator import launch
from app.strategy import MyStrategy

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

Run it:

python -m app.main

(Or, if you'd rather have a single file: copy examples/atomic/minimal_closed_loop.py verbatim and run it directly.)


Walkthrough#

class MyStrategy(ControlStrategyBase) — the framework's only required extension point. Subclassing ControlStrategyBase and implementing process() is the entire contract for "I have a strategy."

super().__init__() — initialises the input/output slots (self.joints, self.contacts, self.ems_output, etc.) to safe defaults. Always call it.

process() — called every control cycle (~100 Hz, governed by hardware). The framework populates self.<input> slots before each call and reads self.ems_output after. We don't touch any of them this step.

launch(MyStrategy) — the framework's reference orchestrator. It:

  • forks a backend subprocess running ClosedLoopEngine.run() against your strategy,
  • handles Ctrl-C and signals the backend to shut down cleanly,
  • (when configured) wires up the GUI process. None here, so it just waits.

if __name__ == "__main__": — required for any multiprocessing-based Python program on Windows. Without it the subprocess re-imports your script and fork-bombs.


Verify#

Expected console output:

[Orchestrator] Starting backend process…
[Backend] Initialising engine (hardware auto-detected)…
Suit is connected.
[Backend] Starting engine (LSL OFF)
[Orchestrator] Running headless (Ctrl-C to stop)
[MyStrategy] cycle 100
[MyStrategy] cycle 200
[MyStrategy] cycle 300

If you see [MyStrategy] cycle N lines incrementing once per second, the closed loop is running. The suit isn't doing anything yet — that's the next step.

Press Ctrl-C to stop. You should see:

[Orchestrator] Caught Ctrl-C — stopping backend
ClosedLoopEngine stopped after 1432 cycles
[Backend] Engine stopped.

If the cycle counter isn't incrementing, hardware probably isn't connecting. Confirm:

  • Teslasuit Control Center is running.
  • The suit is powered on (LEDs lit).
  • Suit and computer are on the same WiFi network.
  • The suit appears as connected in Control Center.

If still stuck, see Troubleshooting.


What you've learned#

  • The minimum viable strategy is one class with one method.
  • The framework runs in a backend subprocess; Ctrl-C shuts it down cleanly.
  • The control loop is hardware-paced — no time.sleep, no software clock.

Next#

Step 2 — Reading sensor data. You'll start reading joint angles and foot contacts in process().