Roomba project

From AIRWiki
Jump to: navigation, search
Roomba Analysis and Modification
Image of the project Roomba project
Short Description: Roomba's control system study and modification
Coordinator: GiuseppinaGini (gini@elet.polimi.it)
Tutor: ThomasFerrari (tferrari@elet.polimi.it)
Collaborator:
Students: AndreaScalise (andrea.scalise@aol.it), NiccoloTenti (nicotenti@libero.it), MohammedAli (mohammed.ali@mail.polimi.it)
Research Area: Robotics
Research Topic: Robot development
Start: 2010/05/10
End: 2011/09/25
Status: Closed
Level: Ms
Type: Course

This project is about studying the vacuum cleaner Roomba's brain, in order to modify and add new behaviours to it: in particular a wall-following algorithm will be implemented and tested. The interfacing with the robot will be provided by Pyro, a library, environment, graphical user interface to explore AI and robotics using the Python language.

Interfacing with Pyro

Pyro is a powerful software written in Python, used to provide a high level interface to many types of robots, regardless of their hardware structure. Moreover, Pyro provides an efficient simulation enviroment of the most common robots, so that you can implement your own algorithms for these robots even if they are not at your own disposal. Unfortunately though, a simulator for Roomba has not been implemented yet, so you need the physical robot in order to work on it. Here are the main steps to configure properly the Pyro environment and to interface it with a Roomba. For the project a Roomba 535 will be used (the one shown in the photo).

Pyro: download and installation

First of all, you need to download the latest version of Pyro. You can choose between different versions and operative systems from this website: http://pyrorobotics.org/download/. Since the project will be developed under Linux, we will refer to Linux Ubuntu OS, using the Pyro version 5.0.0. Then, you need to install it, open a terminal, move to the pyrobot folder, write the following commands and follow the instructions:

./configure.py
make
makefile

If you need Pyro only for this project, we suggest to avoid installing all the optional functionalities needed for the camera support (since Roomba doesn't have any).

If you have python version 2.6 or higher, you need to modify the following file in the pyrobot folder:

/pyrobot/bin/pyrobot

changing the words "site-packages" with "dist-packages" wherever it appears. Attention: this modification must not be done if you have earlier python versions. From now on, we will refer to python version 2.6.

Then you should move the pyrobot folder you have downloaded (after the installation) to the folder

/usr/lib/python2.6/dist-packages

Now you can run the program writing from terminal:

cd /usr/lib/python2.6/dist-packages/pyrobot/bin/
./pyrobot

You can also copy the file executable file in your home folder, and write from terminal:

./pyrobot

Roomba's Pyro files configuration

Before starting to use pyrobot with the Roomba, you must modify some settings in the Roomba robot files; these modifications are needed because the interfacing with Roomba that Pyro provides it's general, so there might be different behaviours according to your Roomba model (in this documentation we refer to Roomba 535).

First of all, you need to open the following file, as administrator:

/usr/lib/python2.6/dist-packages/pyrobot/robot/roomba.py

At line 459, you will find the code

dev.sendMsg('\x89\x00\x80\x00\x00')

You should change the last '0' with a '1', so that it will look like the following:

 dev.sendMsg('\x89\x00\x80\x00\x01')

This piece of code represents the bytes sent to the Roomba to force the rotation anticlockwise, but with the final zero, it represents a translation forward.

At line 316, you can find the code which transforms the value related to the rotation angle got by the sensors in radians. You should replace the value '258' with '38.5'. In this way, the original code was:

self.sensorData['angleInRadians'] = (2.0 * self.sensorData['rawAngle']) / 258

and then it will be:

self.sensorData['angleInRadians'] = (2.0 * self.sensorData['rawAngle']) / 38.5

This depends on how the Roomba sensors process the information, and probably it worked for another model of Roomba. The value 38.5 was obtained in experimental way: we made the Roomba do a 360 degrees turn, and registered the rotation degrees value shown on Pyro interface (it was about 0.99 radians instead of 6.28). In order to increment the degrees value to 6.28, we multiplied the value in radians observed for 258, and then, making the hypotesis that during the rotation the sensor detects the rotation angle in linear way, we divided the result for 6.28. Since the sensor detects discrete values, the number 38.5 is very approximative, and the correct angle shown on the Pyro interface could change if you make the Roomba do a complete rotation at once, or if you do a complete rotation step by step. However, we think that 38.5 can be a good choice for a working algorithm.

At line 318, you have to substitute the value '0.001' with '0.01' in the following line, since the distance run by the Roomba is in centimeters; the old code was:

d = self.sensorData['distance'] * 0.001

and must be substituted with:

d = self.sensorData['distance'] * 0.01

In the end, at line 319, a '-' is needed instead of the '+', because the movement forward and backward are inverted, so the code:

self.x += math.cos(a) * d
self.y += math.sin(a) * d

becomes:

self.x -= math.cos(a) * d
self.y -= math.sin(a) * d

Wall following algorithm

Here we present the main purpose of this project, a basic wall following algorithm. It makes the Roomba to keep track of the wall on its right using its wall sensor, while the left and right bumper sensors are used to warn the roomba that he hit a wall while going forward.

There are other ways to implement a wall following algorithm (also forcing Roomba to keep the wall on his left), without using the wall sensor, but only the bumper sensors: this kind of implementation would be "blind" and less intelligent, though.

In order to implement the algorithm, we used the finite state machine utility provided by Pyro, that is we classified a pool of possible behaviours of the Roomba and for each of them we implemented a different finite state.

We remind you that in Pyro a simulator for Roomba is not available yet, so if you want to try this algorithm, you need the robot at your disposal.

We will describe the main parts of the algorithm.

Importing libraries

from pyrobot.brain import Brain
from pyrobot.brain.behaviors import State, FSMBrain

These two lines are needed to import from Pyro library the finite state machine and brain structure. They let us to use the algorithm as a brain for the Roomba, using the FSM concepts.

First state: findWall

class findWall (State):

   def onActivate(self): 
       print "Start"

   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")

       self.robot.move(0.3,0)   
		
       if ((self.right == 1) or (self.left == 1)) :
          self.goto('turnSx')

When the Roomba is in this state (e.g when we start the brain), he will just go straightforward until he finds a wall in front of him. Here the function onActivate(self) just prints out the state in which the robot has just entered (and so it is for the other states as well). The function update(self) is called periodically, while Roomba is in the current state: in this case, the values of the Roomba left and right bumper sensors are assigned to the variables self.left and self.right (this will be done also in the other states), and the robot keeps on walking forward (self.robot.move(0.3,0) - the first number represents the traslation component, the second one the rotation component) until he hits a wall, which means that one bumper sensor touched a wall surface, changing value from 0 to 1 (if ((self.right == 1) or (self.left == 1))): in this case the robot passes to the state "turnSx", with the command self.goto('turnSx')

Second state: turnSx

class turnSx (State):
 
   def onActivate(self): 
       print "State turnSx"
	
   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")

       self.robot.move(0,0.3)  

       if ((self.right == 0) and (self.left == 0)): 
          self.goto('correctDx')

When the Roomba is in this state, it means that he just hit a wall, so he starts to turn left (because the wall following for this Roomba using the wall sensor can be done only if the wall appears on the robot's right, since there the wall sensor is) with the code self.robot.move(0,0.3), which was explained before. While turning left, the Roomba necessarily keeps contact with the wall through his bumper sensors, so he will stop turning only when the bumper sensors change values from 1 to 0. In this situation, the robot is not in contact with the wall anymore, and he points with the front part in a direction almost parallel to the wall, slightly diverging on the left.

When he stops turning, he passes to the next state (self.goto('correctDx')).

Third state: correctDx

class correctDx (State):

   def onActivate(self):
       print "State correctDx"

   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")
       self.wall = self.robot.getSensor("wallSensor")
 
       self.move(0.25,-0.37)
	
       if ((self.right == 1) or (self.left == 1)) :
          self.goto('turnSx')

       if (self.wall == 1): 
          self.goto('correctSx')

Passing from the state turnSx to this state, the robot is not in contact with the wall anymore, and the wall sensor doesn't detect any wall, because the Roomba should point slightly toward the wall in order to enable his wall sensor, which we remind to be located in the front part, a little bit on the right. The purpose of this state is to make Roomba going forward, adjusting a little bit his direction to the right, in order to make him closer to the wall he just left on his right. This is done by the code self.move(0.25,-0.37): the minus in the rotation component means a clockwise rotation. There's a lot of freedom setting the values for the move function: since we want the Roomba to make a 90° turning when there's a corner, the rotation value is pretty high (it goes from 0 to 1). In order to keep track of the wall, we need the wall sensor: that's why in the update function, we initialize the variable self.wall, with the value of the wall sensor in that exactly moment (this will be done also in the next state). So Roomba moves in the way described before until, turning right, his wall sensor detects a wall (if (self.wall == 1)): in this case the control passes to the state "correctSx". Instead, if the robot hits a wall in front of him while moving in this way, resulting in a change of value of the bumper sensors, the controll is passed again to "turnSx" state.

Fourth state: correctSx

class correctSx (State):
 
   def onActivate(self):
       print "State correctSx"
        
   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")
       self.wall = self.robot.getSensor("wallSensor")
        
       self.move(0.3, 0.15)

       if ((self.right == 1) or (self.left == 1)) : 
          self.goto('turnSx')

       if (self.wall == 0): 
          self.goto('correctDx')

This state has the same purpose as the previous one, just in symmetric way. The only things that change are the values in the move functions: the rotation, positive because anticlockwise, is less strong, since we don't want the Roomba to get too far from the wall. In this case, the robot keeps on moving in this way as far he detects the wall on the right with the wall sensor: as soon as he looses track of it, the control passes to the state "correctDx", in order to make him get closer to the wall again. As before, if a wall will be hit in the front while moving, the control passes to the state "turnSx".

The main point of this algorithm is a good balance between the move functions in the states "correctDx" and "correctSx": using the values above, we think it's a good trade-off to have a working and pretty fluent wall following.

Init function

def INIT(engine): 
       brain = FSMBrain("Wall Following Brain", engine)
       # add states:
       brain.add(findWall(1))
       brain.add(turnSx())
       brain.add(correctDx())
       brain.add(correctSx())
	
       return brain

This function just initializes the finite state machine, creating a new brain (brain = FSMBrain("Wall Following Brain", engine)) and adding all the states we created in the machine.

Finite state machine scheme

Here we show the behavioural functioning described just now, representing it by a conventional finite state machine automaton.

Every circle describes a state, so we have four circles for the four states in the algorithm. The arrows represent the transictions between one state to another, which is done in the algorithm through the function "goto". The arrow without origin, entering the "findWall" state indicates that that state is the initial one.

It's important to notice that the algorithm presented doesn't have a final state, but it ends up in a infinite loop, allowing the Roomba to work until a forced stop.

FSM-HighRes.jpg


Complete algorithm

In order to use the wall following algorithm, you must create a new .py file, write the algorithm code, and save the file in the following folder:

/usr/lib/python2.6/dist-packages/pyrobot/plugins/brains

In this way, it will be available between the brains' list when you run Pyro, as we will show.

Here is the complete algorithm, to copy in the brain file, as explained just before:

from pyrobot.brain import Brain
from pyrobot.brain.behaviors import State, FSMBrain

class findWall (State):

   def onActivate(self): 
       print "Start"

   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")

       self.robot.move(0.3,0)   
		
       if ((self.right == 1) or (self.left == 1)) :
          self.goto('turnSx')


class turnSx (State):
 
   def onActivate(self): 
       print "State turnSx"
	
   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")

       self.robot.move(0,0.3)  

       if ((self.right == 0) and (self.left == 0)): 
          self.goto('correctDx')


class correctDx (State):

   def onActivate(self):
       print "State correctDx"

   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")
       self.wall = self.robot.getSensor("wallSensor")
 
       self.move(0.25,-0.37)
	
       if ((self.right == 1) or (self.left == 1)) :
          self.goto('turnSx')

       if (self.wall == 1): 
          self.goto('correctSx')

  
class correctSx (State):
 
   def onActivate(self):
       print "State correctSx"
        
   def update(self):
       self.left = self.robot.getSensor("leftBump")
       self.right = self.robot.getSensor("rightBump")
       self.wall = self.robot.getSensor("wallSensor")
        
       self.move(0.3, 0.15)

       if ((self.right == 1) or (self.left == 1)) : 
          self.goto('turnSx')

       if (self.wall == 0): 
          self.goto('correctDx')


def INIT(engine): 
       brain = FSMBrain("Wall Following Brain", engine)
       # add states:
       brain.add(findWall(1))
       brain.add(turnSx())
       brain.add(correctDx())
       brain.add(correctSx())
	
       return brain

Connecting Roomba and Pyro

Now, we are ready to test our wall following algorithm on a real Roomba.

First of all, you need a way to connect physically the robot with a computer: on the Roomba Airwiki page (Roomba) you can find different ways to connect a Roomba with a pc: we will use a modified USB cable. If you have some experiences in electronics, you will manage to build your own cable following the instructions explained in this website: http://todbot.com/blog/2006/07/19/roombongle-a-roomba-usb-dongle/#more-119.

Then, you can run Pyro interface as explained before: all the commands are very clear and easy to understand. We will explain the two basic buttons useful to run the wall following brain, situated in the middle of the window, on the left (but also in the struments panel on the top).

Robot button: clicking this button, a window will appear showing all the possible robot files that Pyro supports. You must select the file named RoombaRobot.py. Once you selected it, you should type the serial port number the Roomba is connected to, and a transfer rate: for the first one, since we are using a USB cable, you must write the command

/dev/ttyUSBx

where "x" indicates the USB port number you are using on your pc. About the transfer rate, a value of 115200 is good (if you use a Zigbee connection way, it's recommended to lower this value to 96800, because we experimented that the connection was more stable in this way). If everything is correct, Pyro should write a confirmation message on the main board.

Brain button: with this button, you can load one of the brains for your robot. If you put the .py file with the wall following algorithm in the folder we specified before, then it will appear in the list: just select it.

Roomba in action

Now you just have to press the Run button in the middle of the window, and Roomba will start to behave in the way we specified in the algorithm.

We will show now three short videos of Roomba in action. We experienced some issues when Roomba has to follow dark walls. In fact, the IR wall sensor seems not to detect these kind of surfaces, so the algorithm doesn't work properly then, but it will force Roomba to follow "blindly" the wall.


The first video shows the general wall following algorithm.


In this second video, you can see the difference of behaviour, when Roomba follows a light surface (in the beginning) and a dark surface (after some seconds).


This last video shows another wall following, on a different floor material. In the end you can see another demonstration that dark surfaces are not detected by the wall sensor.