Swarm Robotics


Last updated: May 12th, 2017

News

July 14th, 2017
  • The project for the second session is now available Project detail.
  • The project submission deadline is August 25th at 23:59.
  • The exam will take place on September 6th.
May 12th, 2017
  • The deadline to deliver the project has been moved to Tuesday 6th of June, no delay allowed.
  • The exam will take place Tuesday 13th of June at the IRIDIA Lab
May 12th, 2017
May 5th, 2017
May 5th, 2017
  • Uploaded solutions for the aggregation exercise.
April 21st, 2017
  • Uploaded solutions for color selection exercise.
March 31st, 2017
  • Uploaded solutions for obstacle avoidance and synchronization exercises.
March 17th, 2017
  • Created this website and added initial content.

Exercises

Here are the experiment files for the course and their solutions:


ARGoS

ARGoS is a multi-robot simulator developed at IRIDIA. We will use it to work on exercises in this course, as well as on the final course project.

Getting ARGoS

The machines in the computer room all have ARGoS installed, and you're encouraged to use them. To log into these machines, you can use your ULB account or these credentials:

  • Username: fsa-vub-students
  • Password: fsa-vub-students

IMPORTANT: when you log in with these credentials, you just have a temporary, local session on the computer. This means that all data is erased when you log out. Remember to back up your data!

If you prefer to install ARGoS on your computer, download one of the packages below or compile it directly from the sources.

ARGoS works only on UNIX systems (Linux and MacOSX). Windows is not supported.

Installation/upgrade on other platforms

To install ARGoS on other platforms, check the following page: argos download.

Running ARGoS

To run ARGoS, you need an XML file that configures the program for a specific experiment. We will provide you with all the necessary XML files for each exercise. Once you have the XML file, open a terminal window and write this command:

$ argos3 -c file.argos

This will open the two windows of ARGoS - the simulation window and the code editor.

Programming the Robots

Lua

To tell the robots what to do, you need to write a script. The script is written in a language called Lua (v5.2). Lua is a very simple but powerful scripting language. You can find more information on the Lua manual. For nice tutorials, check The Lua Crash Course and the Lua Wikipedia page.

The table below contains a short Lua reference, with examples. It covers just the essential parts of the language you need to program the robots.

Comments
-- This is a single-line comment

--[[ This is a multiline
     comment ]]
                    
Variables
-- assignment (no need to declare a variable)
x = 2.55   -- number
x = "ciao" -- string

-- printing on the ARGoS logger
log("INFO: x = " .. x)
logerr("ERROR: x = " .. x)

-- NOTE: in ARGoS, the standard Lua 'print()' function does not work
                    
Conditionals
-- simple if/then
if x > 3 then
   logerr("x is too big")
end

-- if/then/elseif/else
if x > 3 then
   logerr("x is too big")
elseif x < 3 then
   logerr("x is too small")
else
   logerr("maybe I just don't like x")
end

-- checking for equality
if x == 3 then
   log("x is equal to 3")
end

-- checking for inequality
if x ~= 4 then
   log("x is different from 4")
end

-- combining conditions with OR
if (x > 3) or (x < 3) then
   logerr("x is not 3")
end

-- combining conditions with AND
if (x > 3) and (y > 3) then
   logerr("x and y are too big")
end

-- negating a condition
if not (x > 3) then
   logerr("x is <= 3")
end
                    
Loops
-- 'while' loop to print 1 2 3 4 5
x = 0
while x < 5 do
   x = x + 1
   log(x)
end

-- 'repeat until' loop to print 1 2 3 4 5
x = 0
repeat
   x = x + 1
   log(x)
until x == 5

--[[ 'for' loop with increase/decrease of a variable:
     for x = init_value, end_value, step do ... end
     where:
        x = init_value initializes the counting variable x to init_value
        end_value      is the stopping value
        step           is the (optional) value to sum to x at each iteration
     the loop continues:
        - when x <= end_value, if step > 0
        - when x >= end_value, if step < 0
  ]]

-- 'for' loop to print 1 2 3 4 5
for x = 1, 5 do
   log(x)
end

-- 'for' loop to print 1 3 5
for x = 1, 5, 2 do
   log(x)
end

-- 'for' loop to print 5 4 3 2 1
for x = 5, 1, -1 do
   log(x)
end
                    
Tables
-- creating an empty table
t = {}

-- creating a table with some initial value
t = { x=3 }

-- using the contents of a table: two equivalent ways
log("t.x = " .. t.x)       -- dot syntax
log("t['x'] = " .. t["x"]) -- string syntax

-- printing the contents of a table: a custom function
function table.print(t)
   for key,value in pairs(t) do
      log(key .. " -> " .. value)
   end
end

-- tables are always passed by reference!
t1 = { x=3 }
t2 = t1      -- now t2 points to the contents of t1 -> no deep copy
t2.x = 5
log(t1.x)    -- prints 5, not 3!

-- copying tables the right way
function table.copy(t)
   local t2 = {}
   for key,value in pairs(t) do
      t2[key] = value
   end
   return t2
end
t1 = { x=3 }
t2 = table.copy(t1)
t2.x = 5
log(t1.x)    -- prints 3
log(t2.x)    -- prints 5

-- tables can be used as arrays
a = { "Huey", "Dewey", "Louie" } -- indices are assigned automatically starting from 1
log(a[1])                        -- prints "Huey"
log(a[2])                        -- prints "Dewey"
log(a[3])                        -- prints "Louie"
log(#a)                          -- prints the number of elements in a

-- sorting the contents of a simple table
a = { "Huey", "Dewey", "Louie" }
table.sort(a)  -- this operation modifies a!
table.print(a) -- prints "1 -> Dewey", "2 -> Huey", "3 -> Louie"

--[[ To sort the contents of a nested table, such as those in the robot,
     you need to specify a comparation function to tell the sort algorithm
     how to compare two table elements ]]

-- Take a nested table
t = {
   { x=4, label="Huey" },
   { x=56, label="Dewey" },
   { x=0.6, label="Louie" }
}

-- Sort it by x in increasing order
table.sort(t, function(a,b) return a.x < b.x end)

-- Sort it by x in decreasing order
table.sort(t, function(a,b) return a.x > b.x end)

-- Sort it by label in increasing order
table.sort(t, function(a,b) return a.label < b.label end)

-- Sort it by label in decreasing order
table.sort(t, function(a,b) return a.label > b.label end)

                    
Functions
-- defining a function
function my_fun(p)
   log("Called my_fun(" .. p .. ")")
end

-- returning a value
function my_add(a, b)
   return a + b
end
                    
Math
-- All the math functions are part of the 'math' table

-- Setting a 2D vector from length and angle
function vec2_new_polar(length, angle)
   local vec2 = {
      x = length * math.cos(angle),
      y = length * math.sin(angle)
   }
   return vec2
end
v = vec2_new_polar(2, math.pi/3)

-- Summing two 2D vectors (v1 = v1 + v2)
function vec2_sum(v1, v2)
   v1.x = v1.x + v2.x
   v1.y = v1.y + v2.y
end

v1 = { x=1, y=2 }
v2 = { x=3, y=1 }
vec2_sum(v1, v2)
table.print(v1)   -- prints "x -> 4", "y -> 3"

-- Getting the angle of a 2D vector
function vec2_angle(v)
   return math.atan2(v.y, v.x)
end

--[[ NOTE: to get a random number, DO NOT use math.random(). Rather,
     use robot.random, described below. ]]
                    

Robot-related Functions and Variables

The robot-related functions and data are stored in the robot table. For instance, to set the robot wheel speed, you need to call

robot.wheels.set_velocity(2,3)

Analogously, To store the reading of the 4th proximity sensor in a variable named r, you type

r = robot.proximity[4].value

NOTE: don't modify the values of the robot attributes. More specifically:

  • Never write directly into the robot attributes
    • robot.proximity[4].angle = 1.67 -- NO!
  • Never apply operations such as table.sort() to the robot table
    • table.sort(robot.proximity, function(a,b) return a.value > b.value end) -- NO!
  • If you intend to use the values of a robot table, copy that table first:
    • myprox = table.copy(robot.proximity)
    • table.sort(myprox, function(a,b) return a.value < b.value end)
robot.base_ground The base ground sensor reads the color of the floor. It is a list of 8 readings, each containing a table composed of value and offset. The value is either 0 or 1, where 0 means black (or dark gray), and 1 means white (or light gray). The offset corresponds to the position read on the ground by the sensor. The position is expressed as a 2D vector stemming from the center of the robot. The vector coordinates are in cm.
The difference between this sensor and the robot.motor_ground is that this sensor returns binary readings (0 = black/1 = white), while robot.motor_ground can distinguish different shades of gray.
robot.distance_scanner The distance scanner is a rotating device with four sensors. Two sensors are short-range (4cm to 30cm) and two are long-range (20cm to 150cm). Each sensor returns up to 6 values every time step, for a total of 24 readings (12 short-range and 12 long-range). Each reading is a table composed of angle in radians and distance in cm. The distance value can also be -1 or -2. When it is -1, it means that the object detected by the sensor is closer than the minimum sensor range (4cm for short-range, 20cm for long-range). When a sensor returns -2, it's because no object was detected at all.
This device is initially off. You need to use the enable() function to switch it on. The function disable switches it off. The function set_angle() locks the device at a specific angle, while set_rpm(s) sets the angular speed of the device to s (which must be a number). If you want to convert from radians per second to RPMs, apply the following formula: RPM = 30/pi * radsec.
robot.gripper The gripper allows a robot to connect to objects such as boxes and cylinders, or other robots. A robot attached to a passive object can transport it, if it is light enough.
To lock the gripper, you use lock_positive() or lock_negative(). They both do the same job, so you can call the one you prefer. To unlock the gripper (thus releasing the attached object), use unlock().
robot.id A string containing the id of the robot.
robot.leds Sets the color of the robot LEDs. The robot has a total of 13 RGB LEDs. 12 of them are arranged in a ring around the robot body, and one (also called the beacon) is positioned at the top of the robot body.
To set the colors of a single LED, use set_single_color(idx, color). idx is the number of the LED to set (1-12 for the body LEDs, 13 for the beacon). color can be expressed as a string, such as "red", "green", "blue", etc., or as a triplet of numbers r,g,b.
To set all colors at once, use set_all_colors(color). The color parameter works like set_single_color(idx, color).
robot.light The light sensor allows the robot to detect light sources. The robot has 24 light sensors, equally distributed in a ring around its body. Each sensor reading is composed of an angle in radians and a value in the range [0,1]. The angle corresponds to where the sensor is located in the body with respect to the front of the robot, which is the local x axis. Regarding the value, 0 corresponds to no light being detected by a sensor, while values > 0 mean that light has been detected. The value increases as the robot gets closer to a light source.
robot.motor_ground The motor ground sensor reads the color of the floor. It is a list of 4 readings, each containing a table composed of value and offset. The value goes from 0 or 1, where 0 means black, and 1 means white. The offset corresponds to the position read on the ground by the sensor. The position is expressed as a 2D vector stemming from the center of the robot. The vector coordinates are in cm.
The difference between this sensor and the robot.base_ground is that this sensor can distinguish different shades of gray, while robot.base_ground returns binary readings (0 = black/1 = white).
robot.proximity The proximity sensors detect objects around the robots. The sensors are 24 and are equally distributed in a ring around the robot body. Each sensor has a range of 10cm and returns a reading composed of an angle in radians and a value in the range [0,1]. The angle corresponds to where the sensor is located in the body with respect to the front of the robot, which is the local x axis. Regarding the value, 0 corresponds to no object being detected by a sensor, while values > 0 mean that an object has been detected. The value increases as the robot gets closer to the object. For example, to access the angle of the first proximity you can write robot.proximity[1].angle and to access the value of the first proximity you can write robot.proximity[1].value
robot.random This table offers a set of functions to draw random numbers from a distribution.
Use bernoulli() to get either 0 or 1 from a Bernoulli distribution with p=0.5. You can also write bernoulli(p) to set a different value for p.
Use exponential(m) to get a random number from an exponential distribution with mean m.
Use gaussian(s) to get a random number from a Gaussian distribution with standard deviation s and zero mean. You can also write gaussian(s,m) to set a non-zero mean.
Use uniform() to get a random number from a uniform distribution in the range [0,1). Alternatively, you can use uniform(max) to get a number between 0 and max, or uniform(min,max) to get a number between min and max. If you want integer numbers, use the functions uniform_int(max) and uniform_int(min,max).
robot.range_and_bearing The range-and-bearing system allows robots to perform localized communication. Localized communication means that a robot, upon receiving data from another robot, also detects the position of the sender with respect to its local point of view. It is important to notice that the range-and-bearing system is not like WiFi. First, because two robots can exchange data only if they are in direct line of sight - if an object is between two robots, the robots can't communicate. Second, because robots that send data can only broadcast it in a limited area - you can't pick who you talk to as you would with an IP address. Third, the robots can exchange only 10 bytes of data.
To set the data to broadcast, use set_data(). This function accepts input in two forms. You can write set_data(idx, data), and this means that you set the idx-th byte to the value of data. data must be a number in the range [0,255]. Alternatively, you can write set_data(data), where data must be a table containing exactly 10 numbers in the range [0,255].
At each time step, a robot receives a variable number of messages from nearby robots. To know the number of messages received in a given time step, use #robot.range_and_bearing. Each message is stored in a table composed of data (the 10-bytes message payload), horizontal_bearing (the angle between the robot local x axis and the position of the message source; the angle is on the robot's xy plane, in radians), vertical_bearing (like the horizontal bearing, but it is the angle between the message source and the robot's xy plane), and range (the distance of the message source in cm). For example, to access the first byte of data of the first message received you can write robot.range_and_bearing[1].data[1]. To access the range and the horizontal_bearing of the same message you can write robot.range_and_bearing[1].range and robot.range_and_bearing[1].horizontal_bearing
robot.wheels The real robot moves using two sets of wheels and tracks called treels. For simplicity, we treat the treels like normal wheels.
To move the robot, use set_velocity(l,r) where l and r are the left and right wheel velocity, respectively. By 'wheel velocity' we mean linear velocity. In other words, if you say set_velocity(5,5), the robot will move forward at 5cm/s.
You can get some information about robot motion and wheels, too. axis_length is the distance between the two wheels in cm. velocity_left and velocity_right store the current wheel velocity. distance_left and distance_right store the linear distance covered by the wheels in the last time step.
robot.turret The foot-bot gripper is attached to a rotating device called the gripper. You can control the gripper by either setting its rotation, or its rotational speed. To set its rotation, you must first call the method set_position_control_mode() to switch the gripper to position control mode, and then call set_rotation(angle) to rotate the gripper at angle angle. Alternatively, you can set the rotational speed by calling set_speed_control_mode() first, and then set_rotation_speed(speed). With set_passive_mode() you instruct the gripper to be in a state in which, as the robot moves with an object gripped, the turret rotates due to the weight of the gripped object.
robot.colored_blob_omnidirectional_camera This device returns a list of colored blobs, along with their position with respect to the robot center. A colored blob in ARGoS corresponds to an LED. The list of blobs varies in size over time, depending on what the robots sees. To know the number of messages received in a given time step, use #robot.colored_blob_omnidirectional_camera.
To start collecting blobs, you need to call enable(). To stop, call disable().
Each blob is stored in a table composed of distance (the distance of the blob source in cm), angle (the angle between the robot local x axis and the position of the blob source; the angle is in radians), and color (a table composed of red, green, and blue, which are three RGB components of the blob's color in the range [0,255]). For example, to access the red component of the first blob seen by the robot you can write robot.colored_blob_omnidirectional_camera[1].color.red

Project Details

Exam Modalities

Get the full description here: SI_Project_2017.pdf.

Technical Information

In this section you will learn how to run the project assignment. You must download a package that contains the necessary files to execute the experiment:

  • The XML configuration file;
  • The loop functions (i.e., the logic to gather the data) in source format.

To run the experiment, you need some setup explained in the following.

NOTE: the loop functions are given to you in source format. You are allowed to modify them if you want to log more data (this is optional). However, do not modify them to cheat on the results! We will run your code with the original loop functions, just to be sure. If you modify the loop functions, add them to the material you send us.

Setting up the Code

  1. To correctly execute the experiment code, you need to have a version of ARGoS equal to 3.0.0-beta45. To check your current version of ARGoS, type at the command prompt:
    $ argos3 --version
    		  
  2. Download the experiment files: SR_Project_H414.tar.
  3. Unpack the archive and compile the code:
    $ tar xvf SR_Project_H414.tar 		    # Unpacking
    $ cd SR_Project_H414	                    # Enter the directory
    $ mkdir build                               # Creating build dir
    $ cd build                                  # Entering build dir
    $ cmake -DCMAKE_BUILD_TYPE=Release ../src   # Configuring the build dir
    $ make                                      # Compiling the code
                      
  4. Set the environment variable ARGOS_PLUGIN_PATH to the full path in which the build/ directory is located:
    $ export ARGOS_PLUGIN_PATH=/path/to/SR_Project_H414/build/
                      
    You can also put this line into your $HOME/.bashrc file, so it will be automatically executed every time you open a console.
  5. Run the experiment to check that everything is OK:
    $ cd /path/to/SR_Project_H414           # Make sure you are in the right directory
    $ argos3 -c foraging.argos         # Run the experiment
                      
    If the usual ARGoS GUI appears, you're ready to go.

Frequently Asked Questions

If I use metatables to do something like this:
   MyTable = {}
   MyTable.__index = MyTable
		      
...and then select a robot, ARGoS gets slow and eventually crashes. What's wrong?
This is a known issue with the variable state widget of the Lua editor in ARGoS. The bug is still there, but there is a workaround. Do something like this:
   MyTable = {}
   MyTable_mt = {}
   MyTable.__index = MyTable_mt
		      
Is there a way to automatically pick a different random seed at ARGoS startup? Editing the XML file for each experiment looks really tedious.
Sure. Just remove the random_seed attribute from the <experiment> section.
Can I ask a question?
You just did. If you have questions of general interest, they will end up here, along with answers.

Contact

Antoine Ligot aligot@ulb.ac.be for help with the robotics project
Lorenzo Garattoni lgaratto@ulb.ac.be for help with the robotics project
Marco Dorigo mdorigo@ulb.ac.be for administrative stuff
Mauro Birattari mbiro@ulb.ac.be for administrative stuff
Course Web Site http://cs.ulb.ac.be/public/teaching/infoh414/