Skip to content

Instantly share code, notes, and snippets.

@williamdenton
Created July 17, 2019 08:20
Show Gist options
  • Save williamdenton/02817e7844ce330e87cb480f484050fc to your computer and use it in GitHub Desktop.
Save williamdenton/02817e7844ce330e87cb480f484050fc to your computer and use it in GitHub Desktop.
HomebridgeSetup for Garage Door
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#define STASSID ""
#define STAPSK ""
#define APIKEY ""
#define LED_ON LOW
#define LED_OFF HIGH
#define LIMIT_SWITCH_UP_POSITION D3
#define LIMIT_SWITCH_DOWN_POSITION D8
#define LIMIT_SWITCH_UP_ACTIVATED HIGH
#define LIMIT_SWITCH_DOWN_ACTIVATED HIGH
#define DOOR_RELAY D5
#define DOOR_TRAVEL_TIME_MILLIS 15 * 1000 //15 sec
const char* ssid = STASSID;
const char* password = STAPSK;
const char* apiKey = APIKEY;
enum doorState {
isOpen,
isOpening,
isClosing,
isClosed,
isStopped,
isInvalid
};
IPAddress ip (192, 168, 1 , 199);
IPAddress gateway(192, 168, 1 , 1 );
IPAddress ipmask (255, 255, 255, 0 );
IPAddress dns (192, 168, 1 , 2 );
ESP8266WebServer server(80);
unsigned long lastKnownStateTime = 0;
doorState lastKnownState;
doorState getDoorState() {
//the polling/looping nature of the system means we can capture the last known state
//then can infer if opening or closing if door starts moving
bool isAtUpLimit = digitalRead(LIMIT_SWITCH_UP_POSITION) == LIMIT_SWITCH_UP_ACTIVATED;
bool isAtDownLimit = digitalRead(LIMIT_SWITCH_DOWN_POSITION) == LIMIT_SWITCH_DOWN_ACTIVATED;
if (isAtUpLimit && isAtDownLimit) {
return isInvalid;
}
if (isAtUpLimit) {
lastKnownState = isOpen;
lastKnownStateTime = millis();
return isOpen;
}
if (isAtDownLimit) {
lastKnownState = isClosed;
lastKnownStateTime = millis();
return isClosed;
}
if (isLastKnownStateRecent()) {
switch(lastKnownState){
case isClosed:
return isOpening;
case isOpen:
return isClosing;
}
}
return isStopped;
}
bool isLastKnownStateRecent() {
return lastKnownStateTime > 0 && (millis() - lastKnownStateTime) < DOOR_TRAVEL_TIME_MILLIS;
}
void pushDoorTrigger() {
digitalWrite(DOOR_RELAY, HIGH);
delay(500); //hold button down
digitalWrite(DOOR_RELAY, LOW);
delay(500); // wait for door action to start and limit switches to disengage so status reports correctly
}
void handleStatus() {
digitalWrite(LED_BUILTIN, LED_ON);
if (!isAuthenticated()) {
rejectRequest();
digitalWrite(LED_BUILTIN, LED_OFF);
return;
}
sendGarageDoorStatus();
digitalWrite(LED_BUILTIN, LED_OFF);
}
void handleOpenDoor() {
digitalWrite(LED_BUILTIN, LED_ON);
if (!isAuthenticated()) {
rejectRequest();
digitalWrite(LED_BUILTIN, LED_OFF);
return;
}
switch (getDoorState()) {
case isClosed:
case isClosing:
case isStopped:
case isInvalid:
pushDoorTrigger();
break;
}
sendGarageDoorStatus();
digitalWrite(LED_BUILTIN, LED_OFF);
}
void handleCloseDoor() {
digitalWrite(LED_BUILTIN, LED_ON);
if (!isAuthenticated()) {
rejectRequest();
digitalWrite(LED_BUILTIN, LED_OFF);
return;
}
switch (getDoorState()) {
case isOpen:
case isOpening:
case isStopped:
case isInvalid:
pushDoorTrigger();
break;
}
sendGarageDoorStatus();
digitalWrite(LED_BUILTIN, LED_OFF);
}
String getStateForResponse() {
//https://github.com/KhaosT/HAP-NodeJS/blob/81319b35d1588453cfcb1a823805643de7df74dc/lib/gen/HomeKitTypes.js#L476-L481
//https://github.com/washcroft/homebridge-http-garagedoorcontroller/blob/95924d796c0dae2110853d0f762791b8f24df9aa/index.js#L671-L687
switch (getDoorState()) {
case isOpen:
return "OPEN";
case isOpening:
return "OPENING";
case isClosed:
return "CLOSED";
case isClosing:
return "CLOSING";
case isStopped:
return "STOPPED";
default:
return "UNKNOWN";
}
}
void sendGarageDoorStatus() {
server.send(200, "text/plain", getStateForResponse());
}
void handleNotFound() {
digitalWrite(LED_BUILTIN, LED_ON);
if (!isAuthenticated()) {
rejectRequest();
digitalWrite(LED_BUILTIN, LED_OFF);
return;
}
server.send(404, "text/plain", "That's totally not a thing that I can do, I'm a Garage Door not a genie");
digitalWrite(LED_BUILTIN, LED_OFF);
}
bool isAuthenticated() {
if (server.hasHeader("x-api-key")) {
String suppliedApikey = server.header("x-api-key");
if (suppliedApikey == apiKey) {
return true;
}
Serial.print("Auth Failed with supplied key: ");
Serial.println(suppliedApikey);
return false;
}
//TODO: header is not being send/parsed correctly
Serial.println("Auth Failed - No Api Key");
return true;
return false;
}
void rejectRequest() {
server.send(403, "text/plain", "Unauthorized");
}
void setup(void) {
pinMode(LIMIT_SWITCH_UP_POSITION, INPUT_PULLUP);
pinMode(LIMIT_SWITCH_DOWN_POSITION, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(DOOR_RELAY, OUTPUT);
Serial.begin(115200);
WiFi.config(ip, gateway, ipmask, dns);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/status", HTTP_GET, handleStatus);
server.on("/open", HTTP_PUT, handleOpenDoor);
server.on("/close", HTTP_PUT, handleCloseDoor);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
getDoorState();
server.handleClient();
}
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": ""
},
"accessories": [
{
"accessory": "HttpGarageDoorController",
"name": "Garage Door",
"httpHost": "192.168.1.199",
"httpPort": 80,
"httpSsl": false,
"httpStatusPollMilliseconds": 2000,
"httpRequestTimeoutMilliseconds": 5000,
"httpHeaderName": "x-api-key",
"httpHeaderValue": "",
"apiConfig":
{
"apiType": "Generic",
"doorOpenMethod": "PUT",
"doorOpenUrl": "/open",
"doorOpenSuccessContent": "OPENING",
"doorCloseMethod": "PUT",
"doorCloseUrl": "/close",
"doorCloseSuccessContent": "CLOSING",
"doorStateMethod": "GET",
"doorStateUrl":"/status"
}
}
],
"platforms": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment