I've left the garage door open at least a couple of times. My wife has done it at least once. Surely something can be done to alert us if this happens again! For this project I used an ESP32 to monitor the state of the garage door, and a simple server-side Rust process to generate alerts if the door is left open for too long.
As the ESP32's built-in Hall Effect sensor
can detect a magnetic field, the hardware for this project consisted of simply:
► An ESP32-PICO-KIT (but any ESP32 would do)
► A USB charger and cable
► A magnet
By using the built-in sensor, there is no wiring required, apart from plugging in a USB charger to provide power.
Reading from the Hall Effect sensor is very easy. First the ADC1 module needs to be configured
to reading 12 bit values, as the Hall Effect sensor readings to not cover the full range of the
input. Then we can read from the sensor simply by calling hall_sensor_read()
.
adc1_config_width(ADC_WIDTH_BIT_12);
...
int value = hall_sensor_read();
At first glance the Hall Effect sensor on the ESP32 acts a lot like a random number generator — the values it returns are super noisy! A simple way to filter out the noise is to perform lots of readings (200) and average them.
This logic is implemented on the ESP32. It could easily be implemented on the server-side instead. I read 200 samples from the Hall Effect sensor every 5 seconds, and then decide whether the door is open or closed.
My initial idea to define fixed thresholds for door open and door closed didn't work reliably. The values returned by the sensor seemed to drift over time, possibly due to temperature. Instead I maintain an average of recent readings from when the door is considered closed, and define a difference threshold relative to this average.
To keep things simple, I use a fixed message format over UDP. The message is only sent when we determine
that the door is open, and for 30 seconds after the door closes. The message contains three fields:
► An indication of whether the door is open or closed
► Number of seconds since the last state change
► The most recent sensor reading (for debugging).
The IP addresses of the ESP32 and the server are hardcoded, as is the port number for the UDP messages. UDP is a connectionless protocol, so this simplifies the the implementation on both sides.
On the server side I use syslog to log when the door changes state, but otherwise the alerting behaviour is config-driven. I use CallMeBot and their Signal API to send messages when the door has been left open for too long, and then again when the door is closed.
Configuration defines:
► How long the door needs to have been open before sending an alert,
► The phone number of the recipient,
► The recipient's CallMeBot API key,
► The message text for when the door is open, and for when it is closed.
monit is used to monitor the state of the ESP32 (via ping), and that the server process is running and listening on the expected UDP port. I'll receive an email if either side fails, and the server process can be restarted automatically.
Not long after the initial deployment this monitoring identified that if the ESP32 lost its wifi connection, it never came back. As this is a rare occurrence, I fixed it in the simplest possible way by rebooting the ESP32 on disconnection.
No matter how much I tweaked the algorithm, I couldn't get the sensor to reliably detect when the door was open or closed. It nearly always worked, but when you get a message saying the garage door is open when nobody is home, it's somewhat concerning.
This issue happened rarely enough that it was impossible to debug.
I gave up on the Hall effect sensor, and purchased an HC-SR04 Ultrasonic Distance sensor. This sensor has four connectors — VCC, ground, trigger and echo. Connect VCC to +5V on the ESP32, ground to GND, trigger and echo to GPIO pins.
gpio_set_direction(TRIGGER_PIN, GPIO_MODE_OUTPUT);
gpio_set_direction(ECHO_PIN, GPIO_MODE_INPUT);
...
// set trigger pin high for 10us
ESP_ERROR_CHECK(gpio_set_level(TRIGGER_PIN, 1));
usleep(10);
ESP_ERROR_CHECK(gpio_set_level(TRIGGER_PIN, 0));
...
// wait for the echo pin to go high
while( !gpio_get_level( ECHO_PIN ) );
int64_t start = esp_timer_get_time();
// wait for the echo pin to go low
while( gpio_get_level( ECHO_PIN ) );
int64_t stop = esp_timer_get_time();
Just imagine that the code above contains a little more error checking to ensure we don't get stuck in and endless loop. My code is on Bitbucket if you want to see how I did it. Then to determine the distance:
int64_t time_diff = stop - start; // microseconds
// speed of sound 343m/s, half the distance to account for the sound
// travelling there and back
double distance = time_diff * 0.0343 * 0.5; // centimetres
Then a simple threshold can be used to determine whether the door is open or closed. I set the threshold to 1 metre and placed the sensor approximately 65cm from the garage door.
One advantage of an ultrasonic sensor is that they look like eyes, so for a bit of fun you can add a face and give your project a name. This is Gary, the garage door monitor.
Source code for this project can be found on Bitbucket.