Skip to main content

Abject, disorientated programming


If we're honest, we've been feeling a little exhausted lately.  It's been a bit too hectic, with all the stuff that Rosie's been up to.  Don't get us wrong.  We want her to be happy, energetic and excitable... but also to be a lot more careful.  You know, not to destroy our house from top to bottom.

But it's also sort of our own fault too.  Rosie is... erm... (how can we be polite about this..?) a little... wide.  After all, let's not kid ourselves.  She is (for now) still just a plastic box from the local supermarket.  Of the cheapest variety.  So while she has one ultrasonic distance sensor attached to her front - being positioned in the middle of her very un-aerodynamic, flat body - its coverage isn't exactly great.  In fact, we've concluded: it's actually terrible.  And that's why we think she has table legs for breakfast, or wrestles gnomes in the garden, or chooses to play tag with the toilet brush (yuck!).  All very silly robot pastimes.  So silly, in fact, that she needs to be upgraded.

So we've decided.  Like cheeseburgers.  Chocolate bars.   Scoops of ice cream.  Two of something is almost always better than one (just ask your dentist).  With two HC-SR04 ultrasonic distance sensors, Rosie should have more information about the world around her.  She should know a lot more.  She might even be able to detect incoming objects, better.

Therefore, let's double up on our ultrasonic-ness.  Make our ultrasound code, well... more sound.  And dial up the volume of our sound waves in the name of Pi-ence.  And just maybe, it'll be just what prompts Rosie to find a better hobby.  One that doesn't involve broken tables, angry garden gnomes, and a dangerous liaison with a very filthy toilet brush.

You will need to have these:

  • Raspberry Pi 3 (kitted out with wheels), and Raspbian running on SDHC card
  • Computer from which you are connecting to the Raspberry Pi remotely
  • 2 × HC-SR04 ultrasonic distance sensors.  How many?  Dos.  Deux.  二.  두.  Oh, did we mention we need two of these? 
  • Some resistors, soldering iron, solder, cables and heat shrink if you want to take this seriously.  Very seriously.  More on these mysterious items later.

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
  6. Achoo! Crash-choo! Episode II
  7. Web of Pies
  8. Hello supervision, goodbye supervision

You will need to do this:

  • Attach a second HC-SR04 ultrasonic distance sensor to the Pi's GPIO pins, using a voltage divider circuit if required (it's probably the case unless you are using a dedicated module)
  • Modify the Python code to create a class for the ultrasonic distance sensor 
  • Adapt the code to make use of the class, and the second sensor
  • Challenge the robot to crash into something

What do you get after all this?

In this episode, we briefly return to the mystical land of electronics.  Mainly, it's because unlike our first HC-SR04 ultrasonic distance sensor that plugged directly into designated GPIO pins on the RasPiRobot V3 motor controller board, with the second one, we're very much on our own.  Oh yes, and it's also an excuse to solder some stuff together using a soldering iron.

We can learn here, that a HC-SR04 ultrasonic distance sensor outputs a 5 volt electrical signal on its "echo" pin when the sensor detects that a pulse sent via the "trigger" pin was returned.  But we also learn that Raspberry Pi GPIO pins - when configured as an input pin - expects electrical signals back at 3.3 volts.  That doesn't sound very good?  No, it's the perfect recipe for some zapped circuitry (or so we're told and afraid to find out).  And that's why we need to use two Resistors to create a Voltage Divider Circuit to convert the 5V output from the sensor (echo) into 3.3V.  Time to solder some resistors to wires and pretend to know a whole lot more about electronics.

Then, once we've connected our sensor (together with the home-made voltage divider circuit) to our spare GPIO pins, we want to modify Rosie's Python code to make use of it.  But before we do, we want to abandon RasPiRobot V3 motor controller board libraries' own function to interact with the sensor, and create our own.  And when we do, we'll use a dash of Object Orientated Programming in Python, using Classes.  This way, whether we have 1 distance sensor on our Rosie, or 1 million on Rosie v2100 Mark III Mars Explorer Edition, we can reuse our single class for each Instance of our sensor.

So that's how the theory goes.  Two ultrasonic distance sensors + more Python code = ∞ of Rosie happiness.  Let's put this into practice and see what happens.

This is simply too much detail:

Who wants more cables?  Better still, who wants random bits of electronic circuity attached to their robots?  If your answers to these questions are emphatic yeses, it's time to proceed to our next stage.

Thankfully for us, back when people were still wearing top hats and waistcoats, some very clever people worked out what the relationship is between very scientific stuff; like electrical power (measured in Watts - W), voltage (measured in Volts - V), current (measured in Amps - A) and resistance (measured in Ohms - Ω*).

*Yes, that is actually the symbol for a measure of electrical resistance.  We didn't make this one up.

You may have heard of some equations, like Voltage (V) = Current (I) × Resistance (R), otherwise known as Ohm's Law.  Georg Ohm's important discovery pretty much underpins all of electronics, so we might as well know about it.

V = I × R

How does this help us with Rosie?  It doesn't (not right now).  So let's move onto another boring equation.  We want to reduce our 5V "echo" output down to 3.3V.  There's an equation that helps us with this too:

Vout = Vin × R2 / (R1 + R2)

The principles are helpfully explained here (and yes, Ohm's Law makes an appearance yet again).  In short, we want two resistors (a R1 and a R2) that if we connect together, provide us with a voltage of 3.3V at the point at which they connect.  As explained here, a 1kΩ resistor for R1 and a 2kΩ resistor for R2, provide us with a Vout value which is exactly two thirds of Vin.  So the 5V Vin, helpfully becomes, approximately, 3.3V Vout when this sensor output is "high".  Just what we need.  Thanks Mr. Ohms.  You really are the reason why we're getting on grandly with our Raspberry Pi.

Now we could create this voltage divider circuit in lots of different ways.  It could be built in to the circuit board, like it is for the dedicated ultrasonic distance sensor pins on the RasPiRobot V3 motor controller board.  Or we could use, for example, a breadbord (nothing to do with bread, of the wheat variety).  But you wanted to solder some stuff, right?  Sure.  We did too.  So we simply soldered one end of the 1kΩ resistor to the cable attached to the HC-SR04 ultrasonic distance sensor's echo pin, then soldered the 2kΩ resistor to the other end of the 1kΩ resistor.  The remaining end of the 2kΩ resistor was then soldered to the ground (0V) cable.  Crude.  Yes.  But quite effective, we found.

Here are the pins in question:

Pi GPIOHC-SR04
Any 5V (e.g. 2, 4)Vcc
33 (BCM 19)Trigger
37 (BCM 26)Echo (via voltage divider circuit)
Any 0V Ground (e.g. 6, 9, 14, 20, 25, 30, 34, 39)0V (Ground)

Where's the 3.3V output then, I hear you ask?  It's where the two resistors meet, and this is where we need to solder a new cable our for our "actual" echo pin that we connect down to the Pi's GPIO pin.  All the joints were finally covered in heat shrink, and come to think of it, so were the resistors too.
 


And here's an important public service announcement:

Soldering can be dangerous.  Like many things that are dangerous, it involves heat, and some tools (but not stunt bikes, or lethal animals... except later, a Python).  So don't do it by yourself if you've never done it before, or don't know what you're doing.
And if you're what others consider a child, always have an adult there to help you.  This way, you can laugh at their mistakes, rather than be in trouble for making them.

Here's is a not a very exciting photo of our test rig.


...And more professional looking schematics relating to this additional ultrasonic sensor.


Great.  We now have random electronic bits hanging out of our plastic box.  And we think we've learnt some useful things about electricity.  What else do we need to do?

We did promise you some Python action in this post.  So here it is.

So far, we used the RasPiRobot V3 motor controller board's own library for our distance checking.  This made sense, as we were using the GPIO pins on the motor controller board for the HC-SR04, and we only had one of it.  That way, we were able to use the rrb3 module's get_distance() function to obtain our distance readings from the connected sensor.  Simples.

...But now that we need to use GPIO pins that are not on the motor controller board, and on the Pi itself, we ought to create our own bit of code to do the same thing.  We also now intend to have two sensors, but we may decide to have more in the future.  We might even stop using the RasPiRobot V3 motor controller board someday.  So creating reusable Python code that represents what a HC-SR04 sensor is and does seems like something that will save us time in the future.  We could then keep using the same bit of code, for each sensor we attach, using any supported GPIO pin of our choosing.

Aha!  We must be talking Object Orientated Programming?  We sure are.  Many programming languages support it.  Python does too.  And, surprisingly, we were actually already using it when we were doing this with our code to create specific instances of a RasPiRobot V3 motor controller board, or a RGB LED (aka 'the squid').  Only, we were using code that others had created for us.

rr = RRB3(9.0, 6.0)
rgb = Squid(16, 20, 21)

Object Orientated Programming (OOP) allows the coder - yes, that's now you - to define and use... guess what... Objects.  That's why it's called Object Orientated Programming, and not Meerkat Orientated Programming, as there are no meerkats to be seen during programming.  And how do we describe an object?  An object probably does something (OOP calls these functions, Methods).  Or it has probably got some attributes (Instance Variables).  These blueprints for Objects are called Classes.  And using a class, we can create many Instances of that object.  Confused?  Let's call on some furry friends to help out.

...And for some reason, we still have meerkats in our heads.  So here's an example of a meerkat class:

class Meerkat:
    def __init__(self, name, age, fur_colour):
        self.name = name
        self.age = age
        self.fur_colour = fur_colour
    def grow_older(self):
        self.age += 1
        print(self.name, "is now aged", self.age)

During initialisation of a specific instance of a meerkat (using this class as a template), we give our meerkat a name, age and fur colour.  These will be unique to each instance of a meerkat.  Meerkats can also grow old (really, they do - they don't stay cute forever), so we have a method (basically a function in the class) that adds one year to that meerkat's age.  We can now create many imaginary meerkats, and make them grow older, together.  Pretty pointless, we realise.

m1 = Meerkat("Bob", 2, "Brown")
m2 = Meerkat("Charleen", 5, "Golden")
m1.grow_older()
m2.grow_older()
print(m1.age)
print(m2.age)


We made some imaginary meerkats - christened Bob and Charleen - grow one year older using Python code.  Let's move swiftly on *move on people, nothing to see here* and actually do something useful with OOP and classes.

So what does a distance sensor need to be able to do?  Take temperature?  No - check distance, of course.  And all distance sensors need to do just that, so let's make those bits of Python code methods.  What else do we need to know about our distance sensors to use them?  The Raspberry Pi GPIO pins each uses for "echo" and "trigger" signals are pretty important, so that we know which pins to send our 'trigger' signal to, and from which pins we expect back the 'echo' signal.  Like the meerkats, perhaps we can provide a meaningful description too for our instances of the sensor, like about where they are positioned.  These can be our Instance Variables.

Let's give this a go.  First, we'll create a DistanceSensor class, which has been largely stolen borrowed from the bits relevant to the distance sensor of the RRB3 class.  It consists of a special "__init__" method which allows us to initially assign out our GPIO pins, and description, followed by a bunch of other methods to send a trigger to the sensor on the 'trigger' pin, and wait for a response on the "echo" pin.  If you look closely, there are other stuff that happens in the class, to work out the distance to the nearest object using the speed of sound*, and the time difference between the trigger and echo signals, but let's pretend that we understand all that for now.

*For those that might find this fact interesting, speed of sound through air (i.e. your robot hasn't been launched into space yet) actually differs depending on environmental factors such as temperature. You can use an online calculator such as the one here, but if you want to take this experiment further, you could temporarily skip forward to a later project of ours where we use a temperature sensor connected to the Pi.  From its reading, you can (allegedly) use the following equation if the air is dry like our humour:

v = 331 + 0.6 × T

...where v is the (speed of sound), and T the temperature in Celsius (c).

Now back to our class (quite literally).

class DistanceSensor:
    #... 
    def __init__(self, sensor_description = None, GPIO_PIN_TRIGGER = None, GPIO_PIN_ECHO = None):
        self.GPIO_PIN_TRIGGER = GPIO_PIN_TRIGGER
        self.GPIO_PIN_ECHO = GPIO_PIN_ECHO
        self.sensor_description = sensor_description
        if (self.GPIO_PIN_TRIGGER == None) or (self.GPIO_PIN_ECHO == None):
            raise ValueError("Values must be provided for both Trigger and Echo GPIO pins")
        gpio.setmode(gpio.BCM)
        gpio.setwarnings(False)
        gpio.setup(self.GPIO_PIN_TRIGGER, gpio.OUT)
        gpio.setup(self.GPIO_PIN_ECHO, gpio.IN)
        #...then all our other methods to do stuff with the sensor

So like our friendly meerkats Bob (m1) and Charleen (m2), in our existing check_distance() function, we now create two instances of a DistanceSensor class - _ds1 and _ds2.  For each, during instantiation, we set their unique descriptions (no, we don't actually have to call them Bob and Charleen), and GPIO pins they use for echo and trigger signals.  We've also wrapped the object instantiations using the try / except clauses.  It should allow us to capture any exceptions (errors) raised by our class.  In fact, from the code above, you'll notice that if no GPIO pin numbers are provided, the class will raise a ValueError.  Our bit of exception handling will exit the program, as we don't want Rosie travelling around the world without her distance sensors working correctly.

try:
    _ds1 = DistanceSensor("Front left HC-SR04", 18, 23)
    _ds2 = DistanceSensor("Front right HC-SR04", 19, 26)
except Exception as e:
    print(e)
    sys.exit(1)

Whenever we want to get our distances from our ultrasonic sensors (_d1 and _d2), we can now do this to assign their recordings to our two variables, _distance_fl_cm and _distance_fr_cm:

_distance_fl_cm = _ds1.get_distance()
_distance_fr_cm = _ds2.get_distance()

...And because we want to change Rosie's alert level when either of the two sensors notice something interesting (and not necessarily both), we have changed the if statements to take into consideration this logic.

if ((_distance_fl_cm < _DISTANCE_DANGER_CM) and (_distance_fl_cm_last < _DISTANCE_DANGER_CM)) or ((_distance_fr_cm < _DISTANCE_DANGER_CM) and (_distance_fr_cm_last < _DISTANCE_DANGER_CM)):
    #...code to change alert level to critical, if not already
elif ((_distance_fl_cm < _DISTANCE_WARNING_CM) and (_distance_fl_cm_last < _DISTANCE_WARNING_CM)) or ((_distance_fr_cm < _DISTANCE_WARNING_CM) and (_distance_fr_cm_last < _DISTANCE_WARNING_CM)):
    #...code to change alert level to warning, if not already
elif ((_distance_fl_cm >= _DISTANCE_WARNING_CM) and (_distance_fl_cm_last >= _DISTANCE_WARNING_CM)) or ((_distance_fr_cm >= _DISTANCE_WARNING_CM) and (_distance_fr_cm_last >= _DISTANCE_WARNING_CM)):
    #...code to change alert level to normal, if not already

We've incorporated all this into our existing code, and named it "rosie-web-multisensor.py". It looks a little something like this:

And that's about it.  We're not changing any of our Flask code for web access, or the HTML, or CSS.  The end result is subtle, but we've actually done a whole load of things behind scenes to make our code more reusable.  And using Python Object Orientated Programming, we can make Rosie's application a lot more powerful.

Now, let's return to name some more of our imaginary meerkats...

Even more reading:

There is excellent information on the technical specs of the HC-SR04 sensor, and the voltage divider circuit required, on the ModMyPi website.  Highly recommended reading if you are intending to do some soldering of your own.
Official Raspberry Pi information on GPIO pins can be found here:
Python's official documentation on classes can be found here:
We're continuing to make use of the Monk Makes' RasPiRobot V3 motor controller board, and its libraries written by Simon Monk:

Comments

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 starlet...

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 w...