Skip to main content

Achoo! Crash-choo! Episode II


Some humans find it very, very hard to multi-task.  Like drink coffee, eat toast (with baked beans on top), talk and cycle... all at the same time (...and all of this just after waking up, from a bad night's sleep).  But this is where robots should be cleverer than humans.  They can do many things, at the same time (sort of).  Or they do a little bit of each thing so extremely quickly that it seems like they are doing it all simultaneously.  But, regardless, we still like to think we are cleverer than robots.  And we like to be bossy.  So we're going to order them to be better than us... and multi-task.

Let's remind ourselves:  We were able to move Rosie by controlling her remotely.  Separately, we got her to alert us when there was an object in front of her.  But - I hear your say - how do we get her to do both of those things at the same time?  Could she, for example, think through all the steps needed to drink her Starbucks latte, eat beans on toast, describe to us the taste (yummy!), and compete in the Tour de France, all at the same time?  And how does she keep track of how she's progressing with each task, and work out what to do next when things (obviously) start to go wrong?  If she's now drinking beans on coffee, she needs to stop cyclingRight?

Here's the thing.  Everything we've been doing so far has been boringly sequential... we progressed through each line of Rosie's Python code embedded deep in her brain, in order, one line at a time.  That's so boring.  So yesterday.  Today, we want to get Parallel.  As per the words to this famous song from 1981*:
Let's get parallel, parallel
I want to get parallel
Let's get into parallel
Let me hear your Rosie talk
 *Not really.

You will need to have these:

  • Raspberry Pi 3 (with motors / wheels), and Raspbian running on SDHC card
  • Computer from which you are connecting to the Raspberry Pi remotely
  • Ultrasonic range (distance) sensor and 'RGB' LED to plug into Pi's GPIO pins
  • Obstacles... lots of them

You will need to have done these things:

  1. I’ve got Pi(3) brain
  2. Why-Fi-ght the Wi-Fi?
  3. Hurrah-ndom inventions
  4. Don't reinvent the eel
  5. Achoo! Crash-choo! Episode I

You will need to do this:

  • Adapt the Python program to use a thread
  • Try and make Rosie crash!

What do you get after all this?

You may not have noticed it, but we were walking (or in the case of Rosie, crashing) into a big problem.  And that problem wasn't (for once) Tammy, the cat.

We can control Rosie remotely using our keyboard (over SSH).  And our grand plan was just to combine that bit of code with our code for the distance sensor.  Rosie can then be controlled, but also detect obstacles, at the same time.  Easy.  We just copy and paste in the new code somewhere before or after the existing code.  And we can move onto more interesting things, right?

No way, Jose.  Esto no es tan simple.  And this is why.  The code that takes keyboard input halts, and waits (patiently) on a line of code that requires a human - yeah, that's you - to enter an arrow key before carrying on.  So while it's waiting, it won't be able to do any other useful stuff, like check for approaching alien spaceships using the distance sensor.  And, likewise, once the motors have been instructed to move forwards, Rosie's application will continue to remain paused, until instructed to stop or change direction, using another keyboard key.  There's no background monitoring during these pauses.  We don't get our LED colour changes.  We don't get our auto-break.  That's right... Rosie will continue to crash into Tammy.  Booo!  It's called Blocking, and we don't like it.

So we think.  And we think some more...  What we would actually like to do is run two sets of code in parallel; one in the background, constantly checking the distance sensor, and another (the main program) that waits for, and processes, your input.  And crucially, the main program should have access to what the other bit of code is reporting back so that it can use that information to help it make decisions.

There are many different techniques in programming to achieve this; but in Python, the two most common solutions involve the use of Processes or Threads - mechanisms that seemingly allow bits of code to run in parallel.  There are endless discussions on the Internet about the differences between the two, and why you might use one or the other.  But we don't like the sound of processes (we like to make things up as we go along, you know, break the rules) so let's use threads instead.

Confused already?  Of course.  But is it all worth it?

Yes!  Because when it's all working, Rosie appears to be doing a lot more than just accepting our demands.  She is thinking for herself in the background, and constantly trying to stop us from making the wrong decisions.

This is simply too much detail:

Let's see some Blocking in action.  Temporarily create a file called 'blocking.py' (aren't we being imaginative today?) and place the following lines in the file:

touch blocking.py
nano blocking.py
...creates 'blocking.py' Python application file and open in nano

print("ROSIE: Where would you like to go now?")
destination = raw_input()
print("ROSIE: Ok, let's go to...")
print(destination)


If you now run this, you'll notice that it is waiting for you to enter something.

python blocking.py
...runs the 'blocking.py' Python code


It's only once you enter that something (and hit 'Enter') that the code carries on and completes.  This makes sense in this case, because carrying on without having the destination from you is pointless.  Where would you go?


We can also confirm this by running Python interactively. 

Sometimes we don't want to create an application (the .py file) to test our code.  We just want to run some Python commands in real-time, and see what it does.  There is nothing stopping you from running Python interactively.  To do this, from the Linux shell, simply type:

python
...launches Python shell


Here, you can run any Python code you like - interactively - to see what happens.  Use the function exit() to leave the shell at any time.  For example:

message = "Welcome to the world of Python shell"
print(message)
exit()


Start up the Python shell again.  Now, let's just type in the following line from the 'blocking.py' file:

destination = raw_input()


There; we have confirmed that the raw_input() function is Blocking, and that it waits until someone enters something.  All very useless, if you have other things that you need your robot to be thinking about and doing in the meantime.

Now, time to meet the latest of your Python friends: Thread.  He's a bit difficult.  He's moody.  He likes to work alone in most cases.  But you'll grow to like him.  After all, he has special powers; he allows one section of your code to magically run independently. Now would be a great time to ask him to do just that.

Create a new Python file called 'rosie_wheels_with_distance.py'.

touch rosie_wheels_with_distance.py
nano rosie_wheels_with_distance.py
...creates 'rosie_wheels_with_distance.py' Python application file and open using nano text editor... but you knew this already!

This is the code that hopefully achieves our aims (for now):

Most of the code will look familiar, as it is simply an ugly mash-up of 'rosie_wheels.py' and 'rosie_distance.py'.  The main points of interest are listed out below.

First things first.  We need the threading module to use... wait for it... threads.   If you are using Processes instead, the module would be called 'multiprocessing'.  Read up on the differences between threads and processes before choosing which one to use.

from threading import Thread

A new function check_distance() is created.  This essentially contains the bulk of our code from 'rosie_distance.py' which checks distance sensor input, and changes Rosie's alert level.  It's a busy function: it also does the LED colour changing, and the emergency stopping of motors.  We would like the function to be run as a thread, in the background, infinitely, regardless of what we are telling Rosie to do.  It's, after all, critical to her safety.

def check_distance():
    #...code to check distance, change alert level, change LED colour, stop motors...

A variable to track Rosie's alert level (0 = normal, 1 = warning, 2 = danger) is declared as being 'global'.  This is to allow the main program (more on this later) to receive Rosie's alert level from the background thread.

global alert_level

There are other (more sophisticated, and probably correct!) ways to share data between threads, the most common being Queues, but we'll stick to using a global variable for now.  But remember, running threads can cause a lot of havoc if you don't keep track of what they are all doing, and to what, and when.

And the rest of the program, borrowed heavily from 'rosie_wheels.py', ends up in the 'main' part of the program.  In Python, this is defined using a bizarre bit of code.  Don't ask.  Just accept that this is how you define the main section of your program which only gets executed when running your program directly.

if __name__ == "__main__":
    #...bunch of code to do what you want the application to do

Oh, yes.  If you want the thread to now start, don't forget to create an instance of it, and map it back to our check_distance() function.

t = Thread(target = check_distance)
t.daemon = True
t.start()

The rest of the content here is pretty much the same as the original 'rosie_wheels.py' file (which in turn is based on the sample '08_manual_robot_continuous.py' program from GitHub), with the exception of:

if alert_level != 2:
    rr.forward(0, 0.5)
    print("ROSIE: I'm moving forwards.\r")
else:
    print("ROSIE: For your safety, forward is currently disabled.\r")

Here, Rosie is simply refusing to follow instructions when the 'UP' key is pressed, if the last alert level provided by the thread, running check_distance() loop, was 'danger' (2).   How sensible of her?

And that's it.  With a bit of threading, we have made a dramatic improvement to Rosie's IQ.  Tell us, who is cleverer now - you or Rosie?

Run the program to find out!


Even more reading:

Python's official documentation on threading module:
A reminder that our particular implementation makes use of the following GitHub contributions for lower-level interaction with the hardware that we are using:

Comments

  1. Pretty cool! My "Posie" enjoying a Rosie Mind Meld.

    pi@RWPi:~/RWPi $ python posie_wheels_with_distance.py
    RRB3__init__
    New state: STARTUP
    pollBumpers started with 0.100 interval
    UltrasonicDistance: readingsPerSec: 10
    pollUltrasonicDistance started with 0.100s cycle
    UltrasonicDistance: reading thread told to start
    Motors: worker thread readingsPerSec: 20
    Motors: pollMotors thread started with 0.050000 at 2018-02-19 10:32:38.538830
    waiting for threads to start

    ROSIE: I'm starting up...
    Spoken: I'm starting up

    ROSIE: I've started my distance sensor...
    ROSIE: Startup complete. Controls enabled.

    I've started my distance sensor
    -------------------------------------------
    Use the arrow keys to move ROSIE
    Press CTRL-c to quit the program
    -------------------------------------------
    ROSIE: I'm turning left.
    Spoken: I'm turning left.

    ROSIE: Too close! Too close!
    Spoken: Too close! Too close!

    ROSIE: It's all good.
    Spoken: It's all good.

    ROSIE: I'm turning right.
    Spoken: I'm turning right.

    ROSIE: I'm moving forwards.
    Spoken: I'm moving forward

    ROSIE: I'm going backwards.
    Spoken: I'm going backwards.

    ROSIE: keyp == 3 detected
    RRB3.cancel() called
    Motors.cancel() called
    Waiting for Motors.control Thread to quit
    New state: SHUTDOWN

    ********* RWPi STATUS *****
    2018-02-19 10:33:49 up 55 min, 4 users, load average: 0.02, 0.06, 0.08
    battery.volts(): 7.7
    battery.hoursOfLifeRemaining(): 8 h 25 m
    currentsensor.current_sense(): 481 mA
    Processor Temp: 55.8'C
    Clock Frequency: 600 MHz
    throttled=0x0
    irDistance.inInches: 14.7
    usDistance.inInches: 33.6
    bumpers: NONE


    bumpers.cancel() called
    UltrasonicDistance.cancel() called
    Waiting for UtrasonicDistance.readThread to quit
    do_run went false. Stopping pollUltrasonicDistance thread
    New state: DONE
    myPDALib.PiExit(): PDALib.pi.stop() called
    do_run went false. Stopping pollBumpers thread
    pi@RWPi:~/RWPi $

    ("Posie" uses the Pi Droid Alpha interface card that provides 16 digital I/O, 8 analog-digital-conversion, and H-Bridge motor control. http://www.mikronauts.com/raspberry-pi/pi-droid-alpha/)

    Posie thinks Rosie is a great robot friend to have.

    ReplyDelete

Post a Comment

MOST VISITED (APPARENTLY)

LoRa-Wan Kenobi

In the regurgitated words of Michael BublĂ©: It's a new dawn .  It's a new day .  It's a new Star Wars film .  For me .  And I'm (George Lucas, and I'm) feeling good .  Unfortunately for Canadian Mike, the Grammy that year was won by the novelty disco classic with the famous refrain: We love IoT, even in Planet Tatooine * . *Not true. Clearly, the Star Wars producers didn't sincerely mean the last Jedi the previous time around.  Return of the Jedi, released during the decade that spearheaded cultural renaissance 2.0 with the mullet and hair-metal , was less economic with the truth.  Either way, we're going to take inspiration from the impressive longevity of the money-spinning space-opera and reboot our franchise with some Jedi mind tricks.  Except this particular flick doesn't require an ever-growing cast of unrecognisable characters, unless ASCII or UTF counts.  In place of an ensemble gathering of Hollywood stars and starlets, we will b

Battle of BLEtain

The trolling . The doxing . An army of perplexing emojis. And endless links to the same - supposedly funny - viral video of a cat confusing a reflection from a dangling key for a golden hamster, while taking part in the mice bucket challenge. Has social media really been this immense force for good? Has it actually contributed significantly to the continued enlightenment of the human (or feline) race? In order to answer these poignant existential questions about the role of prominent platforms such as Critter, StinkedIn and Binterest, employing exceptional scientific rigour equal to that demonstrated by Theranos , we're going to set up a ground-breaking experiment using the Bluetooth Low Energy feature of MicroPython v1.12, and two ESP32 development boards with inexplicable hatred for one another.  And let them hurl quintessentially British expressions (others call them abuse) at each other like two Wiltshire residents who have had their internet access curbed by the co

Hard grapht

You would all be forgiven for assuming that bar , pie and queue line are favourite pastimes of the British .  Yet, in fact – yes, we did learn this back in GCSE maths – they are also mechanisms through which meaningless, mundane data of suspect origin can be given a Gok Wan -grade makeover, with the prime objective of padding out biblical 187-page PowerPoint presentations and 871-page Word reports (*other Microsoft productivity tools are available).  In other words, documents that nobody has the intention of ever reading.  But it becomes apparent over the years; this is perhaps the one skill which serves you well for a lifetime in certain careers.  In sales.  Consultancy.  Politics.  Or any other profession in which the only known entry requirement is the ability to chat loudly over a whizzy graph of dubious quality and value, preferably while frantically waving your arms around. Nevertheless, we are acutely conscious of the fact that we have spent an inordinate amount