Our garage has a ladder sticking out near where we park our car. We want to park close to the ladder leaving enough room to walk in front of the car and also allow room behind the car for the garage door to close. A typical solution to this need, and one we’ve used in the past, is to have a tennis ball on a string positioned such that the car window nudges the ball and shakes the string — so you know when to stop. A great analog solution! I, however, desired a digital solution that incorporates a Raspberry Pi.
OK, a digital solution should either have a sensor that is tripped by the car or, even better, something that could dynamically measure and show the distance from the car to the ladder. This would require some way to provide feedback to the driver. Hmmmm, feedback could be visual or maybe via sound. Yes, that’s it! I could have an R-Pi measure the distance and “talk” to the car driver. Another project is born 😎
So, what is needed to pull this project off? A real time distance-measuring device that interfaces to a Raspberry Pi, software to convert values (measurements) to text, software to “read” the text (text-to-speech), and hardware for audio output.
Distance sensors come in several different flavors with different interfaces. There are laser-based, IR-based and untrasonic range sensors. Different types have different ranges and accuracy. I wanted one that could measure from about 1 foot to about 6-10 feet (30 cm to about 2-3 meters). I also wanted a fairly wide “cone” to detect different size and shape fronts and rears on different vehicles. Most sensor interfaces are either I2C or serial. The Inter-integrated Circuit (I2C) Protocol only requires two signal wires to exchange information but requires connection to R-Pi headers and more complex software. I did not want a soldering project and I liked the idea of a USB interface coupled with popular software support libraries. Numeric values of distance could be sent as ASCII text via the serial interface and read and manipulated on the R-Pi without the need for low-level timing loops, etc. After some research, I settled on the Maxbotix HRUSB-MaxSonar-EZ high resolution USB ultrasonic sensor, model MB1413. I purchased one from Adafruit for about $50
I decided to program my application in Python as all the pieces I needed could be accessed via various python libraries or by calling-out to the operating system for helper applications. I decided on the pySerial 3.x module for serial communications. I also installed the “minicom” Linux terminal application, which enabled me to talk to the range sensor for setup and testing. The sensor communicates via the USB port in a stream of ASCII text numbers. Python provides the ability to maipulate strings and convert numbers into various units of measure (inches, feet, cetimeters, meters, etc.). I wanted values and commands (“vehicle detected … 3 feet … 2 feet … 1 foot … stop!”) to be spoken. To do this, I needed a text-to-speech (TTS) utility. There is a nice comparison of different TTS engines that run on the Raspberry Pi on eLinux. After some research and experimentation I went with “pico2wave”. I found this to produce the most natural speech — and it supports multiple languages. As pico2wave outputs a “WAV” file, I needed another app to convert the WAV file into audible output. I used “aplay”, a Linux command-line soundfile player.
My first step was to ensure the ultrasonic module could communicate with the R-P. I connected the sensor to a USB port of the R-Pi with a USB 2.0 A-Male to Micro B cable. After doing the usual “apt-get” update and upgrade, I installed the minicom terminal package:
sudo apt-get install minicom
Need to determine which USB port is being used. Look for “tty” entries with “USB” suffix.
This command returned — for my R-Pi:
OK, time to run miniterm with appropriate settings and see what data the sensor is returning:
minicom -b 57600 -o -D /dev/ttyUSB0
enters terminal mode and shows something like:
Welcome to minicom 2.7
Compiled on Apr 22 2017, 09:14:19.
Port /dev/ttyUSB0, 15:28:59
Press CTRL-A Z for help on special keys
and a line constantly updating:
The “R” indicates a range reading and the number is the distance from the sensor in milimeters. As you move things closer or further from the sensor, you’ll see the distance number change 🙂 You can exit terminal mode with CTRL-A followed by “x”.
Now we have the raw data that we want to manipulate. I want to convert to imperial units (inches, feet, yards) for us Americans and I want the R-Pi to talk. I am using Python 2.7 which is installed as “python” on the R-Pi. Do not confuse with Python 3.5 (“python3” on R-Pi).
Make sure you have the python serial library installed:
sudo apt-get install python-serial
Install the Test-To-Speech (TTS) engine/app:
sudo apt-get install libttspico-utils
If you have your R-Pi setup for audio output, you can test things by using “pico3wave” and “aplay”. I’ll leave it as a user exercise to ensure “aplay” is outputting to the correct device and that TV or amp/speakers are connected correctly. The use of “aplay”, from the ALSA framework, over “omxplayer” allows for more output devices such as USB powered speakers, DACs or other USB audio devices.
pico2wave -w sorrydave.wav "I'm sorry Dave. I'm afraid I can not do that"
you should see output from aplay – something like:
Playing WAVE 'sorrydave.wav' : Signed 16 bit Little Endian, Rate 16000 Hz, Mono
I’m going to call various Linux apps via Python. I want the output from “pico2wave” (TTS) to be input to “aplay”. In the Linux shell, I would normally pipe output (stdout) of one app to input (stdin) of another. Unfortunately, “pico2wave” does not behave well 🙁 There is a workaround under Linux. You can make (link) a file be stdout and use this file as input. For our purpose, we’ll do this via this command:
sudo ln -s /dev/stdout /var/local/pico2wave.wav
I integrated the various pieces (sensor readings, text-to-speech and audio output) via a Python program. I used the serial input library, did some measurement conversions and used system call for TTS output.
#!/usr/bin/python # -*- coding: utf-8 -*- import serial import time import os # Function to read chars from serial input until you get a <CR> or null # before getting a line, clear the buffer/cache. We do not want "lagging" data def readlineCR(port): rv = '' port.reset_input_buffer() while True: ch = port.read() rv += ch if ch == '\r' or ch == '': return rv # Function to get sensor reading as text, validate & return numeric value in mm def getDist(): mmdist = 0 while True: response = readlineCR(port) if len(response) == 6 and response == 'R': mmdist = int(response[1:5]) return mmdist # setup serial port parameters per MaxBotix specs port = serial.Serial( '/dev/ttyUSB0', baudrate=57600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, writeTimeout=0, timeout=10, rtscts=False, dsrdtr=False, xonxoff=False, ) # Open serial port for read/write print port.isOpen() print 'Port opened...' # initialize variables # saymtric = say metric measurement (in mm) else imperial (in inches) # last = last measurement read - used to determine movement # inches = used if conversion to imperial units desired # delta = value in mm to determine motion between readings # inrange = if closer then this, speak! (1800mm = 6' == 72") # mmmm[ ] = used to determine median of multiple readings -- filter out noise saymetric = True last = 0 inches = 0 delta = 10 inrange = 1800 mmmm = [0, 0, 0] # Loop forever for constant measuring. Will speak if closer than 6 ft (180 cm) while True: # validate & convert text to numeric value in mm # filter noise by using median of 3 readings mmmm = getDist() mmmm = getDist() mmmm = getDist() mmmm.sort() mmm = mmmm # only talk if there is movement. check delta if abs(last - mmm) > delta: inches = int(mmm * 10 / 254) print mmm # check distance and use selected units of measure (metric vs. imperial) if (mmm < inrange): if (saymetric): os.system('pico2wave -w /var/local/pico2wave.wav ' + '"' + str(mmm) + '"' + ' | aplay -q') else: os.system('pico2wave -w /var/local/pico2wave.wav ' + '"' + str(inches) + '"' + ' | aplay -q') # save mm value to use in delta motion check last = mmm
There are a couple of areas to note in the code. MaxBotix suggests using the median of multiple readings to reduce “noise” and spurious measurements. I also don’t want the R-Pi always talking! I check to ensure an object (my car) is within 1800mm (6 ft.) and that there has been some movement since the last reading (use of “delta” variable).
I believe that there are many use cases for being able to measure distance, detect motion and have the R-Pi speak. Please LMK if you build this “parking-assist” system or if you develop other uses for the concepts presented here.