1 Directory Structure
PLAIN |
2 Startup Process

3 Configuration Loading Chain
assets/config/<robot>/mode.yaml is the entry point of the configuration system, for example:
YAML |
The loading logic can be understood as:
- First select
sim/robotbased onactive_mode - Establish the
tag -> scopemapping (e.g.pd_stand -> pd_stand/default) BasicParam(tag)findsscopebased ontag- Use
LOAD_PARAM(xxx)in the parameter class to read fields from the corresponding YAML

4 Task and Runner
4.1 Concepts and Relationships
- Task
- It is a scheduling unit that defines threads, periods, priorities, and which Runners to execute (
runner[]). - There are two types of Task in configuration:
task_resident/*.yaml: resident tasks, run in parallel over the long term after startup.task_motion/*.yaml: motion tasks, only one motion task is active at a time, switched via keys or automatic transition.
- It is a scheduling unit that defines threads, periods, priorities, and which Runners to execute (
- Runner
- It is an algorithm execution unit; each Runner is responsible for a specific piece of logic (e.g. estimation, control, actuation).
- The relationship can be understood as:
Task(scheduling shell)->Runner(business logic); Task drives the Runner lifecycle interfaces periodically.
4.2 Runner Lifecycle and State Transitions
BasicRunner(core/include/basic/basic_runner.h) defines a unified lifecycle:Initialize(): initialization after construction (resources, default state)Enter(): called when entering the current task (parameter loading, initial value preparation)Run(): periodically executes the main logicTryExit(): exit attempt stage (can execute reset/convergence logic over multiple cycles)Exit(): performs one-time cleanup after confirming exit (variable reset/initialization preparation)End(): cleanup at the end of the lifecycle
MotionRunner(core/include/basic/motion_runner.h) adds control-related interfaces on top of the above lifecycle:SetupContext()/TeardownContext(): set and restore the global context when entering/exitingGetMutableOutput().SetCommand(...): write to the control output cacheGetOutput(): read the current output cache
- Typical call sequence (
Initialize()is only called when the runner is constructed):- Runner of
resident task:Enter -> Run -> TryExit -> Exit -> End - Runner of
motion task:SetupContext -> Enter -> Run -> TryExit -> Exit -> TeardownContext -> End
- Runner of
TryExit()vsExit():TryExit(): tends toward continuous reset, suitable for "needs time to complete" exit processes (e.g. planning the arm back to origin); may be called in a loop, and reports completion viakTrying/kCompleted.Exit(): tends toward one-time processing, usually performs variable reset, state cleanup, and pre-switch cleanup.
- Common states and transition semantics:
RunnerState:kRunning/kTryExit/kFaultTransitionState:kCompleted/kTrying/kFailed/kRestoreRunning
- The state machine can be understood as follows:
- Running stage:
kRunning(continuously callsRun()) - Trigger exit:
kTryExit(entersTryExit()) - Exit result:
kCompleted: executesExit()and completes the transitionkTrying: remains in theTryExit()stagekRestoreRunning: returns tokRunningkFailed: enters fault/failure handling (usually taken over by the upper layer)
- Running stage:
5 RL Walking Policy Deployment Code Analysis
This section takes RlWalkingExampleRunner as an example to provide an in-depth analysis of how the RL-based walking policy is deployed in the real-robot framework. This Runner inherits from MotionRunner and strictly follows the execution paradigm of "data acquisition - logic computation - cached output". The overall deployment logic can be broken down into the following three core modules: global parameter configuration, system input source acquisition, and system output with cache arbitration.
5.1 Global Parameter Configuration and Model Initialization
Before entering periodic control, the system needs to complete the configuration of low-level mechanism switches, the dynamic loading of configuration parameters, and the instantiation of the neural network model.
- **Global control context setup (SetupContext)**For RL policies trained with serial joints, you need to manually set
parallel_by_classic_parserto False
C++ |
Ankle joint control


For non-ankle joint control modes, the MIT control mode is used. For ankle joint control modes, there are currently two control modes:
- Serial-joint MIT mode commands to parallel-joint MIT mode commands: you need to specify
parallel_by_classic_parser=True(e.g., PD standing mode). The user only needs to provide the position, velocity, and feedforward torque commands for the serial joints, which are automatically converted into the motor's position, velocity, and feedforward torque commands. Kp and Kd retain the serial-joint values and are not converted; - Serial-joint torque mode commands to parallel-joint torque mode commands: you need to specify
parallel_by_classic_parser=False(e.g., RL walking mode). The user only needs to provide the position, velocity, and feedforward torque commands for the serial joints. The total torque command of the serial joints is automatically converted into the motor's torque command, and the Kp and Kd sent to the motors are set to 0.
Note:
parallel_by_classic_parserdefaults to True. For an RL policy trained with serial joints, you need to manually setparallel_by_classic_parserto False;- Currently, the ankle joint only supports the above two control modes for converting serial-joint commands to parallel-joint commands, and does not support sending commands directly to the ankle motor layer (if you implement the serial-parallel resolution yourself, please contact us).
- Parameter file reading and dynamic loadingWhen the Runner is constructed, the default configuration is obtained via
ParamManager. When the state machine triggers a task switch and enters theEnter()stage, the system validatesparam_tag_. This design allows the robot to dynamically reload configuration files of different tags (e.g. flat-ground walking, stair climbing, and other gait parameters) without stopping, achieving highly flexible scheduling.
C++ |
- Inference engine and model loadingThe system uses
math::MNNModelas a lightweight neural network inference engine. By combining the global environment path with the relative path specified in the parameter file, it precisely locates and loads the.mnnmodel file.
C++ |
5.2 System Input and Proprioception
During the periodic execution of Run(), the Runner needs to extract the robot's physical state (proprioception) and external interaction commands at high frequency from the global data pool (data_store_) to build the observation space for the network.
- **Joint state information (Joint State)**Accurately obtain the actual position
q_real_and actual velocityqd_real_of each joint. The underlying architecture supports obtaining more dimensions of state (such as feedforward torque, stiffness, damping, etc.) via theJointInfoTypeenum class, meeting the needs of different policies.
C++ |
- **Inertial measurement unit information (IMU State)**Obtain the body quaternion and angular velocity. To eliminate installation errors and unify the data into the robot base coordinate system, the code performs standard coordinate system rotation transformations and computes the gravity projection vector used to represent the body posture.
C++ |
- **User remote command (Remote Command)**Parse the gamepad input data and convert it into desired linear and angular velocities combined with scaling factors of different dimensions. To prevent sudden command changes from impacting the model, the system introduces a first-order low-pass filter (lpf_command_) for signal smoothing.
C++ |
5.3 Command Cache and Global Arbitration Dispatch
The actions computed by the algorithm cannot directly pass through to the hardware; they must follow the framework's caching and unified scheduling mechanism to ensure the safety and strict timing of robot control commands.
- Write to local output cacheAfter completing forward inference, the Runner uses the
GetMutableOutput().SetCommand(...)interface to write the desired position (q_des_), desired velocity (qd_des_), feedforward torque (tau_ff_des_) and the underlying PD gains into its own dedicated cache.
C++ |
- Task-level integration and synchronized dispatchThe core of this architecture design is data bus decoupling. The Task Manager plays the role of an "arbiter" at the end of each control cycle. It uniformly collects the local caches of all active Runners, processes possible command interpolation (smooth transition), singularity checks, and torque limits. After confirming safety, it strictly synchronizes the packaged complete data frame to each underlying motor according to the bus communication cycle.
6 Control Input and ROS2 Integration
Native SDK provides a unified input and external data interface mechanism for receiving control commands and exchanging data with external systems. Overall, it can be divided into two categories:
6.1 1) Control Input
The control input of Native SDK is uniformly handled by runner/input_command_arbiter.
Different input devices must first be wrapped as corresponding Adapters, and then registered into `InputCommandArbiterRunner` to participate in input source management.
The current input source registration location is as follows:
C++ |
Two types of input sources are currently built in:
gamepad: physical gamepad inputvirtual_gamepad: virtual gamepad input
To extend a new input method (such as keyboard, network control, custom remote control, etc.), the following steps are usually required:
- Add the corresponding Input Adapter
- Responsible for converting external input into the framework's internal unified control input format
- Register the Adapter in
InputCommandArbiterRunner- Access the input source arbitration flow via
RegisterInputSource(...)
- Access the input source arbitration flow via
- Set priority and switching strategy as needed
- Input sources participate in priority management according to registration order (the current convention in code comments is from low to high priority)
This allows the new input device to be integrated into the existing system without modifying the upper-level control logic.
6.2 2) ROS2 Integration
For Native SDK, ROS2 is an independent plug-in communication module that bridges ROS2 data with Native SDK via ros2_bridge.
To extend ROS2 functionality, the following types of modifications are usually involved:
- Add a new ROS2 Node
- Add a new node implementation in
src/ros2_node
- Add a new node implementation in
- Add Topic
- Add the corresponding publish / subscribe topic in the existing node
- Add Msg
- Add the corresponding msg definition in
src/protocol
- Add the corresponding msg definition in
After adding the new code, it also needs to be explicitly activated in the ROS2 Bridge configuration.
Example of configuration file location:
Bash |
Example configuration is as follows:
YAML |
The extension rules are as follows:
- If adding a node
- You need to add the corresponding node name in
activated_node - Also supplement the node's configuration items
- You need to add the corresponding node name in
- If adding a topic
- You need to add the topic name and mapping relationship under
publish_topicsorsubscribe_topicsin the corresponding node configuration
- You need to add the topic name and mapping relationship under
- If adding a msg
- You first need to add the message definition in
src/protocol, then implement the message send/receive logic and configuration mapping in the node
- You first need to add the message definition in
