When someone is about to sneeze in your face, you try and stop them. And, when a robot is about to crash into your sleeping cat, Tammy, you
We built something that roams around exactly how you tell it to. But did you notice something? If you asked it to do something stupid (sorry, Tammy), it did it without asking. That doesn't sound very much like human behaviour to us (or does it?) We want Rosie to be more careful, and to not listen to us all of the time. After all, robots should surely be cleverer than us humans, right? Help us make the right decisions, more often than not.
There are many possible ways to prevent a collision with poor old Tammy (the guinea cat). If it does what it says on the tin, a 'distance sensor' sounds like a good place to start. Because if you are getting closer to something - generally speaking - you should slow down, or stop.
So let's find Rosie (and Tammy!) a crash helmet, knee-pads and take out some special robot insurance. It's time to make her - NOT - crash into things.
Be warned, though, as there is more to this than it first seems. Like all good superhero movies, this post will have a sequel or two. It's because what should be simple, never is. Don't worry: you'll always know when you've encountered a frustrating Python conundrum that you have to just, erm, crash through.
You will need to have these:
- Raspberry Pi 3, and Raspbian running on SDHC card
- Computer from which you are connecting to the Raspberry Pi remotely
- Ultrasonic range (distance) sensor to plug into Pi's GPIO pins
- 'RGB' LED, also to plug into Pi's GPIO pins
You will need to have done these things:
You will need to do this:
- Connect the ultrasonic range (distance) sensor to Pi's GPIO pins
- Connect RGB LED to Pi's GPIO pins
- Create a Python program, using Control Flow statements
What do you get after all this?
A Distance or Range Sensor uses ultrasound to calculate the distance to an object using echoes that are returned after hitting a surface. Us humans can't see or hear it happening. If we want to predict that a collision is likely to happen, warning when an object is near enough to be a threat seems like one of many approaches.The distance being recorded by the sensor will be an input to our program. But what do we do with it? Well, quite frankly, we're feeling ambitious. Very ambitious. Let's try and do two things(!!!):
- What do you see in action movies when something very bad is about to happen (like when baddies have entered the secret hideout)? Yes. A big red light comes on. It's scary. It's exciting. And it tells you all you need to know (that... something bad is about to happen). So let's start with a blue light, turn it yellow as Rosie approaches an object, and finally, turn it red when Rosie thinks she is way too close. Then we know that Rosie knows. She knows that we know. And we know that she knows that we know. And she knows that...
- Are you still controlling your robot manually using your controls? Great (it hasn't just become human-grade intelligent overnight then). Let's try and prevent you - silly human - from doing what humans like to do: ignore warnings... like red lights that obviously mean bad. Disabling the 'forward' control might be one way to do it, forcing you to only turn left, right or back.
For now, we are leaving behind the sample programs included with the motor controller board, and 'borrowing' ideas to create our own. And because we are now telling the program to make its own decisions based on input, we are about to require Python's useful Control Flow statements. No ifs. No buts (note to reader: there is really actually no but statements in Python).
Let's proceed to make robots that do (semi-)intelligent things.
This is simply too much detail:
First things first. If you want to use your distance sensor with your Pi, you will need to connect it to your GPIO pins somehow. It's best to refer to the instructions that came with your sensor as the pins it needs may vary. Our RasPiRobot v3 motor controller happens to have four GPIO pins earmarked for a distance sensor (HC-SR04), so we'll be using them.It's a similar story for the RGB LED, a magical light that can change colour. However, we had to find spare GPIO pins outside of the motor controller board to avoid conflict with the pins already used by the motor and distance sensor. We used GPIO pins 16, 20, and 21. Pi 3 has, in total, 26 configurable GPIO pins (GPIO2 to GPIO27), so there should be some still spare to accommodate the LED.
Everyday, we make decisions based on rules that we have inside our heads. You hurry out your house in the morning if the time on the clock is greater than 08:02. Only while you are on the bus, you read a book. And if you have money and you are thirsty, you buy a drink from the shop. Machines are no different (except, we're not too sure if they get thirsty and drink).
For no other reason than for our amusement, let's momentarily return to our 'rosie_random.py' application we wrote before. Let's make a copy of it, and call the new file 'rosie_random_with_if.py'.
cd /home/pi/rosie...navigate to 'rosie' directory where we store all our goodies
cp rosie_random.py rosie_random_with_if.py...copy existing 'rosie_random.py' file and rename it to become 'rosie_random_with_if.py'
Suddenly, you're feeling helpful. You know your little brother is definitely afraid of three-wheeled eels, so let's put an if statement at the end of the program to warn him when it appears in a suggestion. Aren't we nice?
nano rosie_random_with_if.py...edit the copied file using our
We want the code to look like this:
Let's step through the changes. Firstly, rather than print the results directly on the screen (like we did before), we want to store the random selections in three variables. This allows us to add further code to do something else with the results.
chosen_object = OBJECTS[randint(0, len(OBJECTS)-1)] chosen_activity = ACTIVITIES[randint(0, len(ACTIVITIES)-1)] chosen_gadget = GADGETS[randint(0, len(GADGETS)-1)]
As a result, when we are printing the results on-screen, we are now doing it using the variables.
print("Silly human, invent me a") print(chosen_object) print(chosen_activity) print(chosen_gadget)
The final bit of code we have added begins with an if. In this example, an if statement simply tests a variable to see if it meets a certain condition, and depending on the result, does or does not execute the code after it. In this case, we are testing to see if 'chosen_gadget' is a 'three-wheeled eel', and printing a message afterwards if it's a match.
if chosen_gadget == "three-wheeled eel": print("Oh no! Not another three-wheeled eel! Run away!")
Now, run the new application a few times and you should eventually see the helpful warning message appear for your little brother.
python rosie_random_with_if.py...surely you know how to run Python programs by now?
But where's the fun in this? Wouldn't it be more hilarious if it was always a three-wheeled eel?
We could fix the final selection in many different ways, but let's do it using a while statement.
cp rosie_random.py rosie_random_with_if_and_while.py nano rosie_random_with_if_and_while.py...make another copy of the application, called 'rosie_random_with_if_and_while.py'. Then edit it using nano to make it look like this.
These two new lines have been added immediately after the gadget has been selected.
while chosen_gadget != "three-wheeled eel": chosen_gadget = GADGETS[randint(0, len(GADGETS) - 1)]
This way if the chosen gadget is not equal to (!=) a three-wheeled eel, it will continue to randomise until it is... inside a while loop. Let's run it to see if it has the desired effect.
python rosie_random_with_if_and_while.py...surely, surely, surely you know how to run Python programs by now?
MISSION ACCOMPLISHED. Your little brother is now officially irked. He doesn't know that the program has been rigged... with a suspect while loop.
...But this is just getting too silly. Does any of this have anything to do with Rosie? It's time to move on, by creating our application. And to test whether it works, we'll just move an object in front of Rosie's distance sensor, and observe the LED change colour.
As instructed here, we need to do a few things first to be able to use the RGB LED (aka 'Squid'). Installing the Squid libraries ensures that we can interact with the RGB LED with a few simple Python functions, without needing to understand what's happening with the GPIOs behind the scenes.
git clone https://github.com/simonmonk/squid.git cd squid sudo python setup.py install...clones the squid libraries from GitHub. Then install using 'setup.py' file.
Now, we can officially use the squid libraries to easily control the RGB LED.
For the distance sensor, the RasPiRobot v3 libraries we installed for the motor controller already has a function to obtain distance readings from the sensor. It's as easy as running the function get_distance(), and we don't have to worry about the maths taking place behind the scenes.
Now, let's create our application.
touch rosie_distance.py nano rosie_distance.py...creates file called 'rosie_distance.py' and edit using nano
All ready? Let's go. Here's (very) basic code that allows us to do what we want.
There's plenty of comments in the code to help explain what's going on, but here are some of the key points.
Unlike previous applications we've written, we want this one to run forever. That's why we have majority of our code in an infinite while loop. We'll be using this technique a lot going forwards, as sensors, for example, need to keep taking readings. Don't worry, we can still exit the program at anytime, using Control+C keys.
while True: #...application code here
In order to smooth over one suspect distance reading, we have if statements testing for both the current and last distance readings. Both need to satisfy the test for an alert to be registered:
if distance < distance_danger and distance_last < distance_danger:
And we only want to know when the alert level changes. Otherwise, the program will be alerting all the time.
if alert_level != 2: alert_level = 2 rgb.set_color(RED)
Lastly, we use the sleep() function to pause the application for 0.5 seconds inside the loop. This means we only get a distance sensor reading every half a second, which means we're not stressing the application and Pi, and we can also see what's going on. In reality, if the robot is moving around quickly, we'd want this value to be much smaller to be more sensitive.
time.sleep(0.5)
And that's it. Run the program and wave objects in front of the distance sensor.
python rosie_distance.py...seriously?
Congratulations, we now have the key ingredients in place for a collision avoiding robot.
But how do we do all of this while we also control the motors? Surely robots should be able to multi-task, and do lots of different things, all at the same time? Some functions will completely stop you from running any other code until they complete (this is called blocking). A function that waits for you to enter something might just be one of those.
So in our next post, we investigate how we can make Rosie truly multi-task.
Even more reading:
Python's official documentation on control flows can be found here:Raspberry Pi's official GPIO documentation can be found here:
The motor controller board and libraries in use are from Monk Makes.
My RugWarriorPro+Raspberry Pi robot's name is Pogo, and when being a "poser" Rosie, I call it Posie.
ReplyDeleteI figured out my cntrl-c not working issue - had to add my set_cntrl_c_handler to my simulated rrb3 init().
Posie doesn't have a squid LED so I created a "print only" squid.py. Having a "mood indicator" sounds like a great idea, since one should always know when your robot is in a foul mood.
(My musing on moody robots: http://alanmcdonley.blogspot.com/2014/09/i-sense-therefore-i-feel-moody-behavior.html)
Posie does have a speaker so I hear "her" report "Too Close! Too Close!" (My text-to-speech path has a painful broadcast delay...)
Looking forward to setting Posie loose a bit in the next installment.