webots-controller-programming
Webots Controller Programming
Use this skill to implement and debug standard robot controllers in Webots. Focus on the controller process model, control-loop timing, device lookup, sensor/actuator data flow, and language-specific setup. Treat the simulation loop as the primary control structure.
Load references/controller_api_reference.md when exact API signatures, language mappings, Makefile templates, or termination semantics are needed.
Follow the Controller Architecture Model
- Run each controller as a separate process from the Webots simulator process.
- Bind exactly one controller to each Robot node through the Robot field
controller. - Place controller source in
controllers/<controller_name>/. - Match file naming to controller naming conventions per language.
Typical structure:
<project>/
worlds/
demo.wbt
controllers/
my_controller/
my_controller.py
# or my_controller.c, my_controller.cpp, my_controller.java, my_controller.m
Lifecycle rules:
- Start controller process when simulation starts or when controller reload occurs.
- Initialize controller runtime (
Robot()in Python,wb_robot_init()in C) before any device call. - Keep process alive by repeatedly calling the simulation step function.
- Terminate cleanly when step returns
-1.
Implement the Simulation Loop First (Critical Pattern)
Treat the following loop as mandatory scaffolding before adding robot behavior.
Python:
from controller import Robot
robot = Robot()
timestep = int(robot.getBasicTimeStep())
while robot.step(timestep) != -1:
# read sensors
# process
# write actuators
pass
C:
#include <webots/robot.h>
int main() {
wb_robot_init();
int timestep = (int)wb_robot_get_basic_time_step();
while (wb_robot_step(timestep) != -1) {
// read sensors
// process
// write actuators
}
wb_robot_cleanup();
return 0;
}
Execution semantics:
- Call
robot.step(timestep)/wb_robot_step(timestep)once per control iteration. - Read fresh sensor data only after a successful step call.
- Apply actuator commands before the next step boundary.
- Stop loop when step returns
-1(simulation ended or controller terminated). - Use a control step that is a multiple of
WorldInfo.basicTimeStep.
Use the Device Access Pattern
- Retrieve device handles by name using
robot.getDevice("name")orwb_robot_get_device("name"). - Match names exactly with device
namefields defined in.wbtor.proto. - Enable sensors with a sampling period before reading values.
- Skip enable for actuators; issue commands directly.
Python example:
from controller import Robot
robot = Robot()
timestep = int(robot.getBasicTimeStep())
distance = robot.getDevice("front_ds")
left_motor = robot.getDevice("left wheel motor")
right_motor = robot.getDevice("right wheel motor")
distance.enable(timestep)
left_motor.setPosition(float('inf'))
right_motor.setPosition(float('inf'))
C example:
#include <webots/robot.h>
#include <webots/distance_sensor.h>
#include <webots/motor.h>
int main() {
wb_robot_init();
int timestep = (int)wb_robot_get_basic_time_step();
WbDeviceTag ds = wb_robot_get_device("front_ds");
WbDeviceTag left = wb_robot_get_device("left wheel motor");
WbDeviceTag right = wb_robot_get_device("right wheel motor");
wb_distance_sensor_enable(ds, timestep);
wb_motor_set_position(left, INFINITY);
wb_motor_set_position(right, INFINITY);
while (wb_robot_step(timestep) != -1) {
}
wb_robot_cleanup();
return 0;
}
Read Sensors with Correct Timing
- Enable sensor once using control period (commonly
timestep). - Read values only after at least one
stepfollowing enable. - Expect repeated values if multiple reads occur without an intervening step.
- Handle vector sensors as arrays (
gps.getValues()in Python, pointer arrays in C APIs).
Python pattern:
gps = robot.getDevice("gps")
gps.enable(timestep)
while robot.step(timestep) != -1:
position = gps.getValues() # [x, y, z]
C pattern:
WbDeviceTag gps = wb_robot_get_device("gps");
wb_gps_enable(gps, timestep);
while (wb_robot_step(timestep) != -1) {
const double *p = wb_gps_get_values(gps); /* p[0], p[1], p[2] */
}
Command Actuators with Step-Aware Semantics
- Use position control via
motor.setPosition(target)/wb_motor_set_position(tag, target). - Use velocity control by setting position to infinity, then setting velocity.
- Limit effort with torque/force APIs where supported (
setAvailableTorque,setAvailableForce, C equivalents). - Treat actuator writes as buffered commands committed at the next step.
Python pattern:
left = robot.getDevice("left wheel motor")
right = robot.getDevice("right wheel motor")
left.setPosition(float('inf'))
right.setPosition(float('inf'))
while robot.step(timestep) != -1:
speed = 3.0
left.setVelocity(speed)
right.setVelocity(speed)
C pattern:
wb_motor_set_position(left, INFINITY);
wb_motor_set_position(right, INFINITY);
while (wb_robot_step(timestep) != -1) {
double speed = 3.0;
wb_motor_set_velocity(left, speed);
wb_motor_set_velocity(right, speed);
}
Avoid Common Pitfalls
- Avoid chaining multiple
setPositioncalls before onestep; only the last command is applied. - Avoid assuming repeated
getValuecalls produce fresh data withoutstep; values remain unchanged. - Avoid using sensor values in the first loop iteration immediately after
enable; run a step first. - Avoid non-multiple control periods; keep controller step as an integer multiple of
WorldInfo.basicTimeStep.
Configure Language Runtimes
Use the language-specific bootstrap that matches the selected controller language.
- Python:
from controller import Robot - C:
#include <webots/robot.h> - C++:
#include <webots/Robot.hpp>andusing namespace webots; - Java:
import com.cyberbotics.webots.controller.Robot; - MATLAB: define function/script whose name matches controller name
Python may run with the bundled interpreter or an external system interpreter, depending on project setup.
Read Controller Arguments
- Set controller arguments in Robot field
controllerArgs. - Parse in Python from
sys.argv. - Parse in C/C++ through
argc, argvinmain.
Python example:
import sys
from controller import Robot
robot = Robot()
mode = sys.argv[1] if len(sys.argv) > 1 else "default"
C example:
#include <stdio.h>
#include <webots/robot.h>
int main(int argc, char **argv) {
wb_robot_init();
const char *mode = (argc > 1) ? argv[1] : "default";
printf("mode=%s\n", mode);
wb_robot_cleanup();
return 0;
}
Set Up Development and Debugging Workflow
- Edit with the built-in Webots editor for quick iteration.
- Use external IDEs (VS Code, PyCharm, CLion, others) by opening
controllers/<name>/as a project folder. - Compile C/C++ controllers through Makefile-driven builds from controller directory.
- Debug C/C++ extern controllers with
gdborlldbby attaching to the controller process. - Run extern controllers as separate processes when tighter IDE/debugger integration is required.
For C/C++ build templates, Robot API signatures, and process termination timing details, consult references/controller_api_reference.md.