Project anatomy
The shape of an RapidKit application — what files you
typically write, where each lives, and how they fit together.
A typical headless application#
my_fes_app/
├── app/
│ ├── __init__.py
│ ├── strategy.py # ControlStrategyBase subclass — your algorithm
│ └── messages.py # MyControlMessage subclass — your parameters
└── main.py # orchestrator.launch(MyStrategy)That's all you need for a working closed-loop application. No GUI, no IPC code, no hardware boilerplate.
A typical GUI application#
my_fes_app/
├── app/
│ ├── __init__.py
│ ├── strategy.py
│ ├── messages.py
│ ├── engine.py # ClosedLoopEngine subclass — calibration gate, on_cycle hook
│ └── gui/
│ ├── __init__.py
│ ├── main_window.py # PyQt5 MainWindow
│ └── widgets/
│ ├── controls.py # operator sliders / toggles
│ └── plots.py # real-time data plots
└── main.py # orchestrator.launch(...)Both examples/elbow_flexion/ and
examples/walking_fes/ follow this layout. Use
either as a template.
What each file does#
app/strategy.py#
Your ControlStrategyBase subclass. The closed-loop algorithm
itself — read self.joints, write self.ems_output. No
multiprocessing, no Qt, no SDK calls.
This is the file you'll edit the most.
app/messages.py#
Your ControlMessage subclass — the runtime parameters your
operator can adjust. Plain dataclass fields with sensible defaults.
@dataclass
class MyControlMessage(ControlMessage):
threshold_deg: float = 15.0
quad_amplitude: int = 30The fields here become channels in the AppData_ControlMessage
LSL outlet (when LSL is enabled) and become rows in the GUI.
app/engine.py#
Optional. A ClosedLoopEngine subclass when you need lifecycle
hooks: calibration gate (on_start), shared-memory writes
(on_cycle_complete), reactions to messages (on_control_message,
on_utility_message), or custom shutdown (on_stop).
If your application doesn't need any hooks, skip this file and
pass the strategy directly to launch().
app/gui/#
PyQt5 widgets when present. A typical layout:
main_window.py— the top-levelQMainWindowsubclass; owns tabs, menus, the Qt event loop entry point.widgets/— leaf widgets (sliders, plots, status indicators). Many will subclassfes_framework.gui.base_widget.FesWidgetfor the standard data-update pattern.
The GUI process talks to the backend via:
control_queue(it pushesMyControlMessage),utility_queue(bidirectionalUtilityMessage),SharedRingBuffer(it pulls live sensor frames at 30 Hz).
main.py#
The launcher. Always tiny — just launch(...) with the right
arguments.
from fes_framework.orchestrator import launch
from app.strategy import MyStrategy
from app.engine import MyEngine
from app.gui.main_window import gui_main
if __name__ == "__main__":
launch(
MyStrategy,
engine_class=MyEngine, # optional
gui_runner=gui_main, # optional
lsl_enabled=True, # optional
external_input_streams=[...], # optional
)What you do not write#
You never write code for any of the following — the framework owns them:
- Hardware connection / SDK initialisation
- Frame parsing (ctypes → dataclasses)
- Channel ID lookup (
MuscleMapdoes it) - Looped haptic playable management
- LSL outlet creation
- Process forking / IPC plumbing
- Cleanup on shutdown
If you find yourself writing any of this, look at the relevant concept page — there's almost certainly a framework hook for it.
Putting it on disk#
For a new application:
- Copy
examples/elbow_flexion/to a new directory. - Rename
elbow_*files to your application name. - Replace the strategy logic in
<your>_control_strategy.py. - Adjust
<your>_types.py(control message, shared-memory dtype). - Edit the GUI tabs to expose your application's parameters.
- Test by running
python -m my_fes_app.main.
There is no scaffold generator (RapidKit new my_app) yet.
Copy-paste from an example is the supported workflow.
When to grow beyond this layout#
For most research applications the layout above is enough. Consider splitting further when:
- The strategy file exceeds ~500 lines — split utility classes
into
app/utils/. - You have multiple operators with different needs — give each
one its own GUI module under
app/gui/operator_x/. - You're shipping the application as an executable — add a
PyInstaller
.specfile alongsidemain.py. Seeexamples/elbow_flexion/main.specfor an example.
See also#
- Installation — setting up the environment
- Quickstart — 10-minute walkthrough
- Implementation Guide — the 9-step build
- Examples — real applications using this layout
