Summary
I did the light-speed last time, but what about sound speed? Well if you use a sonar, it turns out you can calculate the distance of objects using the timing of the echo:
def get_distance(sonar_pin):
GPIO.setup(sonar_pin, GPIO.OUT)
GPIO.output(sonar_pin, GPIO.LOW)
time.sleep(0.000002)
GPIO.output(sonar_pin, GPIO.HIGH)
time.sleep(0.00001)
GPIO.output(sonar_pin, GPIO.LOW)
GPIO.setup(sonar_pin, GPIO.IN)
start_time = time.time()
timeout = start_time + 1
while GPIO.input(sonar_pin) == 0:
if time.time() > timeout:
print("Timeout waiting for echo start")
return None
start_time = time.time()
timeout = time.time() + 1
while GPIO.input(sonar_pin) == 1:
if time.time() > timeout:
print("Timeout waiting for echo end")
return None
end_time = time.time()
duration = end_time - start_time
distance = (duration * 34300) / 2
return distance
This tech would have be considered state of the art in the 80's, but now it's a few dollars online and can be plugged to a raspi.
And what better use of this than to detect if people are passing through a giant green pipe and make a Mario sound?
Sounds like fun
Like every year, Halloween is the occasion to throw a freakishly big party with my nerdy friends, and like every year we spend about 2 months making it happen, turning the house into a temporary creative chaos of scavenged cardboard and duck tape.
While we all do a little bit of everything, some are good with their hands:
Some are creative:
And some have raspberry pis.
We strive to make the first steps into the house something special, so you can get in the mood right away, and this time the theme being video games, I stupidly decided to make a giant Mario pipe so that you can warp into fun right out of the gate.
Now, it turns out that making a structure big enough to:
let full adults go through in the dark,
survive the weather since it's outside,
resist drunks and kids plus avoid tripping them,
look like enough of a Mario pipe to be identifiable by even the most casual gamer,
and do that for about 120 persons coming and going all night long in full costume,
is actually much harder than I expected.
Because it's not the topic of this article, I won't go into details about the structure, but you know what would be totally fun?
Making a Mario-jumping-in-a-pipe noise every time someone goes through!
The setup
A teacher friend of mine had a big set of all sorts of sensors he uses at school, so he lent me the whole box. Inside were a couple of those little buggers:
Those are sonars, that can send inaudible sound waves and catch them back, then you get the signal accessible from Python. You can do all sorts of things with them, and I thought they would nicely serve as my "people-go-through-the-pipe" detector, since theoretically, they would disturb the echo when passing.
And they did. As you'll see, the code is not even that long.
The box also came with a pair of microbits, and I started hacking with those. I got a PoC working quite quickly, but then hit a wall: they don't have enough memory to hold the full Mario sound. This set me back a little and meant I had to come to the party disguised as a Sims since it was realistically the only thing I had time to do left after rewriting the thing.
Sul Sul, everyone!
I’m lying, I’m just lazy. The rewrite took 15 minutes.
So I dusted out an old raspberry pi 3b+ with more than enough SD card space to hold the entire Mario game library up to Sunshine.
For the sound, I just picked the first portable speaker with enough strength to scream over the party music that I could find, which turned out to be a Bose SoundLink mini.
Toss in a USB charger and a few cables for the pins, and we are good to go.
The assembly
The only hiccup was that the hardware documentation out there is a cruel world, full of outdated stuff, or even sometimes flat-out misleading or wrong. At first, I had the wrong Raspi pin layout and got very lucky I didn't fry the whole thing. Also, the sonars were actually not the one from the picture I've shown you above, but a subtly different model with not 4 but 3 pins and zero tutorial about it.
Gosh, I love software. You can update, hack, replace, patch, workaround and cheat. Hardware is so unforgiving, if you work in that field people, you have my respect.
Anyway, it's still Raspi stuff, so basically Lego, which means it all worked out well in the end despite my total noobiness.
Since it was a one-night event, I skipped the soldering and just plugged everything with temporary cables:
Then dump the spaghetti into a cardboard box hidden behind the belly of the Mario pipe. One little hole to position the sensor inside, glue gun the whole thing, and voilà.
I also bought a fire extinguisher, just in case.
Sound is hard, actually
So you know how you can't hear ultrasound? Well, turns out that just because you can't hear it doesn't mean it's not there, duh!
And you know how many things actually emit ultrasounds?
Me neither, but a lot more than I imagined, because the sonar regularly went off randomly without anybody passing through.
So yes, if you ever wondered, a tree falling in a forest still makes a noise if nobody is here to hear it, and it messed up my beautiful contraption. And by a tree I mean a few music tracks, people moving very precise chairs, certain types of vehicles and some animals.
Then there is the reverberation, a slightly bad angle, and boom, all the calculations are messed up. Because sounds can bounce... several times...
Sure, I knew that. Ahem.
This sonar idea was stupid. I should have used a laser and blinded the kids going through as my instinct told me to.
The very scientific solution was to find an orientation that suffers from the least of those problems, then adjust the Python script to the minimal sensitivity that would work until I got down to 1% of false positives. Which is enough for a party.
Also, the speaker designers smartly built it so that after a while, it would save battery by going to sleep, and you had to manually wake it up. So I added an aplay
call to crontab
to play "It's-a-me, Mario" every minute.
I'm a professional.
The code
One pip install RPi.GPIO pygame
and bit of sshing later, here is the code that went into this IoT wonder:
import RPi.GPIO as GPIO
import time
import pygame
import threading
import os, sys
# Set SDL to use the dummy NULL video driver,
# so it doesn't need a windowing system since the raspi
# is headless and somehow doc though it would be a good
# idea to mention it
os.environ["SDL_VIDEODRIVER"] = "dummy"
# Preload the sound system, the sound file and make it audible
pygame.mixer.pre_init(frequency=44100, size=-16, channels=2)
pygame.init()
pygame.mixer.init()
pygame.mixer.music.load("pipe.mp3")
pygame.mixer.music.set_volume(1.0)
# Those are the PINS we get the signal from
GPIO.setmode(GPIO.BCM)
SONAR1_PIN = 8
SONAR2_PIN = 4
def get_distance(sonar_pin):
"""Get distance of an echo from a sonar"""
# Because our sonars are 3 pins model,
# we have to use the same pin in output
# or input mode to read/write the signal
GPIO.setup(sonar_pin, GPIO.OUT)
# We send some sound
GPIO.output(sonar_pin, GPIO.LOW)
time.sleep(0.000002) # totally not arbitrary
GPIO.output(sonar_pin, GPIO.HIGH)
time.sleep(0.00001)
GPIO.output(sonar_pin, GPIO.LOW)
# Then we read
GPIO.setup(sonar_pin, GPIO.IN)
# We wait up to one second for the pin
# to read nothing. When this changes, it means
# we started receiving sound.
start_time = time.time()
timeout = start_time + 1
while GPIO.input(sonar_pin) == 0:
if time.time() > timeout:
print("Timeout waiting for echo start")
return None
# Ok, now we are receiving something, log the time
start_time = time.time()
# Then wait until we stop receiving stuff
timeout = time.time() + 1
while GPIO.input(sonar_pin) == 1:
if time.time() > timeout:
print("Timeout waiting for echo end")
return None
# How much time did we wait between start and stop?
end_time = time.time()
duration = end_time - start_time
# Let's multiply that by the speed of sound in air
# to get the distance et justify the clickbait title
distance = (duration * 34300) / 2 # back and forth, so divide by 2
return distance
def play_music():
"""Play mario pipe sound, with a lock"""
# I would like to thank my mum and ChatGPT for this
if not pygame.mixer.music.get_busy(): # Check if music is playing
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
movement_detected = False
first_detection_time = time.time()
# Every 10 ms, send an echo through 2 sonars, and if the Gods
# are with us and it looks like somebody passing, play a funny sound.
# Because what else is there in life?
while True:
time.sleep(0.01)
# A lot of hard engineering went into finding those distances
# and timing, which involved a few people with beers, a dog
# and myself attempting to walk throught the pipe and
# bruteforce it until it works
if not movement_detected or time.time() - first_detection_time >1:
movement_detected = False
distance1 = get_distance(SONAR1_PIN)
if distance1 is not None:
print(f"Distance 1: {distance1} cm")
if distance1 < 80:
movement_detected = True
first_detection_time = time.time()
if movement_detected:
distance2 = get_distance(SONAR2_PIN)
if distance2 is not None:
print(f"Distance 2: {distance2} cm")
if distance2 < 80 and (time.time() - first_detection_time) > 0.3:
threading.Thread(target=play_music).start()
movement_detected = False
if distance1 is None:
print("You mother never loved you")
Notice how we have two sonars? In my first attempt, I played the sound only when people entered, not when they exited, so I diffed the two readings. It worked fine in the lab, actually my bathroom, for a week, and got me to giggle every time I went to pee. Then I stopped giggling once I actually installed it in production.
Long story short, I never could avoid the false positives with two sensors, so the actual code was live truncated into using only one sonar carefully positioned at a 37° angle with alpha centaury and a rusty nail.
But it worked.
And it was really goofy for a whole glorious night.
Sounds like a great idea :-)