Today's project: Controlling the garage doors with my phone

I wanted to link my garage doors to my Homekit, so that I can control them by using my mobile phone or by voice commands using Siri. I have Homekit setup with the help of Homebridge running on a Raspberry Pi, and my garage doors are operated by Turner 423 (Also known as Novomatic 423). This particular door operator has two pins on it which can be connected to simulate the press of the remote. If this wouldn't have been the case, I would have taken a remote apart and replaced the buttons on it with the wires coming from the relays.

I decided to use Lolin D1 mini again, with 2 relays, one per door. I added an extension cord to the power outlet which provided power to the garage door motor, and attached a mobile phone charger with micro-usb cable there to power the Lolin up.

The idea was to run a server on the Lolin and to do a rest call to control the doors. NOTE: Not the most secure way of doing it :)

Parts

Wiring it

The relays need 5v and GND and a connection to digital output on the Lolin. The digital output pin then controls the operation of the relay. The sensors need to be connector to GND from other wire, and other wire to digital pin on Lolin that is used to read the sensor.

The code includes some code for sensors that would detect when the door is open, but I decided to not use them as I felt it was better to just report the door as open whenever the door closed sensor did not report that the door was closed completely.

Also if you have just one door, you can ignore the parts for DOOR2. See the code for the correct pins where to attach the relay and the sensor. For DOOR1 these would be D6 for relay, and D1 for sensor. The part without wires on the sensor should be placed in such way that it aligns with the wired part when the door is closed, see following image:

IMG_1466-1

Wiring diagram

Here is a diagram to show the wirings more clearly. With this wiring, the relay should be toggled for X amount of seconds (default 1 second, set in the code). Test it before wiring the relay to anything.

garage_door_wiring

The relay has 3 screw terminals on the other end of it. These are the ones you want to connect to the door motor. Two of them are always connected to each other, and they change when the data pin goes to LOW (connects to ground). So the relay can be used to either disconnect the two wires, or to connect them when enabled. In my case, I wanted to connect the two wires when the relay is enabled.

Using it

Navigate your browser (or use curl) to call http://192.168.1.123/1/status to get the status for the DOOR1. 0 means it's open, 1 means it's closed. Navigating to http://192.168.1.123/1/open or http://192.168.1.123/1/close or http://192.168.1.123/1/stop all do the same thing, which is to trigger that relay for that X amount of time. Replace the 192.168.1.123 with the IP that your Lolin happened to get from your router.

This is in no way secure, so make sure you have strong wifi password, and block the access to internet for that Lolin so it can only be controlled from within your local network.

Additionally the code does not care currently if the door is closed and you ask it to be closed again, it's still going to toggle the relay, which ends up opening the garage door. That could be rather easily added to the code, but I decided to leave it out for now.

Homebridge

For Homebridge integration (see Http-GarageDoor), I am using the following config:

    {
      "accessory": "Http-GarageDoor",
      "name": "Left Door",
      "debug": false,
      "username": "",
      "password": "",
      "immediately": false,
      "polling": true,
      "pollInterval": 30000,
      "http_method": "GET",
      "urls": {
        "readCurrentState": { "url": "http://192.168.1.123/1/status", "body": "" },
        "readTargetState": { "url": "http://192.168.1.123/1/target", "body": "" },
        "open": { "url": "http://192.168.1.123/1/open", "body": "" },
        "close": { "url": "http://192.168.1.123/1/close", "body": "" }
      },
      "mappers": [
        {
          "type": "regex",
          "parameters": {
            "regexp": "^The door is currently (OPEN|CLOSED), yo!$",
            "capture": "1"
          }
        }
      ]
    }

The code

#include <ESP8266WiFi.h>

#define DOOR1CONTROLRELAYPIN D6
#define DOOR1SENSORCLOSEDPIN D1
#define DOOR1SENSOROPENPIN D2

#define DOOR2CONTROLRELAYPIN D7
#define DOOR2SENSORCLOSEDPIN D3
#define DOOR2SENSOROPENPIN D4

#define RELAYENABLEDTIMEMS 1000

const char* ssid = "yourWifiSSID";
const char* password = "yourWifiPassword";

WiFiServer server(80);

void setup() {
  pinMode(DOOR1CONTROLRELAYPIN, OUTPUT);
  digitalWrite(DOOR1CONTROLRELAYPIN, HIGH);
  pinMode(DOOR1SENSORCLOSEDPIN, INPUT_PULLUP);
  pinMode(DOOR1SENSOROPENPIN, INPUT_PULLUP);
  
  pinMode(DOOR2CONTROLRELAYPIN, OUTPUT);
  digitalWrite(DOOR2CONTROLRELAYPIN, HIGH);
  pinMode(DOOR2SENSORCLOSEDPIN, INPUT_PULLUP);
  pinMode(DOOR2SENSOROPENPIN, INPUT_PULLUP);

  connectToWifi();
  server.begin();
}

void connectToWifi() { 
  WiFi.mode(WIFI_STA);
  WiFi.hostname("garagedoors");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.reconnect();
    delay(5000);
    return;
  }

// Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }

  // Wait until the client sends some data
  while(!client.available()){
    delay(10);
  }
  
  // Read the first line of the request
  String request = client.readStringUntil('\r');
  client.flush();
 
  execute(request, &client);

  delay(10);
}

void execute(String request, WiFiClient *client) {
  String doorNumString = request.substring(request.indexOf("/")+1);
  String commandString = doorNumString.substring(doorNumString.indexOf("/")+1, doorNumString.indexOf(" "));
  doorNumString = doorNumString.substring(0, doorNumString.indexOf("/"));

  if (commandString.equals("status")) {
    sendStatus(doorNumString.toInt(), client);
  } else if (commandString.equals("target")) {
    sendTargetStatus(doorNumString.toInt(), client);
  } else if (commandString.equals("open") || commandString.equals("close") || commandString.equals("stop")) {
    operate(commandString, doorNumString.toInt());
    emptyResponse(client);
  } else {
    emptyResponse(client);
  }
}

/*
    "0": open
    "1": closed
  */
void sendStatus(int doorNum, WiFiClient *client) {
  client->println("HTTP/1.1 200 OK");
  client->println("Content-Type: text/html");
  client->println("");
  int sensorClosedPin = DOOR1SENSORCLOSEDPIN;
  int sensorOpenPin = DOOR1SENSOROPENPIN;
  if (doorNum == 2) {
    sensorClosedPin = DOOR2SENSORCLOSEDPIN;
    sensorOpenPin = DOOR2SENSOROPENPIN;
  }
  if (digitalRead(sensorClosedPin) == LOW) {
    client->println("1");
  } else {
    client->println("0");
  }
}

/* "0": open
   "1": closed
 */
void sendTargetStatus(int doorNum, WiFiClient *client) {
  client->println("HTTP/1.1 200 OK");
  client->println("Content-Type: text/html");
  client->println("");
    int sensorClosedPin = DOOR1SENSORCLOSEDPIN;
  int sensorOpenPin = DOOR1SENSOROPENPIN;
  if (doorNum == 2) {
    sensorClosedPin = DOOR2SENSORCLOSEDPIN;
    sensorOpenPin = DOOR2SENSOROPENPIN;
  }
  if (digitalRead(sensorClosedPin) == LOW) {
    client->println("1");
  } else {
    client->println("0");
  }
}

void operate(String commandString, int doorNum) {
  int relayPin = DOOR1CONTROLRELAYPIN;
  int sensorClosedPin = DOOR1SENSORCLOSEDPIN;
  int sensorOpenPin = DOOR1SENSOROPENPIN;
  if (doorNum == 2) {
    relayPin = DOOR2CONTROLRELAYPIN;
    sensorClosedPin = DOOR2SENSORCLOSEDPIN;
    sensorOpenPin = DOOR2SENSOROPENPIN;
  } else if (doorNum != 1) {
    return;
  }
  if (commandString.equals("open")) {
    digitalWrite(relayPin, LOW);
    delay(RELAYENABLEDTIMEMS);
    digitalWrite(relayPin, HIGH);
  } else if (commandString.equals("close")) {
    digitalWrite(relayPin, LOW);
    delay(RELAYENABLEDTIMEMS);
    digitalWrite(relayPin, HIGH);
  }
}

void emptyResponse(WiFiClient *client) {
  client->println("HTTP/1.1 200 OK");
  client->println("Content-Type: text/html");
  client->println("");
  client->println("<!DOCTYPE HTML>");
  client->println("<html>");
  client->println("</html>");
}