- 1 Sensing the World
Sensing the World
|I see dead people.
-: Cole Sear (played by Haley Joel Osment) in Sixth Sense, M. Night Shyamalan, 1999.
|I see all obstacles in my way. -: In the song I can see clearly now, Johnny Nash, 1972.|
Cole Sear in Shayamalan's Sixth Sense is not refering to dead bodies lying in front of him (for those who have not seen the movie). The five senses that most humans relate to are: touch, vision, balance, hearing, and taste or smell. In all cases our bodies have special sensory receptors that are placed on various parts of the body to enable sensing. For example the taste receptors are concentrated mostly on the tongue, the touch receptors are most sensitive on hands and the face and least on the back and on limbs although they are present all over the body, etc. Besides the difference in the physiology of each kind of receptors there are also diffferent neuronal pathways and thereby sensing mechanisms built into our bodies. Functionally, we can say that each type of sensory system starts with the receptors which convert the thing they sense into electrochemical signals that are trasmitted over neurons. Many of these pathways lead to the cerebral cortex in the brain where they are further processed (like, "Whoa, that jalapeno is hot!!").
Sensing is an essential component of being a robot and every robot comes built with sensors that are capable of sensing different environmental conditions. It is not uncommon, for example, to find sensors that are capable of sensing light, temperature, touch, distance to another object, etc. Robots employ electromechanical sensors and there are different types of devices available for sensing the same physical quantity. For example, one common sensor found on many robots is a proximity sensor. It detects the distance to an object or an obstacle. Proximity sensors can be made using different technologies: infrared light, sonars, or even lasers. Depending upon the type of technology used, their accuracy, performance, as well as cost varies: infrared (IR) being the cheapest, and laser being on the expensive side. Your Scribbler robot has a small set of sensors that can be used to detect certain features of its environment.
The picture below shows two of the Scribbler's four sensors. In this section we will learn about their behavior and the physical quantities they detect.
- List of Scribbler Sensors
- Light: There are three light sensors present on the robot. These are located in the three holes present on the front of the robot. These sensors can detect the levels of brightness (or darkness). Using these the Scribbler can be made to detect variations in ambience light in a room.
- Proximity: At the front of the robot you will notice two tiny lamps inserted as "headlights" on either side of the robot. These are IR emmitters. The light emmitted by these if reflected by the presence of an obstacle will bounce back towards the robot and is captured by the IR sensor that is present in the tiny notch in the middle of the two IR emmiters (see figure above).
- Line: If you tip over your Scribbler and look at the front portion of the chasis (away from the third wheel), you will notice two paris of tiny holes. These are also IR emitters and receivers and can be used to detect lines on the floor.
- Stall: There is also an internal sensor in the robot that tells it whether it is stalled or stuck when it is trying to move.
Getting to know the sensors
Sensing using the sensors provided in the Scribbler is easy. Once you are familiar with the details of sensor behaviors you will be able to use them in your programs to design interesting creature-like behaviors for your Scribbler. But first, we must spend some time getting to know these sensors. This is a hands-on exercise, so be sure to bring your Scribbler with you (make sure that your batteries are still fresh). Start Python, connect to the robot, and then enter the command:
Earlier, in Chapter 1, when you used this command, you did not supply any parameters. But, when you supply a parameter (the value 1 shown above), the joystick window opens up with an additional display of all the sensor values being reported by the robot. These values are updated as the robot moves or at least once every second. A sample joystick window with sensor display is shown below:
The display under the joystick contains four sets of sensor values being reportd by the Scribbler. These are described in more detail below:
- Scribbler Sensor Values
- Light: The current values being reported by the three light sensors are: 135 (left), 3716 (center), and 75 (right). In general, the lower the values, the brighter the light being sensed. This snapshot was taken when the center light sensor was covered by a finger.
- Line: In the presence of a line under the Scribbler (the line has to be aligned with the length of the robot) the Scribbler is capable of detecting a bright edge against a dark edge (that forms a line). The display shows the values of left and right sensors (both are 1). If the right sensor is on a dark are of an edge or a line and the left sensor is on a lighter area or edge, the sensor values would be (left=0, right=1). if the edge is flipped, the values would be (left=1, right=0).
- IR: The IR values displayed are also left and right (in the same orientation as the light). Their values will be either 1 or 0. If there is an obstacle detected, the value will be 0, 1 otherwise.
- Stall: The stall sensor detects that the robot is not moving even though the motors are. That is, it is stuck somewhere. Its values can be 0 or 1, 0 implies it is not stalled and 1 implies it is.
Since you have the robot connected and showing the joystick display with sensor values, try to move the robot around (either with your hand or with the joystick) and observe the sensor values. For the light sensor, try to cover the sensors and observe, turn the lights on or off in the room (if feasible) or pick up the robot and turn it towards or away from the light. Again observe the values reported. Shine a bright flashlight into the sensor holes and observe the values (they should be really low).
Similarly, place the robot so that it is on a thick black line. Also try the edge of your mouse pad (mousepads tend to be of darker colors). Notice the values that are reported.
Place various objects in front of the robot and look atthe values of the IR proximity sensors. Take your notebook, place it in front of the robot about two feet away. Slowly move the notebook closer to the robot. Notice how the value of the IR sensor changes from a 1 to a 0 and then move the notebook away again. Can you guess how far (near) the obstacle should be before it is detected (or cleared)? try moving the notebook from side to side. Again notice the values of the IR sensors.
Using the joystick move the robot around and on purpose, make the robot go into a wall or an obstacle so it gets stuck. read the value of the stall sensor.
Make sure you do this enough number of times so that you are comfortable with the performance of your robot's sensors. if the left-right orientation/labelling is confusing, feel free to mark the robot's sensors with a pen on the robot itself. In the remainder of this chapter, we will learn how we can access these sensor values through commands/functions in the Myro library. Then we will design many interesting behaviors for your Scribbler.
Exercise 1: Place your Scribbler on the floor, turn it on, start Python, and connect to it. Issue the joystick command (shown above to get the joystick display). Now, our objective here is to really "get into the robot's mind" and drive it around without ever looking at the robot. You can use the information displayed by the sensors to navigate the robot. Try driving it to a dark spot, or the brightest spot in the room. Try driving it so it never hits any objects. Can you detect when it hits something? If it does get stuck, try to maneuver it out of the jam! This exercise will give you apretty good idea of what the robot senses, how it can use its sensors, and to the range of behaviors it may be capable of. Place a sheet of paper with a thick black line on it (your instructor will provide one for the lab) and without looking at the robot or the line, see if you can make the robot follow the line. You will find this exercise a little hard to carry out, but it will give you a good idea as to what should go into the brains of such robots when you actually try to design them. We will try and revisit this scenario as we build various robot programs.
Proprioception is the term used to refer to the phenomenon of internal sensing. Here is an illustration of what this means:
Do This: Get something really delicious to eat, like a cookie, or a piece of chocolate, or candy (whatever you fancy!). Hold it in your right hand, and let your right arm hang naturally on your side. Now close your eyes, real tight, and try to eat the thing you are holding. Piece of cake! (well, whatever you picked to eat :-) Without fail, you were able to pick it up and bring it to your mouth, right? Give yourself a Snickers moment and enjoy the treat. Then read on...
This is because of proprioception. Your body's sensory system also keeps track of the internal state of your body parts, how they are oriented, etc. Proprioception in robots is refered to the measurement of movement relative to the robot's internal frame of reference. Sometimes also called dead reckoning, it can be a useful additional sensing mechanism that you can use to design robot behaviors. In fact, the sense that the robot has stalled is a kind of proprioception. Another useful proprioceptory mechanism that exists is time. You have already seen how you can use the wait command to do something for a specific amount of time. In the next exercise, we will ask you to use time to a robot task.
Exercise 2: Design a robot program for the Scribbler to draw a square (say with sides of 6 inches). To accomplish this, you will have to experiment with the movements of the robot and correlate them with time. The two movements you have to pay attention to are the rate at which the robot moves, when it moves in a straight line; and the degree of turn with respect to time. You can write a function for each of these:
def travelStraight(distance): # Travel in a straight line for distance inches ... def degreeTurn(angle): # Spin a total of angle degrees
That is, figure out by experimentation on your own robot (the results will vary from robot to robot) as what the correlation is between the distance and the time for a given type of movement above and then use that to define the two functions above. For example, if a robot (hypothetical case) seems to travel at the rate of 25 inches/minute when you issue the command tranalate(1.0), then to travel 6 inches you will have to translate for a total of (6*60)/25 seconds. Try moving your robot forward for varying amounts for time atthe same fixed speed. For example try moving the robot forward at speed 0.5 for 3, 4, 5, 6 seconds. Record the distance travelled by the robot for each of those times (you will notice a lot of variation in the distance even for the same set of commands, you may want to average those). Given this data, you can estimate the average amount of time it takes to travel an inch. Similarly for turning. Try turning the robot at the same speed for varying amounts of time. Experiment how long it takes the robot to turn 360 degrees, 720 degrees, etc. Again, average the data you collect to get the amount of wait you will need for each degree. Once you have figured out the details use them to write the above function. Then use the following main program:
def main(): # Transcribe a square of sides = 6 inches for side in range(4): travelStraight(6.0) degreeTurn(90.0) print "Done." main()
Run this program several times. It is unlikely that you will get a perfect square each time. This has to do with the calculations you performed as well with the variation in the robot's motors. They are not precise. Besides, it generally takes more power to move from a still stop than to keep moving. Since you have no way of controlling this, at best you can only approximate this type of behavior. Over time, you will also notice that the error will aggregate. This will become evident in doing the exercise below.
Exercise 3: Building on the ideas from the previous exercise, we could further abstract the robot's drawing behavior so that we can ask it to draw any regular polygon (given the number of sides and length of each side). Write the function:
def drawPolygon(SIDES, LENGTH): # Draw a regular polygon with SIDES number of sides and each side of length LENGTH.
Then, we can write a regular polygon drawing robot program as follows:
def main(): # Given the number of sides and the length of each side, draw a regular polygon # First ask the user for the number of sides and side length nSides = input("Enter the number of sides in the polygon you want me to draw: ") sideLength = input("Enter the length of each side (in inches): ") # Draw the polygon drawPolygon(nSides, sidelength) print "Done." main()
To test the program, first try drawing a square of sides 6 inches as in the previous exercise. Then try a triangle, a pentagon, hexagon, etc. Try a polygon with 30 sides of length 0.5 inches. What happens when you give 1 as the number of sides? What happens when you give zero (0) as the number of sides? Note your robot's behavior in each of these cases and write a short report.
Exercise 4: Write a robot program to make your Scribbler draw a five point star. [Hint: Each vertex in the star has an interior angle of 36 degrees.]
Exercise 5: Experiment with Scribbler movement commands and learn how to make it transcribe a path of any given radius. Using this, write a program to draw a circle of any input diameter.
Exercise 6: Try writing a program to draw other shapes: the outline of a house, a stadium, or create art by inserting pens of different colors (you can write the program so that the robot stops and asks you for a new color).
Exercise 7: If you had an open rectangular lawn (with no trees or obstructions in it) you could use a Zanboni like strategy to mow the lawn. Start at one end of the lawn, mow the entire length of it along the longest side, turn around and mow the entire length again, next to the previously mowed area, etc. until you are done. Write a program for your Scribbler to implement this strategy (make the Scribbler draw its path as it goes).
One way you can do some interesting things with robot drawings is to inject some randomness in how long the robot does something. Python, and most programming languages, typically provide a library for generating random numbers. Generating random numbers is an interesting process in itself but we will save that discussion for a later time. Random numbers are very useful in all kinds of computer applications, especially games and in simulating real life phenomena (as in estimating how many cars might be entering an already crowded highway in the peak of rush hour? etc.). In order to access the random number generating functions in Python you have to import the random library:
from random import *
There are lots of features available in this library but we will restrict ourselves with just functions for now: random and ranrange. These are described below:
- Functions for generating random numbers
- random() Each time you invoke this function it returns a random bunber between 0.0 and 1.0.
- randrange(A, B) Returns a random number in the range [A..B-1].
Here is a sample interaction with these two fuctions:
As you can see, using the random number library is easy enough, similar to using the Myro library for robot commands. Given the two functions, it is entirely up to you how you use them. Look at the program below:
def main(): # generate 10 random polygons for poly in range(10): # generate a random polygon and draw it userInput = input("Place a new color in the pen port and enter any number: ") sides = randrange(3, 8) size = randrange(2, 6) drawPolygon(sides, size) # generate a random walk of 20 steps for step in range(20): travelStraight(random()) degreeTurn(randrange(0, 360))
The first loop in the program draws 10 random polygons. The second loop carries out a random walk of 20 steps.
Python: Asking Questions?
Let us say you wanted to design a Scribbler artist (see Exercise 8 below). As you can see from above, it is easy to program various kinds of movements into the Scribbler. If there is a pen in the pen port, the Scribbler draws a path. However, as an artist, that would restrict the Scribbler to draw something in only one color: whatever it is you place in its pen port. However, ss you can see in the example above, we can stop the program temporarily, pretend that we are taking some input and use that as an opportunity to change the pen and then go on. Above, we used the Python input command to accomplish this. There is a better way to do this and it uses a function provided in the Myro library:
>>> askQuestion("Are you ready?")
When this fuction is executed, a dialog window pops up as shown below:
When you press your mouse on any of the choices (yes/no), the window disappears and the function returns the name of the key selected by the user as a string. That is, if in the above window you pressed the Yes key, the function will return the value:
>>> askQuestion("Are you ready?") 'Yes'
the askQuestion command can be used in the program above as follows:
askQuestion("Change my pen to a different color and press 'Yes' when ready.")
While this is definitely more functional than our previous solution, we can actually do better. For example, what happens when the user presses the No button in the above interaction? On ething you know for sure is that the function will return the string 'No'. However, the way we are using this function, it really does not matter which key the user presses. askQuestion is designed so it can be customized by you so that you can specify how many button choices you want to have in the dialog window as well as what the names of those buttons would be. Here is an illustration of how you would write a better version of the above command:
askQuestion("Change my pen to a different color and press 'OK' when ready", ["OK"])
Now this is certainly better. Notice that the function askQuestion can be used with either one parameter or two. If only one parameter is specified, then the default behavior of the funtion is to offer two button choices: 'Yes' and 'No'. However, using the second parameter you can specifyy, in a list, any number of strings that will become the choice buttons. For example,
askQuestion("What is your favorite ice cream flavor?", ["Vanilla", "Chocolate", "Mango", "Hazelnut", "Other"])
This will be a very handy function to use in many different situations. In the next exercise, try and use this function so that you are familiar with it.
Exercise 8: Write a Scribbler program of your own that exploits the Scribbler's movements to make random drawings. Make sure you generate drawings with at least three or more colors. Because of random movements, your robot is likely to run into things and get stuck. Help your robot out by picking it up and placing it elsewhere when this happens.
In writing robot programs you have seen how placing print commands can help improve the interface of your programs. The messages printed on the screen are quite informative and are thus an essential component of the user interface of the program. Most programs are really written not just to be used by their programmers but other people as well. Most of the time when other people use your program they may not be interested in how your program works or the inner details of your program. Thus, by printing informative statements from your program you can create a more usable interface to your program. The input commands also take messages that are printed out as user prompts. The same is also the case for the askQuestion command. In general your goal in writing robot programs should be to make the interface as friendly and useful and informative as possible. In that vein, the Myro library also includes some additional functions that can be used to extend the modalities using which your program/robot interacts with you or the user. You can actually use speech as an output modality. For example, try the command:
>>> speak("Top of the morning to you!")
Your computer has a speech generation system built into it. Though it is rudimentary, you will find it quite useful (and entertaining). When you issue the above command, a default voice assigned to the system is used to speak out the text you specify. You can find out the name of the default speaker in your system:
>>> getVoice() 'MSSam'
That is, 'MSSam' is currently the assigned speech model. In most systems, you get a choice of several speakers. You can find out what other options are available:
>>> getVoices() ['MSMike', 'MSMary', 'MSSam', 'LHMICHAEL', 'LHMICHELLE']
On my computer, I have the above 5 choices available. You can select a specific speaker out of these using the setVoice command:
>>> setVoice('MSMary') >>> speak("Change my pen to a different color and press 'OK' when ready")
You can even make this a choice for the user by defining a new fucntion:
def pickVoice(): setVoice(askQuestion("Pick a voice for me, please.", getVoices()))
Take a careful look at the above definition and make sure you understand how it works. Each time you call pickVoice, you will get a dialog window:
Then, depending on which button you select, the string returned by askQuestion is used to set the voice. Try writing your robot program so that they make good use of these different output modalities.
Talk among yourselves
What kinds of things can your robot talk about? A robot could report back its state, for example, by saying things like "I see light on the left" or "There is an obstacle in front of me."
But the robot can also "talk" about other things, like the time or the weather.
If you wanted to get the current time and date, the most easiest way might be to import another Python library called "time". You can do that with:
>>> import time
You can then use the function called "localtime" like so:
Localtime returns all of the following in order:
- day of the year
- whether it is using daylight savings time, or not
>>> time.localtime() (2007, 5, 29, 12, 15, 49, 1, 149, 1)
In this example, it is May 29, 2007 at 12:15pm and 49 seconds. It is also the 1st day of the week, 149 day of the year, and we are using daylight savings time.
Exercise 9: Modify your program from Exercise 8 to make use of speech. Make the robot, as it is carrying out random movements, to speak out what it is doing. As a result you will have a robot artist that you have created!
Proprioception: Internal Clock
As you saw earlier, proprioception or internal sensing mechanisms are built into humans as well as robots. The wait command, for instance, causes the robot to wait for the specfied amount of seconds before proceeding on to the next command in a program. This also implies that there must be an internal clock mechanism built into the robot. In fact, internal clocks are an essential component of any computing device. Most programming languages also allow you to access this internal clock to keep track of time, or time elapsed (as in a stop watch), or in any other way you may want to make use of time (as in the case of the wait) function. The Myro library provides a simple function that can be used to retreive the current time:
>>> currentTime() 1169237231.836
The value returned by currentTime is a number that represents the number of seconds elapsed since some earlier time (whatever that is). Try issuing the command several times and you will notice that the difference in the values returned by the function represents the real time in seconds. For example:
>>> currentTime() 1169237351.5580001 >>> 1169237351.5580001 - 1169237231.836 119.72200012207031
That is, 119.722... seconds had elapsed between the two commands above. This provides another way for us to write robot behaviors. For example, if you wanted your robot to go forward for 3 seconds, you could either do:
startTime = currentTime() # record start time while (currentTime() - startTime) < 3.0: forward(1.0)
The second solution uses the internal clock. First, it records the start time. Next it enters the loop which says, get the current time and see if the difference between the current time and start time is less than 3.0 seconds. If so, repeat the command forward. As soon as the elapsed time gets over 3.0 seconds, the loop terminates. This is another way of using the while-loop that you learned in the previous chapter. In the last chapter, you learned that you could write a lopp that executed forever using the loop as:
while True: do something
The more general form of the while-loop is:
while <some condition>: do something
That is, you can specify any condition in <some condition>. The condition is tested and if it results in a True value, the loop performs one more iteration. and then tests the condition again, and so on. In the example above, we use the expression:
(currentTime() - startTime) < 3.0
as the condition. If this condition is true, it implies that the elapsed time since the start is less than 3.0 seconds. If it is false, it implies that more than 3.0 seconds have elapsed and it results in a False value, and the loop stops. learning about writing such conditions is essential to writing smarter robot programs and we will return to this topic in the next chapter.
While it may seem that the first solution, using wait seemed simple enough (and it is!), you will soon discover that being able to use the internal clock as shown above provides more versatility and functionality in desiging robot behaviors. This, for example is how one could program a vacuum cleaning robot to clean a room for 60 minutes:
startTime = currentTime() while (currentTime() - startTime)/60.0 < 60.0: cleanRoom()
Now you have seen how to write robot programs that have behaviors or commands that can be repeated a fixed number of times, or forever, or for a certain duration:
# do something N times for step in range(N): do something...
# do something forever while True: do something...
# so something for some duration duration = <some time in seconds> startTime = currentTime() while (currentTime() - startTime) < duration: do something...
All of the above are useful in different situations.
Exercise 10: Rewrite your program from Exercise 9 so that the random behavior using each different pen is carried out for 30 seconds.
In this chapter you have become familiar with all of the sensors your Scribbler robot has. You also learned about proprioception or internal sensing. The wait command is one internal sensing mechanism built into the Scribber. Sensing that it has stalled is another one. You also leanred about generating random numbers and how to use them in your programs to create random behaviors. Additionally, you learned some new Python commands that enable dialog window interaction as well as speech output. By making use of the internal clock, you can also use this proprioception mechanism to design behaviors that are duration specific. That is, now you know how to write robot programs that contain behaviors that are repeated a fixed number of times, or are repeated forever, or are carried out for a specific duration. In the next chapter, we will revisit the sensors and learn about how they can be used to help the robot make decisions.
||Start the joystick window and also show the sensor values.|
||Returns a random number in the range 0.0 and 1.0.|
||A dialog window with MESSAGE-STRING is displayed with choices: 'Yes' and 'No'. Returns 'Yes' or 'No' depending on what the user selects.|
||A dialog window with MESSAGE-STRING is displayed with choices indicated in LIST-OF-OPTIONS. Returns option string depending on what the user selects.|
||Returns the name of the current speaker model (is a string like 'MSMike').|
||Returns a list of all the voices (speaker models) available in the system.|
||Sets the voice to the model 'NAME'.|
||Speaks the MESSAGE-STRING in the current speaker model.|
||The current time, in seconds from an arbitrary starting point in time, many years ago.|
||Returns a random number between 0.0 and 1.0. This function is a part of the random library in Python.|
||Returns a random number in the range A (inclusive) and B (exclusive). This function is a part of the random library in Python.|
Exercise 11: The Myro library also provides a function called, randomNumber() that returns a random number in the range 0.0 and 1.0. This is similar to the function random() from the Python library random that was introduced in this chapter. You can use either based on your own preference. You will have to import the appropriate library depending on the function you choose to use. Experiment with both to convince yourself that these two are equivalent.
Exercise 12: In reality, you only need the function random() to generate random numbers in any range. For example, you can get a random number between 1 and 6 with randRange(1,6) or as shown below:
randomValue = 1 + int(random()*6)
The function int() takes any number as its parameter, truncates it to a whole number and returns an integer. Given that random() returns values between 0.0 (inclusive) and 1.0 (exclusive), the above expression will assign a random value between 1..5 (inclusive) to randomValue. Given this example, write a new function called myRandRange() that works just like randrange():
def myRandRange(A, B): # generate a random number between A..B (just like as defined for randrange)