I built the Garage MC because I wanted to learn more about electronics. As a bonus, I figured it would save my butt the next time I went out and left the garage door open.
*I added an update on 2021-01-06 with OTA updates & MQTT to Home Assistant (detailed below)*
*And in 2023 with the old Blynk app being depreciated, I was finally forced to remove it and update my code*
My Goals- The ability to Monitor the state of the garage door (closed/ajar/open).
- The ability to Control (open/close) the garage door with my Android cell phone (and my wife with her Apple cell phone).
- Learn something new.
- Not spend a lot of money.
Prior to this project I purchased an Arduino compatible Starter Kit from Amazon. So I was a beginner with electronics and just learning the ropes. But I did have the benefit of doing some computer programming 15 years prior, so I was able to quickly pick up coding C++ on the Arduino IDE.
Garage Door Opener: First, I had to determine if I could easily interface a microcontroller with my Liftmaster garage door opener. Luckily, the Liftmaster is operated with two wires in a Normally-Open (think an open switch) state connected back to the wall remote. When the button is pressed on the wall remote, it briefly completes the circuit by "shorting" the wires and triggering the door to open or close. Ohhh, don't forget to check the voltage on those wires...the Liftmaster manual says the lines are approximately 20 volts (not 120 volts), but your unit may be different.
Microcontroller: Next, I had to decide how to connect the Garage MC to the internet. I initially thought about using an ENC28J60 Ethernet Board, and although I prefer hard-wired connections, running the line to my router would be more trouble than it's worth. I researched wifi boards and decided on a NodeMCU ESP8266 due to their low cost and having multiple GPIO pins.
Phone App: There are multiple ways to connect to the NodeMCU with your phone including through SMS messages, using the NodeMCU as a web server, etc. I initially used Blynk, but that has been removed. I now use MQTT to update Home Assistant and connect to it from my phone.
Monitoring of the Garage Door: I decided to use two Hall Effect Sensors to monitor the position of the garage door. It may have been possible to get the door position and even direction of travel from the Liftmaster unit. But, with the off chance that someone pulls the emergency release cord and manually opens the door, the sensors will still notify me that the door is open.
Step 2 : DevelopmentNodeMCU: I started by plugging the NodeMCU into a breadboard and powering it through a USB connection to my computer. The link below has a good tutorial for the initial setup of the NodeMCU on the Arduino IDE: https://www.instructables.com/How-to-Program-NodeMCU-on-Arduino-IDE/
Breadboarding: Time to add the components. Although I made the Fritzing circuit diagram after putting on all the components, I added it here so it could be more easily referenced...
Project Code: I've attached the complete code for my project. I tried to comment it well, but if there's anything you don't understand, or I can improve on, let me know. I've added small code snippets in the tutorial to emphasize some things, such as:
- I made my code non-blocking. Meaning it has no "delay()" statements (okay there's one in the setup to wait for the serial port). I instead relied the "SimpleTimer" library.
First the Relay (Controller): The relay I used is a 1-channel relay that is triggered "LOW" to activate. The relay requires 5 volts, so I used the VIN pin on the NodeMCU to power it. The relay serves two purposes.
- The first is to Activate the garage door opener. This is done by connecting one wire from the garage door opener to the common (COM) pin on the relay. And the second wire to the N/O (Normally Open) pin on the relay. When the relay is triggered (for 300 milliseconds), the circuit is closed.
- The second purpose is to protect the NodeMCU from the 20 volt wires of the garage door opener, since the digital pins on the NodeMCU are tolerant to only 3.3 volts.
I used two functions to trigger my relay, making it non-blocking. When a button is pressed in Home Assistant, it calls a function to activate the door. The ActivateDoorRelay function sets the relay pin "LOW", then sets a timer for 300 milliseconds, instead of using "delay()". The timer then triggers the second function to reset the pin to "HIGH" to turn off the relay. The reason I'm checking that the state hasn't changed between when the button is pressed and the door is activated is because a song is played to warn anyone in the garage that the door is about to move (someone could press the Liftmaster wall remote button during the song activating the door).
void mqttCallback(char* topic, byte* payload, unsigned int length) {
payload[length] = '\0';
char *cstring = (char *) payload;
String newTopic = topic;
String newPayload = String((char *)payload);
if (newTopic == topicGarageControl) {
if (newPayload == "ACTIVATE") {
ActivateDoor();
}
}
}
void ActivateDoor() {
activateState = garageState; // get garage state when the button was pressed
long songLength = PlaySong(); // Play a song
// Wait for the song to finish, then activate the relay
myTimer.setTimeout(songLength, ActivateDoorRelay);
}
void ActivateDoorRelay() {
// only trigger the relay if the state hasn't changed since the button press
if (garageState == activateState) {
digitalWrite(RELAY1_PIN, LOW); // trigger the relay to activate the door
myTimer.setTimeout(300, ActivateDoorRelayReset); // Wait 300ms
}
}
void ActivateDoorRelayReset() {
digitalWrite(RELAY1_PIN, HIGH); // Deactivate the door relay
Blynk.virtualWrite(V10, 0); // Reset the Blynk app switch
}
Hall Effect Sensors (Monitors): The Hall Effect Sensors monitor the position of the garage door. The sensors are non-latching and are digital (it either does or doesn't sense a magnet; an analog output sensor would measure the magnet's strength). I used two neodymium magnets (together to make them stronger) on the garage door to trigger the sensors. I had some CAT6 cable, so I ran it from the NodeMCU on the garage ceiling to each Hall Sensor. The garage door can be in one of three states (since I don't care which direction the door is moving):
- Closed: The Hall Effect Sensor at the door closed position is activated by the magnets.
- Open: The Hall Effect Sensor at the door open position is activated by the magnets.
- Ajar: Neither Hall Effect Sensor is activated. So the door could be moving or stopped in an open position.
I don't like polling for events to happen. So, the Hall Sensors use "Interrupts" to indicate a change of state (ex. door closed to moving). An Interrupt is exactly that, when there is a state change with a Hall Sensor, it interrupts the program code to immediately run a short function.
// Interrupt Declarations
// ESP boards need ICACHE_RAM_ATTR included
void ICACHE_RAM_ATTR InterruptDoorClosed();
void ICACHE_RAM_ATTR InterruptDoorOpen();
// this is only a snippet from the "setup" function
void setup() {
// Interrupts set for the two hall sensors activated on a CHANGE of state
// the Hall sensors require a pullup resistor, I used the internal pullup
pinMode(HALL_CLOSED_PIN,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(HALL_CLOSED_PIN), InterruptDoorClosed, CHANGE);
pinMode(HALL_OPEN_PIN,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(HALL_OPEN_PIN), InterruptDoorOpen, CHANGE);
}
// Interrupt for the Hall Sensor at the Door CLOSED point
// activated on CHANGE and Sets a timer to immediately CheckGarageState
void InterruptDoorClosed() {
hallClosedState = digitalRead(HALL_CLOSED_PIN); // get the sensor state
myTimer.setTimeout(10, GarageStateChanged); // run the function in 10ms
}
// Interrupt for the Hall Sensor at the Door OPEN point
// activated on CHANGE and Sets a timer to immediately CheckGarageState
void InterruptDoorOpen() {
hallOpenState = digitalRead(HALL_OPEN_PIN); // get the sensor state
myTimer.setTimeout(10, GarageStateChanged); // run the function in 10ms
}
Passive Piezo Buzzer: When the door is remotely activated (using the Blynk app), I wanted to alert anyone in the garage that the door was about to move. So I added a buzzer to play a short song. I added a 100 ohm resister to the circuit, although I'm not sure it's necessary. I also made the song non-blocking. Even though this requires me to get the length of the song so I don't activate the door until it completes.
LED's: Finally, I added two LED's, one green and one red. The LED's each require a 220 ohm resistor in their circuit. The green LED indicates that the Garage MC is connected to the Blynk server. And the red LED indicates that the Garage MC has lost it's connection.
My Programming Flow: If you normally don't use timers in your code, the flow of my program may be difficult to grasp [and I'm even not sure I like it anymore]. It basically works like this:
- The setup() function sets timers to:1. Check the internet connection.2. Check the state of the garage door (CheckGarageState), which updates Home Assistant. Home Assistant then handles the automations.
- The loop() function keeps the timers running.
- When there is a change in the state of the garage door (closed to open), an Interrupt is triggered. The interrupts flag a state change then set an immediate timer (10 milliseconds) to check the state of the garage door (CheckGarageState), which updates Home Assistant.
I wanted the Garage MC to look semi-professional, so I decided to mount all the components on a Perfboard (individual copper pads with pre-drilled holes). With that I had to learn how to solder. Which isn't necessarily difficult, but can be difficult to do well.
The Case: A 3D printer is on my son's Christmas list. Hopefully Santa comes through because I'd like to use it as well. As it turned out I had an old Chamberlain myQ (now "Merlin") that I took apart, cut and drilled holes in, and turned it into a project case. I think it turned out okay, even with the relay sticking out the bottom.
Perfboard: Based on the size of the case, I selected a perfboard that would fit in the case, and almost fit the NodeMCU. As you can see in my diagram, the top pin on each side wouldn't fit (D0 and A0), but I wasn't using them anyways. I didn't remove them, they just aren't connected to anything.
I soldered two header pins on the Perfboard (16 pins long, but the NodeMCU has 15 pins on each side). So, the NodeMCU goes on the bottom 15 pins. I mainly soldered connection wires on the bottom of the Perfboard (with a couple of exceptions), and components on top. And to shorten a long story...
And mounted it in my garage above the garage door opener (the yellow CAT6 cable runs to the Hall Sensors by the door track)...
Power: There is an electrical outlet in the ceiling for the Liftmaster opener. So I decided to keep things simple. I plugged in an old Blackberry cell phone charger and used a old USB cable to power the Garage MC.
What would I do different?I'm actually pretty happy with my final result. But there are a couple of changes I'd make:
- I placed the screw connectors for the Hall Effects Sensors facing each other, thinking I was smart and saving space. This made it difficult to perfectly bend the wires of the CAT6 cable into position. I'd definitely take the straight ahead approach next time, and put the connectors side-by-side.
Over the Covid stay at home Christmas break I started learning and setting up Home Assistant. As a result, I decided that I wanted the Garage MC to report back and be controlled by Home Assistant. Because of that, I decided to make the following changes to my code: adding OTA updates, and adding MQTT to communicate with Home Assistant.
OTA Updates
This was the first update to the Garage MC that I've made since October because frankly, it didn't need one. But, climbing the ladder to do updates was a pain so I implemented OTA updates.
I initially used the #include <ArduinoOTA.h> library. But it was a pain getting through my router's firewall. So I recently changed to #include <AsyncElegantOTA.h>.
I added a timeout (to turn off the update process in case I forgot), and made the LED's blink so I'd know the GarageMC was in "update mode". Global variables as follows:
// OTA variables
AsyncWebServer server(80); // OTA Updates - Web Server
#define CLIENT_NAME "GarageMC" // For MQTT topics, MQTT Client ID, and ArduinoOTA
const char ota_pass[] = SECRET_OTA_PWD;
const unsigned long OTA_TIMEOUT = 300000; // Turn off OTA after 5 min. if no update
unsigned long otaTimeout = 0; // End time for OTA = millis()+OTA_TIMEOUT
bool otaOn = false; // set to "true" to turn on OTA updates
const int OTA_BLINK_DELAY = 300; // blink the leds while OTA is ON
unsigned long otaBlinkTimer = 0; // next led blink time
In setup(), I initialize OTA as follows:
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Welcome to the GarageMC!");
});
AsyncElegantOTA.begin(&server, "GarageMC", ota_pass); // Start AsyncElegantOTA
My main LOOP was updated so that if OTA updates was turned on, my Timers would stop.
void loop() {
mqttLoop(); // Check for mqtt messages (and to stop the ota)
if (otaOn) {
if ((unsigned long)(millis() - otaStart) > OTA_TIMEOUT) {
otaReset();
}
// blink the red and green leds while OTA is on
if ((unsigned long)(millis() - otaBlinkTimer) > OTA_BLINK_DELAY) {
otaBlinkTimer = millis();
SetWifiLeds(!digitalRead(WIFI_ON_PIN));
}
}
else { // normal loop
myTimer.run(); // Events Timer (run either way)
}
}
Home Assistant send an MQTT message to the callback function:
if (newPayload == "OTA_UPDATE") {
otaUpdate();
}
MQTT to Home Assistant
I won't go into Home Assistant or MQTT, because I'm still learning them (although have enough of a handle on them now). I used the popular #include <PubSubClient.h> library. The details can be found in my full code. But basically, as a client you connect to the MQTT broker on Home Assistant. You "publish" updates to the MQTT broker, and "subscribe" to topics that you want to receive updates on through a "callback" function.
In it's simplest, connect to the MQTT broker with:
// subscribe to the control topics
const char topicGarageControl[] = CLIENT_NAME"/control";
const char topicGarageOTA[] = CLIENT_NAME"/ota";
// publish to the state topic, the state of the door
const char topicGarageState[] = CLIENT_NAME"/state";
WiFiClient espClient;
PubSubClient client(espClient); // client for connection to MQTT broker
client.setServer(mqtt_server, mqtt_port); // server to connect to
client.setCallback(callback); // the "callback" function for subscribed topics
client.connect(mqtt_client_name, mqtt_user, mqtt_pass); // connect to MQTT broker
// after connecting, subscribe to topics you want
client.subscribe(topicGarageControl);
client.subscribe(topicGarageOTA);
When the garage door state changes (open->ajar->closed), I publish the change to Home Assistant:
// topic = CLIENT_NAME/state
// payload = door state (open/ajar/closed)
client.publish(topic.c_str(), payload.c_str(), retain);
So far, so good. Now I just have to figure out Home Assistant to do something with the data!
Blynk App[REMOVED]
Final CommentsAlthough I added schematics, I also didn't go into specific details on my production wiring, etc. I believe we learn more by trying to it ourselves. With that being said, if your stuck or need help to just get the project working (or wondering why I've done something), send me a message and I'll be glad to help.
I appreciate any feedback and hope you liked my project.
Comments