Garage Parking Buddy Part 2

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

Continued from Part 1

The final part of the "loop()" function determines whether the display should be blanked due to the "US_TO" timeout. If the display animation (scrolling the display up/down or stationary) has completed and the display on flag ("disp_on_f") has been set, the display is re-enabled and a new animation started.

if((US_Timer + US_TO) < millis())
     disp_on_f = false;  
// disable display turn on flag
  if (P.displayAnimate())   
// if display animation finished
    // if display should be still on, 
    // restart animation
// first re-enable display
  old_distance = new_distance;
//wait a little before new measurement

For those who have a Home Automation System based on Home Assistant, the following is the excerpt of my configuration.yaml file.

     - name: "Car Presence"
       state_topic: "USS_status"
       payload_on: "1"
       payload_off: "0"
       device_class: presence
       value_template: "{{ value_json.Presence }}"
     - name: "Car Distance"
       state_topic: "USS_status"
       unit_of_measurement: 'cm'
       value_template: "{{ value_json.Distance }}"

Code for ESPNow WiFi Protocol Option

Way back in the Introduction, I mentioned a version of the program which does not require a WiFi Router nor a Home Automation System. It uses the ESPNow WiFi Protocol and sends messages directly to the Display Module I described in DIYODE Issue 60 in the "Displaying Degrees" article. If you built the Display from this article and loaded it with the Program described, no changes are required to the hardware or software for this Display. I have developed another program "Display_Ultra_NewPing_ESPNow.ino" for the "Car Presence Sensor and Parking Assistant".

The functioning of this program is very similar. The result is that messages are scrolled, from right to left, across the "Displaying Degrees" Display as the photographs below try to portray.

The first photograph indicates that "Car1" is present and located 56 cm from the HC-SR04 Sensor.

The second photograph indicates that "Car1" is not present, i.e. the car is not in the garage.

Much of the programming of the "Display_Ultra_NewPing_ESPNow.ino" program are similar to the "Display_Ultra_NewPing_WFM.ino" program. The major difference between the programs is that ESPNow is substituted for WiFi Manager and the MQTT functions.

Also, as I did not use WiFi Manager, I had to write my own Web Server for inputting the Sensor Name and min/max distance data. This is initially shown in the change to the "#include" statements. The "WiFiManager.h" and "PubSubClient.h" are replaced by "ESP8266WebServer.h" and "ESPNowW.h". Note however, "WiFiManger.h" does use "ESP8266WebServer.h" internally for all its Web Server functions.

The next major change is all the declarations and definitions for ESPNow. The most significant is the inclusion of the MAC Address of the "Displaying Degrees" Display ESP8266. As this was set in the program for the "Displaying Degrees" Display, it is simple to retrieve.

The structure for saving data to EEPROM is also greatly reduced.

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <ESP8266WiFi.h>   // for ESP8266
#include <ESP8266WebServer.h>
#include "ESPNowW.h"
#include <EEPROM.h>
#include <NewPing.h>

All that is required now is the Sensor Name, minimum/maximum distances for ideal parking location and a code variable to indicate the "Car Presence Sensor and Parking Assistant" has been initialised.

There are several additional functions for using ESPNow and a Webserver. For ESPNow, there is the "DataSent()", "TranmissionComplete()" and "SendESPNow()" functions.

The "TransmissionComplete()" function is a callback function that executes on the completion of each ESPNow transmission. It calls the "DataSent()" function on a successful ESPNow transmission to set the "Data_Sent" flag, which is used in the "SendESPNow()" function to repeat sending data until the flag is true. The "DataSent()" function also sets the "ESPNow_Send" flag to indicate the WiFi Link for ESPNow has been established and the link does not have to be established again. This is also used in the "SendESPNow()" function.

// ESP_NOW Server Address (MAC) and other data
uint8_t receiverAddress[] = {0x3A, 0x4A, 0x5A, 0x00, 0x00, 0x01};
bool Data_Sent;         
// Flag indicating Data sent or not
bool ESPNow_Send= false;
#define MAX_SEND 5000   
// Maximum time in ms to attempt Transmission
#define M_SIZE 64       
// Maximum size of Data Array for Data Sent
#define SN_SIZE 32      // Size of Sensor Name array
uint8_t M_BArr[M_SIZE]; // Array holding Data Sent
char M_BArr1[M_SIZE];
long Start_Time;       
// Timer used to measure time taken to send data
unsigned long ES_Timer;   // ESPNow Send Timer
#define ES_TO 10000        // Time in mS to send ESPNow update

The "SendESPNow()" function is called to send a message to the "Displaying Degrees" Display for scrolling across the display, as shown in the photographs above. This function on first call established the link to the "Displaying Degrees" Display.

Once successfully performed, it does not need to be established again. The callback and adding the "Displaying Degrees" Display ESP8266 MAC Address is declared each time (although they may not have to be, though no harm is done it they are).

Then by examining the "New_Range" variable, the message to be sent can be constructed, ie. if the car is present, the message, for example, "Car1 is present at 56cm" is sent otherwise the message "Car1 is not present" is sent.

The appropriate message is then continually sent until the "Data_Sent" flag is set true by the "transmissionComplete()" callback function. Note a random delay is inserted between transmissions just in case other sensors are sending messages to the "Displaying Degrees" Display, thus reducing the chances of collisions.

void SendESPNow( void)
  String USS_Data_Str;
  int Init_Result;
    // If ESPNow not already initialised, 
    // do the following
// Part of setting up for ESPNow WiFi
// we do not want to connect to a WiFi network
    Init_Result = ESPNow.init();
    PRINTL("ESP Init Result is ", Init_Result);
    if(Init_Result != 0) 
      PRINTLN("ESP-NOW initialization failed");
  Data_Sent = false;
  if(New_Range > OUT_OF_RANGE)
    USS_Data_Str = String(USSData.Sens_Name) + " is Present at " +String(new_distance) + "cm";
    USS_Data_Str = String(USSData.Sens_Name) + " is not Present";
  // The following is necessary as 
  // ".toCharArray" requires "char" array
  // and "ESPNow.send_message requires a 
  // "uint8_t" array
  memcpy(&M_BArr, &M_BArr1,USS_Data_Str.length() +1);
    ESPNow.send_message(receiverAddress, &M_BArr[0], USS_Data_Str.length() +1);
    if(millis() > (Start_Time + MAX_SEND)){
      PRINTLN("Timeout has occurred");
      Data_Sent = true;  // Stop transmitting, taking too long
    delay(random(50,150));  // Random delay to help prevent Data Tx collisions

The next series of functions are to do with the Webserver. They are "handle_OnConnect()", which displays the Setup web page once the IP address "" is put into the web browser address bar (as shown below with example input data).

This is after a WiFi Link is established to the Access Point, SSID is "Dist_Sensor" and Password is "12345678".

Next is the "handle_NotFound" function, which handles a wrong address being put into the web browser address bar. This is followed by the "handle_FinishCal()" which is called when the "FINISH" button is clicked. The "INITCODE" is set to indicate setup is complete and all data is saved in EEPROM.

void handle_FinishCal() {
  //Temperature Sensor Setup Finished
  PRINTLN("Setup Finished");
  USSData.init_code = INITCODE;
  EEPROM.put(0, USSData);
  Setup_Flag = false;
  server.send(200, "text/html", "<h1>Setup Completed</h1>"); 

The function "handle_DistSetup()" is called whenever the "Save" button is clicked. All the input data is extracted and placed into variables for saving into EEPROM once the "FINISH" button is pressed.

void handle_DistSetup() {                 
// If a POST request is made to URI /UpdNum
  String Name_Str, Min_Str, Max_Str ;
  PRINTLN("Handle Setup Request");
  if( !server.hasArg("sensor_name") || !server.hasArg("min_distance") || !server.hasArg("max_distance"))
    // If the POST request doesn't have Sensor 
    // name and min and ma distance data
    // The request is invalid, 
    // so send HTTP status 400
    server.send(400, "text/plain", "400: Invalid Request");         
  for(int i = 0; i<SN_SIZE; i++)
    USSData.Sens_Name[i] = 0;
  Name_Str = server.arg("sensor_name");
  Min_Str = server.arg("min_distance");
  Max_Str = server.arg("max_distance");
  USSData.min_distance = Min_Str.toInt();
  USSData.max_distance = Max_Str.toInt();
  server.send(200, "text/html", SendHTML1()); 

The "SendHTML1()" function is called by "handle_DistSetup()" and "handle_OnConnect()" functions to create the web page displayed in the web browser. For those familiar with HTML, this is straightforward.

For those that are not familiar with HTML and want to know more, I use "HTML & CSS – design and build websites" by Jon Ducket.

String SendHTML1(void){
  String ptr = "<!DOCTYPE html> <html>n";
  ptr +="<head><meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">n";
  ptr +="<title>Distance Sensor Setup</title>n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}n";
  ptr +=".button {display: block;width: 160px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}n";
  ptr +=".button-on {background-color: #1abc9c;}n";
  ptr +=".button-on:active {background-color: #16a085;}n";
  ptr +="p {font-size: 14px;color: #888; margin-bottom: 10px;}n";
  ptr +="</style>n";
  ptr +="</head>n";
  ptr +="<body>n";
  ptr +="<h1>Distance Sensor Setup</h1>";
  ptr +="<p>Input Minimum and Maximum Distances (cm)</p>";
  ptr +="<p>from Sensor for best car location</p>";
  ptr +="<form action="/DistSetup" method="POST">";
  ptr +="<p>Sensor Name: <input type="text" name="sensor_name" size="15" maxlength="30"/></p>";
  ptr +="<p>Min Distance: <input type="text" name="min_distance" size="5" maxlength="10"/></p>"; 
  ptr +="<p>Max Distance: <input type="text" name="max_distance" size="5" maxlength="10"/></p>";
  ptr +="<br /><input type="submit" value="Save">";
  ptr +="</form>";
  ptr+="<p>Finish Setup</p><a class="button button-on" href="/FinishCal">FINISH</a>n";
  ptr +="</body>n";
  ptr +="</html>n";
  return ptr;

The "setup()" function is similar to that of the WiFi Manager program version with the exception that the WiFi Manager setup is replaced by the web server setup.

void setup()
  // Get the Reset cause first up
  reset_info = ESP.getResetInfoPtr();
  // Initialise EEPROM Access
  // Read Stored Data in EEPROM Memory
  PRINTLN("n[Scrolling Display] and Ultrasound ESPNow");
  PRINTL("Reset Reason Number is ", reset_info->reason);
   switch (reset_info->reason)
    case PWR_UP_RESET:   
// Power up Reset, battery has been changed
      Setup_Flag = false;
      PRINTLN("Power Up Reset");
    case HW_WDT_RESET:   
// Hardware Watch Dog triggered reset
      Setup_Flag = false;  
// Still use stored settings
      PRINTLN("HW Watch Dog Timeout");
    case EXT_RESET:      
// Reset Push Button Pressed
      PRINTLN("Reset Button Pressed");
      USSData.init_code = 0;   
// Set to not initialised
      Setup_Flag = true;
// shouldn't get here, so better go into Setup
      Setup_Flag = true;
      PRINTLN("Shouldn't get here! ");
  // If Setup_flag true, 
  // webserver to be set up and initiated
    PRINTLN("Starting Soft AP");
    WiFi.softAPConfig(local_ip, gateway, subnet);
    WiFi.softAP(ssid, password);
// All Access Point time to setup
    PRINTLN("Setting up Webserver");
    server.on("/", handle_OnConnect);
    server.on("/DistSetup", handle_DistSetup); 
// input Sensor Name and Distance data
    server.on("/FinishCal", handle_FinishCal);   
// Setup Finish Button has been pressed
  P.displayText(curMessage, scrollAlign, 
scrollSpeed, scrollPause, 
scrollEffect, scrollEffect);
  US_Timer = millis();    
// Initialise keep display alive timer
  ES_Timer = millis();     
// Initialise MQTT Timer

Finally, the "loop()" function is identical to the WiFi Manager program with the exception that the call to "SendUpdMQTT()" is replaced by the "SendESPNow()" function.


Whichever version of the "Car Presence Sensor and Parking Assistant" you choose, the hardware build is the same. If you use the WiFi Manager (WFM) program version and integrate it with your Home Automation System (Home Assistant), you can generate messages, notifications or even alarms when your car has been detected as not present.

This may be important for you, especially with youth crime on the rise and many cars being stolen. I have not yet implemented this with my Home Automation System, I do have other security measures installed, it is something I am considering though. At the moment, I can access my Home Automation System on my iPhone as I have set up Port Forwarding on my WiFi Router. So I do have access and can see immediately if my car is in my garage or not.

If you use the ESPNow program version, I have found that I can take my "Displaying Degrees" Display anywhere in the house or even the garden, as long as it has a 5VDC power source (plug pack or power bank) and display the message sent from the "Car Presence Sensor and Parking Assistant". I am sure, it someone was interested, an alarm sounder could be added to the "Displaying Degrees" Display, such that it would sound on an alarm condition. Some simple hardware modifications would be required and some program changes would be required also.

Peter Stewart

Peter Stewart

Retired Engineer