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-Cshuts 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().
