Garage Parking Buddy Part 1

Arduino-based Car Presence Sensor & Parking Assistant

Peter Stewart

Issue 68, March 2023

This article includes additional downloadable resources.
Please log in to access.

Log in

This handy project helps you park your car in the garage and lets you know remotely if your vehicle is properly parked before you close the garage door.

Here’s a great project by our contributor, Peter Stewart, that helps you park your car safely in the garage without running into the garage wall or storage shelves. You can even use your Smartphone or PC to check that your vehicle is parked properly before you remotely close the garage door.

The broad overview

This project uses an HC-SR04 Ultrasonic Sensor to measure the distance from itself to my car. The timing data is processed by an ESP8266 to display symbols on a MAX7219 LED Dot Matrix Display indicating to the driver whether the vehicle is in the correct parking position. The ESP8266 also sends presence and distance data via WiFi using the MQTT Protocol to my Home Automation System (HAS) for display on my desktop computer in my study or on my iPhone wherever I am.


When I retired around five years ago, I made a list of the many electronic projects I wanted to undertake. I started off with a dozen or so projects. Over time, the list was extended to over 30 projects. One of the projects on my original list was a Sensor for detecting my car in my garage – a Car Presence Detector.

Late last year, when I was trying to think of a new project to undertake, I realised I still had not built my Car Presence Detector. I did purchase some components for the project, which included a HC-SR04 Ultrasonic Sensor.

Five years ago, I thought this would be useful in sensing an object. I put the HC-SR04 in my Parts Box and promptly forgot about it. So it was time to dig out that old HC-SR04 and attack the project. But, as I have learnt over the last five years, there are many very clever people out there in the Maker Community. So maybe, someone has come up with a Car Presence Sensor that I could use.

A search on YouTube soon yielded something that looked like what I wanted. “A New Parking Assistant using ESP8266 and WS2812b LEDs” by ResinChem Tech. This video is well worth a view. While I had a few ESP8266s (WeMos D1 Mini), I did not have the WS2812b LED Strip, nor the TFMini-s LIDAR Sensor (which is quite expensive).

The presenter of this video compared the LIDAR Sensor with a HC-SR04 Ultrasonic Sensor and, while the LIDAR Sensor came out as the best sensor, the presenter indicated that the HC-SR04 could be adequate for up to a “10 or 12 foot range” (3 to 4 metre range).

While I did look further into distance sensors and did find Infrared (IR) sensors that were cheaper than the LIDAR sensors, I opted to continue with the HC-SR04 Ultrasonic Sensor with the thought of upgrading to the IR Sensor at a later date if I found the HC-SR04 to be inadequate. I also decided to use the MAX7219 LED Dot Matrix display, as used in my “Displaying Degrees” article in Issue 60 of DIYODE Magazine (more on this later).

I also had a spare MAX7219 LED Dot Matrix display, so I could start building straight away.

A few weeks ago, Murray Roberts, the DIYODE Editor, contacted me asking what I was up to and suggested I looked at the “Parking Pal” by Johann Wyss presented in Issue 20 of DIYODE Magazine. This is an extremely good article as it explains how the HC-SR04 Ultrasonic Sensor works and if all you want is a “Parking Assistant” this is the way to go.

However, I wanted more. As usual, I looked at my requirements. They are as follows:

  1. Detect my car is parked in my garage;
  2. Indicate to my Home Automation System (HAS) when the car is or is not in my garage;
  3. Provide a display in my garage which indicates I am getting closer to the ideal parking location, at the ideal parking location or too close to the end of my garage (ie. the Parking Assistant);
  4. Connect via WiFi to my HAS providing presence and distance data.
  5. Both the Sensor and Display are to be located on my garage rack at the end of my garage.

The above is fine for me, as I have a WiFi Router and a HAS. Now I referred, previously, to my article “Displaying Degrees” in Issue 60 of DIYODE Magazine. The MAX7219 Display Module used a WiFi protocol, ESPNow, developed by Espressif, the designers of the ESP8266 and ESP32. ESPNow allows WiFi communication between ESP8266s and ESP32s without the need for an external WiFi Router.

In the second half of this article, I will present a different software build to allow the same hardware to be used to send messages via the ESPNow protocol to my Display Module as presented in Issue 60 of DIYODE Magazine. The requirements for this software are still the same, just replace HAS with MAX7219 Display Module.


The main concept of operation for this project is to determine whether my car is in my garage or not. It does this by using a HC-SR04 Ultrasonic Sensor Module. The Ultrasonic Sensor Module has two main components, a Transmitter sensor and a Receiver sensor. The Transmitter, when triggered on the “Trigger” pin by a 10 µsec pulse, sends out Ultrasonic sound wave pulses (eight at 40 kHz). See Figure 1.

The Echo Pin is then raised high and goes low when a reflected ultrasonic sound wave pulse is received at the Receiver sensor. By measuring the width of the pulse received on the Echo Pin, which is the time taken for the pulse to travel to the object and back again. Then dividing it by two gives the time to the object. Then if you know the speed of sound. (see

where 331.3 is the speed of sound at 0°C and θ is the temperature in °C.

Distance can be calculated. Since the Echo Pulse (tpw) is usually measured in µsec, then

Now, I had decided not to use a temperature sensor to determine the temperature to get an accurate speed of sound, I have used an approximation. Note, from the datasheet for the HC-SR04, the manufacturer recommends using 340 m/s as the speed of sound. Working backwards, using equation 1, this implies a temperature of around 14°C. My garage is a lot warmer than this. Now, the library I have used in my program for the SR-HC04 (NewPing Library by Tim Eckel), uses a value equivalent to 350.9 m/s which implies a temperature around 32°C (which is a lot closer in reality in my garage, especially in summer). As I do not need absolute accuracy, I have not changed anything in Tim Eckel’s NewPing Library.

The microprocessor I have used measures the pulse width from the Echo Pin of the HC-SR04 sensor module and calculates the distance of the front of my car from the HC-SR04 sensor module. Using this and the fact I have told the microprocessor that the ideal distance from the front of my car to the sensor is between, for example, 75 to 95 cm. The microprocessor will then set the Display as required. So, for example, if it measures greater than 95 cm, the display is a set of arrows scrolling up, indicating the driver can move the car closer.

If the distance is between 75 and 95 cm, the Display shows several blocks flashing, indicating the driver should stop the car.

If the distance is less than 75 cm, the Display shows a set of arrows scrolling down, indicating the driver should back the car up.

If the car is not in the garage, the program normally responds with a distance measurement of 0 cm. In which case the display is blank. However, on occasion, the measurement was 5 or 6 cm.

This I believe was due to some rounding errors in the Library I used, however, I could not find the error. So I compensated for this in my program (see the “Check_Range()” function).

Now, while the microprocessor is doing its measurements about once every 100 ms (the SR-HC04 manufacturer recommends greater than 60ms to avoid false measurements due to reflections of the ultrasound wave pulses), every second it also sends a MQTT packet via WiFi to my home automation sensor. In this packet, there is data indicating the distance the car is from the sensor and whether the car is present, i.e. in the garage within the range of the sensor, which is approximately four metres. My Home Automation System (HAS), which is based on the “Home Assistant” program running on a Raspberry Pi, displays the following indicating Distance and Presence.

The Main Build:

Parts Required:Jaycar
1 x WeMos/Lolin D1 MiniXC3802
1 x MAX7219 LED Dot Matrix Display Module-
1 x HC-SR04 Ultrasonic Sensor ModuleXC4442
1 x 2-Channel Voltage Level Shifter-
1 x 5Volt Plug PackMP3144
2 x 4-Pin Microphone Panel Male ConnectorPP2010
2 x 4-Pin Microphone Line Female ConnectorPS2012
1 x DC Chassis Connector 2.1 x 5.5 mmPS0522
1 x Momentary Push Button SwitchSP0710
4-Core Alarm CableWB1590
1 x VeroboardHP9542
Red Acetate Sheet-
1 x 4-pin Male Headers^HM3212
1 x 5-pin Male Headers^HM3212
Header Strip – 90 degrees (alternative to above)^-
2 x 8-pin Female Headers+-
2 x 5-pin Female Headers+-
4 x 4-pin Female Headers+-
26 AWG Stranded White Hook Up WireWH3007
26 AWG Stranded Red Hook Up WireWH3000
26 AWG Stranded Black Hook Up WireWH3001

* Quantity required, may only be sold in packs. ^ Break off 4-pin and 5-pin headers from 40-pin strip. + Cut up to make 1 4-pin and 1 5-pin female Headers (4 x P5376 required).

The “Car Presence Sensor and Parking Assistant” consists of very few components as listed in the parts list shown here. The circuit is relatively straightforward. The whole module is powered by an external 5V power pack.

The microprocessor I have used is the commonly available ESP8266 based WeMos (or Lolin) D1 Mini.

I have also used a 2-channel voltage level shifter between the D1 Mini Microprocessor and the HC-SR04 Ultrasonic Sensor. This is due to the I/O of ESP8266 on the D1 Mini operating at 3.3 Volts whereas the “Trig” and “Echo” signals on the HC-SR04 operate at 5 Volts.

I have used Veroboard for building the circuit. As can be seen from Figure 3 wiring diagram, only the D1 Mini and the Voltage Translator are on the Veroboard.

There is five pin header connector on the Veroboard for connecting the MAX7219 LED Dot Matrix display via a cable. There is also a four pin header for connecting a cable to a four-pin Microphone Connector for connecting to the HC-SR04.

Note: In the Parts List, I have specified straight pin headers (Altronics P5430) from which to make the 4/5-pin headers. However, I have used the right angled headers (Altronics P5440) as this helps with not bending cables and pins on assembly in the case.

The Microphone Connector is attached to the case containing the D1 Mini and MAX7219 LED Dot Matrix Display.

The HC-SR04 Sensor Module is contained in a separate case connected via a cable with Microphone connectors at both ends.

The cases for the D1 Mini/MAX7219 Display and HC-SR04 Sensor are 3D Printed. (See the Resources Link at the end of this article for the 3D Print Files).

I started the construction of the “Car Presence Sensor and Parking Assistant” by first cutting the Veroboard to size (Use Figure 3 as a reference and the photographs). I cut the tracks as indicated in Figure 3. I then soldered the hook up wires. While the parts list indicates White hook up wire, which has PVC insulation (Jaycar and Altronics), any colour could be used. I use white hook up wire with PTFE insulation (Element14). PTFE wire is more expensive, but the insulation does not melt when soldering.

I then added the Female headers for the D1 Mini and the 2-Channel Level Shifter followed by the Male headers for the MAX7219 Display and HC-SR04 Ultrasonic cable connections.

Once I added the wiring for the DC Supply and the Reset Switch, I was ready to assemble the Display into the case lid.

The MAX7219 LED Dot Matrix Display does not come with any easy mounting holes. So I designed and 3D printed two clamps that have studs to secure the MAX7219 Display.

I also added a piece of red acetate sheet to place in front of the MAX7219 Display. This helps with the red dots of the MAX7219 Display not being washed out by ambient light, but also diffuses the light a little.

I then made the cables using the schematic in Figure 4.

Both cables are fitted into the Display Case containing the Car Presence Sensor and Parking Assistant Veroboard and the MAX7219 Display.

With respect to the MAX7219 Cable, you can cut the Female Headers to make 5-Pin Headers and solder the wires on as per the schematic. Or what I actually did, was to take a spare five way 150 mm long Dupont cable with female connectors, that I had in my parts box and use that. I also made a second HC-SR04 Cable to be fitted in the Sensor Case holding the HC-SR04 Sensor. The cable connecting both the Display Case and the Sensor Case has the Microphone Female connectors at each end, with Pin 1 to Pin 1, Pin 2 to Pin 2, Pin 3 to Pin 3 and Pin 4 to Pin 4.

With all cables now made, I assembled everything into the Display Case and the Sensor Case.

I used countersunk M3 12 mm long screws on the clamp brackets to hold the MAX7219 LED Dot Matrix Display Module to the front of the Display case. I used 4G x 9 mm Sheet Metal Screws to fasten the Display Case Lid to the Display Case Base. I recommend using the same screws to fasten the Senson Case Lid to the Sensor Case Base.

I tried to be “smart” and tapped the Sensor Case Base with M3 treads in all four mounting holes and used Allen Head M3 screws to hold the Lid and Base together. While this worked, it took a lot of effort and putting such a fine thread into 3D printed PLA may not last long if you undo the screws too often.

One other important thing to note is the location of the HC-SR04 Sensor. As can be seen from earlier photographs, my Sensor is located low on my storage rack at the end of my garage. This is perfectly in line with the number plate on my car. The number plate is a very flat surface and results in very direct reflections for easier and more accurate measurements.

Code for WiFi Router and Home Automation System

I have developed my program "Display_Ultra_Ping_WFM.ino" for the Car Presence Sensor and Parking Assistant using the popular Arduino IDE. The program is worth looking at in some detail as I encountered several issues.

The first issue involved using the ESP8266 Microprocessor and its WiFi functions. I found that when I used the function "pulseIn()" to measure the pulse width returned by the HC_SR04 Ultrasonic Sensor on its "Echo" pin, that on occasion, the ESP8266 would for no apparent reason reset. I added some code at the beginning of my program to determine the reason for the reset. The code snippets I used are shown here.

extern "C" {
#include <user_interface.h> 
// Required for ESP Reset Cause SDK calls
rst_info *reset_info;                
// Structure to hold Reset Information (cause, etc.)
#define PWR_UP_RESET 0      
// Reset reason due to Power Up 
#define HW_WDT_RESET 1     
// Reset caused by Hardware Watch Dog Timer
// Reset reason due to wake up from Deep Sleep
#define EXT_RESET 6                
// Reset reason is Reset Button pressed

void setup(){
// Get the Reset cause first up
reset_info = ESP.getResetInfoPtr();

PRINTLN("nScrolling Display and Ultrasound with WFM");
PRINTL("Reset Reason Number is ", reset_info->reason);

These remain in my program. What I discovered was that I was getting "HW_WDT_RESET" as the reason for the reset. Upon investigation, this is caused, for example, when the WiFi functions have not been able to run because the program is stuck in a loop or in a function that is blocking. This is also when I discovered that the "pulseIn()" function is blocking, i.e. it does not allow anything else to execute until it is finished. In "Parking Pal" by Johann Wyss presented in Issue 20 of DIYODE Magazine, he uses a Library by Martinsos which uses the "pulseIn()" function, which is fine in Johann’s implementation, but would not be suitable in my case due to the use of the WiFi function. I found the "NewPing" Library by Tim Eckel, to be much more suitable as it does not use "pulseIn()" and is non-blocking.

I have kept the Reset Information code in my program, as I use this to determine when the settings need to be changed. So if you need to change settings, just press the Reset Button.

As with all Arduino programs, my program has all the include statements at the beginning. The first two libraries are specifically for the MAX7219 Display. The "" Library is for the interface to the MAX7219 Display.

#include <MD_Parola.h>    // For MAX7219 Display
#include <MD_MAX72xx.h>   // For Max7219 Display
#include <SPI.h>          // For MAX7219 Display
#include <ESP8266WiFi.h>  // for ESP8266
#include <WiFiManager.h>  // For ESP8266
#include <PubSubClient.h> // For MQTT
#include <EEPROM.h>       // For Saving Settings
#include <NewPing.h> // For HC-SR04 Ultrasonic Sensor

The next two Libraries are for using WiFi with the ESP8266 microprocessor.

WiFiManager.h is a particularly useful library as it allows setting up a webserver for handling all the settings required, not only for WiFi, but for MQTT and the HC-SR04 settings.

PubSubClent.h is used for setting up a connection to the MQTT Broker and publishing the data.

EEPROM.h provides the functions to allow writing data to the ESP8266 EEPROM and reading it back.

NewPing.h provides the functions to control the HC-SR04 Ultrasonic Sensor and calculate the distance from the targeted object.

After the Library "includes", I have my definitions and declarations. Firstly for "DEBUG" statements, followed by that for the MAX7219 Display. One area of note is the following.

// Global message buffers used by Scrolling functions
#define BUF_SIZE 6
char curMessage[BUF_SIZE] = { "" };
char forwardMessage[BUF_SIZE] = { 24,24,24,24,24,0 };  // Up arrows
char backMessage[BUF_SIZE] = { 25,25,25,25,25,0 };     // Down Arrows
char stopMessage[BUF_SIZE] = { 19,19,19,19,19,0 };     // Blocks

These are the buffers used for what is displayed on the MAX7219 Display Module. For example, "forwardMessage" is six bytes long with the first five bytes containing the repeated character for an up arrow, resulting in five up arrows being displayed "xxxx".

This is also scrolled upwards to indicate to the driver that the car needs to be closer. "backMessage" buffer contains "xxxx" and is displayed and scrolled down to indicate to the driver that the car is too close to the HC-SR04 Sensor. "stopMessage" buffer contains "XXXXX" and is displayed flashing to indicate to the driver the car is within the ideal parking zone.

After this, we have the definitions and declarations for the HC-SR04, the Structure "USSData" containing the data for WiFi, MQTT and min/max distance data which is stored in EEPROM for recall after a power failure without having to perform a setup again.

This is followed by the declarations for the WiFi Manager Webserver to be added in the "Setup()" function to create what is displayed on the web page by WiFi Manager. See diagrams below.

// The extra parameters to be configured (can be 
// either global or just in the setup)
// After connecting, parameter.getValue() will get 
// you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter c_mqtt_server("server", "MQTT server", "", 32);
WiFiManagerParameter c_mqtt_port("port", "MQTT port", USSData.MQTTPortS, 6);
WiFiManagerParameter c_mqtt_servername("server_name", "MQTT Server Name", "", 32);
WiFiManagerParameter c_mqtt_password("pword", "MQTT Password", "", 32);
WiFiManagerParameter c_mqtt_name("name", "MQTT Name", "", 32);
WiFiManagerParameter c_min_distance("min_dist", "Minimum Distance (cm)", "", 8);
WiFiManagerParameter c_max_distance("max_dist", "Maximum Distance (cm)", "", 8);
void setup()
  // Get the Reset cause first up
  reset_info = ESP.getResetInfoPtr();

//add all your parameters here

Before the "Setup()" function there are three functions that are called from the "Loop()" function, "SendUpdMQTT()", "saveConfigCallback()" and "Check_Range()".

The "SendUpdMQTT()" function is called when a MQTT string is to be sent to my Home Automation MQTT Broker. The function first creates the MQTT string, for example {"Distance":78,"Range":2,"Prescence":1,"Reset Reason":6,"Reset Count":0}. Where "Distance" is the distance measured in cm of the car from the HC-SR04 Sensor, "Range" indicates where the distance lies, ie ‘2’ indicates "IN RANGE" which is in the ideal position; "Presence" ‘1’ indicates the car is in the garage and ‘0’ indicates it is not in the garage; "Reset Reason" is for diagnostic purposes where ‘6’ indicates the Reset Button was pushed; and "Reset Count" indicates the number of the other types of reset that have occurred, ie Hardware Watchdog Timeout or Power Up Reset with the count being reset after the Reset Button has been pushed. The "saveConfigCallback" function is called by the webserver when the "Save" button is clicked on WiFi Manager web page. All the inputs are extracted by this function and placed in the "USSData" structure and then in turn stored in EEPROM.

The "Check_Range" function determines from the distance measurement that is passed to it whether the car is "OUT_OF_RANGE", "APPROACHING", "IN_RANGE" or "TOO_CLOSE" by using the "Minimum Distance" and "Maximum Distance" values input on the WiFi Manager web page. Going back to the "setup()" function, after determining the reset reason, this is used to determine whether the WiFi settings are to be deleted, ie. when the Reset Button has been pushed (EXT_RESET). If the reset button has not been pressed, all the previously saved settings are retrieved from EEPROM. The reset counter is incremented and saved back into EEPROM.

void setup()

  //reset settings - 
  //wipe credentials after a Reset button presssed
  if(reset_info->reason == EXT_RESET)
    // for all other Reset Reasons
    // Read Stored Data in EEPROM Memory
  // increment Reset Counter - sent in MQTT Message
    EEPROM.put(0, USSData);

Next, is to set up all the parameters for the WiFi Manager. First is all the added parameters to be added to the WiFi Web page – "wm.addParameter()". Then the web portal is set up to be non-blocking. This allows other functions to continue, for example, measuring the distance of the car from the HC-SR04 Sensor. The callback function is then allocated, see "saveConfigCallback()" above. This call back function is called when the "Save" button is clicked. The timeout function is set up such that after "20" seconds of trying to establish a WiFi connection, it terminates. The auto reconnection function is also enabled so that if a connection is lost to the WiFi Router, it automatically tries to re-establish the link. After all the settings are complete, WiFi Manager then attempts to connect to the WiFi Router using credentials previously saved. If this fails or credentials have not previously been set up or they have been reset, an Access Point is created using "USSDevice" as the SSID and "12345678" as the password. You can use your mobile phone or notebook computer to connect to this access point. Then by starting a browser and inputting the address "", the WiFi Manager web page as shown above will be presented.

//add all your parameters here
  //set config save notify callback
// how long to try to connect for before continuing
// Set auto re-connection
    PRINTS("connected...yeey :)");
    PRINTS("Configportal running");
  P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
  US_Timer = millis();    
// Initialise keep display alive timer
  M_Timer = millis();     
// Initialise MQTT Timer

After all the WiFi Manager settings are complete, the MAX7219 Display is setup, initially with a blank display. Two timers are set up. The first, "US_Timer" is used to shut down the display if there have been no distance changes from the HC-SR04 sensor in the last "US_TO" milliseconds. The second timer "M_Timer" is used to send a MQTT message to my Home Automation System every "M_TO" milliseconds. Once everything has been set up, the "loop()" function executes repeatedly. Firstly, there is a check to see if the WiFi Manager configuration data has been saved. If not, the processor keeps checking for input from the WiFi Manager web page.

Then the distance ("new_distance") of the HC-SR04 Sensor to the car is determined. Note the returned value is an unsigned long, so it had to be cast to a 16 bit integer. This simplifies the maths later. Also note, that I perform a single reading. In "Parking Pal" by Johann Wyss, he took multiple readings (50) and took an average. I found this not necessary as my readings were fairly consistent. However, if you find you are getting inconsistent readings, you may have to use Johann’s averaging technique. One warning though, if you take readings more often than 60 milliseconds apart, you may get false readings due to reflections from Ultrasonic waves still bouncing around (refer to HC-SR04 Manufacturers Datasheet). You will notice that at the end of my "loop()" function I have a delay of 100 milliseconds.

void loop() {
  // Process WiFi Manager as long 
  // as configuration is not complete
  if(!shouldSaveConfig) wm.process();  
  new_distance = (int16_t)uss.ping_cm();
  PRINTL("Distance to car is ",new_distance);

The program then checks the "New_Range", ie. "OUT_OF_RANGE", "APPROACHING", "IN_RANGE" or "TOO_CLOSE". If the MQTT Timeout has occurred, the program sends an MQTT message to my Home Automation System indicating distance, range, etc as described earlier.

New_Range = Check_Range(new_distance);
  if((M_Timer + M_TO) < millis())
    //Only send MQTT Data when Timeout expires
    M_Timer = millis();  
// re-initialise MQTT Timer

The program then checks the "new_distance" against the previous measurement "old_distance" to determine if the change in distance is enough to wake up the display if it had been disabled due to a timeout "US_TO".

if(abs(new_distance-old_distance) > (old_distance/DIST_TOL))
    Tol_F = true;
    PRINTLN("*****Big change in distance******");
    Tol_F = false;
    PRINTLN("No big change in distance");

Then by using the "New_Range" determined earlier, the program determines what display should be shown on the MAX7219 Display with the appropriate scrolling direction and pause between display updates. In this section of the program, it is also determined whether the Display should be on again or not ("disp_on_f").

switch ( New_Range)
    case OUT_OF_RANGE:
      if(New_Range != Old_Range)
        Old_Range = New_Range;
        disp_on_f = false;  
// disable display turn on flag
      if((New_Range != Old_Range)||Tol_F)
        Old_Range = New_Range;
        US_Timer = millis();
        disp_on_f = true;      
// enable display turn on flag
    case IN_RANGE:
      if((New_Range != Old_Range)||Tol_F)
        Old_Range = New_Range;
        US_Timer = millis();
        disp_on_f = true;      
// enable display turn on flag
    case TOO_CLOSE:
      if((New_Range != Old_Range)||Tol_F)
        Old_Range = New_Range;
        US_Timer = millis();
        disp_on_f = true;      
// enable display turn on flag

Continued in Part 2

Peter Stewart

Peter Stewart

Retired Engineer