Skip to content

How to program your robot

Controllers

Each object in the simulation world can be controlled by a program. This program is called Controller. Each robot should have exactly one controller, implemented as a Python3 program. There is an invisible referee object in the simulation, which takes care of controlling the game and checking the rules.

The controllers are located in the controllers directory. The name of the controller must be located in a subfolder with the same name (i.e. robot/robot.py) and this name ought to be specified in soccer.wbt file.

Hello world, robot!

We have prepared a few sample robot controllers. They can be found in the controllers directory. The controllers for the robots of blue team are located in rcj_soccer_team_blue folder and for the robots of yellow team in rcj_soccer_team_yellow folder, respectively.

Team folders contain a file called rcj_soccer_team_blue.py (blue team) or rcj_soccer_team_yellow.py (yellow team). Each of the robots initially runs this file (for the competition, it should be robot.py). Based on the unique identifier of the robot (which can be 1, 2 or 3) we initialize the code for the particular robot.

Script for determining and initializing the robot controller

A sample initial file might look as follows:

from controller import Robot

from robot1 import MyRobot1
from robot2 import MyRobot2
from robot3 import MyRobot3


robot = Robot()
name = robot.getName()
robot_number = int(name[1])

if robot_number == 1:
    robot_controller = MyRobot1(robot)
elif robot_number == 2:
    robot_controller = MyRobot2(robot)
else:
    robot_controller = MyRobot3(robot)

robot_controller.run()

Let's describe a file for determining robot's name and running specific controller.

from controller import Robot

The Robot class is required to be imported because this is the only way we are able to controll the robot. The Robot class is shipped together with Webots.

from robot1 import MyRobot1
from robot2 import MyRobot2
from robot3 import MyRobot3

Since all of robot controllers are located in the same directory, we can easily import them.

robot = Robot()
name = robot.getName()

Initialize robot instance and get the name of the robot. The name is one of the following {"B1", "B2", "B3", "Y1", "Y2", "Y3"}.

robot_number = int(name[1])
if robot_number == 1:
    robot_controller = MyRobot1(robot)
elif robot_number == 2:
    robot_controller = MyRobot2(robot)
else:
    robot_controller = MyRobot3(robot)

By checking the second character in the name, we can easily get the number identifier of the robot and initialize its controller appropriately.

robot_controller.run()

We just call the method run in order to execute the code for the specific robot we initialized previously.

Robot controller

Let's put together a simple program to showcase how you can go about programming a robot.

import json

TIME_STEP = 32
ROBOT_NAMES = ["B1", "B2", "B3", "Y1", "Y2", "Y3"]
N_ROBOTS = len(ROBOT_NAMES)


class MyRobot:
    def __init__(self, robot):
        self.robot = robot
        self.name = self.robot.getName()

        self.receiver = self.robot.getDevice("supervisor receiver")
        self.receiver.enable(TIME_STEP)

        self.ball_receiver = self.robot.getDevice("ball receiver")
        self.ball_receiver.enable(TIME_STEP)

        self.gps = self.robot.getDevice("gps")
        self.gps.enable(TIME_STEP)

        self.compass = self.robot.getDevice("compass")
        self.compass.enable(TIME_STEP)

        self.sonar_left = self.robot.getDevice("distancesensor left")
        self.sonar_left.enable(TIME_STEP)
        self.sonar_right = self.robot.getDevice("distancesensor right")
        self.sonar_right.enable(TIME_STEP)
        self.sonar_front = self.robot.getDevice("distancesensor front")
        self.sonar_front.enable(TIME_STEP)
        self.sonar_back = self.robot.getDevice("distancesensor back")
        self.sonar_back.enable(TIME_STEP)

        self.left_motor = self.robot.getDevice("left wheel motor")
        self.right_motor = self.robot.getDevice("right wheel motor")

        self.left_motor.setPosition(float('+inf'))
        self.right_motor.setPosition(float('+inf'))

        self.left_motor.setVelocity(0.0)
        self.right_motor.setVelocity(0.0)

    def get_new_data(self):
        packet = self.receiver.getString()
        self.receiver.nextPacket()
        return json.loads(packet)

    def run(self):
        while self.robot.step(TIME_STEP) != -1:
            if self.receiver.getQueueLength() > 0:
                data = self.get_new_data()

                # Get data from compass
                heading = self.get_compass_heading()

                # Get GPS coordinates of the robot
                robot_pos = self.get_gps_coordinates()

                # Get data from sonars
                sonar_values = self.get_sonar_values()

                # Get direction and strength of the IR signal
                if self.is_new_ball_data():
                    ball_data = self.get_new_ball_data()

                self.left_motor.setVelocity(1)
                self.right_motor.setVelocity(-1)

Let's explain the code in detail:

import json

This library is a built-in Python library, which is required to decode the data sent by the supervisor.

TIME_STEP = 32
ROBOT_NAMES = ["B1", "B2", "B3", "Y1", "Y2", "Y3"]
N_ROBOTS = len(ROBOT_NAMES)

We also define some useful constants, whose values will be used later.

class MyRobot:

You can wrap the program into the class as we did. The benefit of OOP (Object Oriented Programming) is that you can later reuse the same common class throughout your controllers and therefore make the code easier to read. We are going to continue with our OOP approach.

def __init__(self, robot):
    self.robot = robot
    self.name = self.robot.getName()
    ...

The __init__ function is something like a constructor of the class and is called when an instance of the MyRobot object is created. We use this function to initialize some important variables. The most important one is the Robot instance, which allows us to get access to the so called Webots devices like motor (for controlling the speed), receiver (for reading data from Supervisor), and sensors like GPS, Compass, Sonars or IR Ball receiver. The name and the team of your robot can be determined by calling self.robot.getName(). It will give you one of {"B1", "B2", "B3", "Y1", "Y2", "Y3"}. The first letter determines the team ("Blue", "Yellow"), while the second one is the robot's identifier. If you want to know the side of your team (either "Blue" or "Yellow"), you can find out by checking self.name[0], which essentially gives you "B" for Blue or "Y" for Yellow side.

def get_new_data(self):
    ...

We are not going to explain this deeply. This function simply decodes the incoming data from supervisor. Feel free to copy and use it. The resulting dictionary just contains a single bit of information: whether a goal was scored and we are waiting for a new kickoff. In case the goal gets scored, the value is True and is reset to False when the referee fires new kickoff.

def run(self):

This is the method which contains the logic for controlling the robot. As mentioned previously, it is called by our initialization script.

while self.robot.step(TIME_STEP) != -1:

The step function is crucial and must be used in every controller. This function synchronizes the sensor and actuator data between Webots and the controllers.

if self.receiver.getQueueLength() > 0:

Before reading data, it is important to check if there is actually something in the queue that we can read.

data = self.get_new_data()

heading = self.get_compass_heading()
robot_pos = self.get_gps_coordinates()
sonar_values = self.get_sonar_values()
if self.is_new_ball_data():
    ball_data = self.get_new_ball_data()

self.left_motor.setVelocity(1)
self.right_motor.setVelocity(-1)

And finally, after reading the new data received from supervisor as well as data from sensors we do some calculations and set the speed of the motors.

Available sensors

GPS

This sensor gives you the exact position of the robot. For more information check official GPS documentation. In rcj_soccer_robot.py you can find get_gps_coordinates(), which demonstrates how to work with GPS sensor.

Compass

Useful sensor to determine the angle (rotation) of the robot from the north. For more information check official compass documentation. In rcj_soccer_robot.py you can find get_compass_heading(), which demonstrates how to work with compass sensor.

Sonars

There are four sonars mounted on the robot (each side having one). Since the exact position of the robot may be retrieved from GPS, these sensors are useful for detecting the opponent's robots. For more information check official distance sensor documentation.

Note that you may encounter some error in measurements. When the robot is next to an obstacle, the value returned is 0 with error of 0%. On the other hand, when the sesnsor does not see anything, the value returned is 1000 with error of 5%. The values and errors in between are linearly interpolated.

In rcj_soccer_robot.py you can find get_sonar_values(), which demonstrates how to work with sonar sensors. For debugging purposes, you may find it useful to turn on rendering rays of distance sensors. This option is available in Webots GUI under View -> Optional Rendering -> Show DistanceSensor Rays.

Ball IR Sensor

There is an infra-red emitter mounted onto the ball. The emitter just emits a signal and each robot receives this signal if it is located within a pre-defined range. The receiver is able to determine the direction as well as strength of the signal, which can be used for navigating robots towards the ball.

For more information check the official receiver documentation or our get_new_ball_data() method in rcj_soccer_robot.py.

Importing shared code

Each team consists of three robots. These robots might share some of the code and it is actually a good practice not to duplicate code all over the place.

Imagine you have the folder structure which looks like this

controllers/
├── robot/
│   └── robot.py
│   └── robot1.py
│   └── robot2.py
│   └── robot3.py
|   └── utils.py

and within robot1.py, robot2.py and robot3.py you want to import some useful code from utils.py. You can easily import it by calling

import utils

and use the shared code rather than copying it into each of the controllers. Do not import anything from robot.py file, otherwise you might get cyclic import problem.

Supported external libraries

In general, the whole Python's standard library can be used in the robot's programs.

Furthermore, to make the computations easier, the Soccer Sim environment supports the following two Python libraries that are normally used for what's called "scientific computing":