Skip to content

Instantly share code, notes, and snippets.

@justincjahn
Last active October 14, 2020 08:49
Show Gist options
  • Save justincjahn/f2b79406f30d80740d42f3dfd13ab03c to your computer and use it in GitHub Desktop.
Save justincjahn/f2b79406f30d80740d42f3dfd13ab03c to your computer and use it in GitHub Desktop.
IOT Garage Door using ESP8266, ESP8266WebServer and Blynk w/ OTA
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IOT Garage Door</title>
<link rel="stylesheet"
href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<style type="text/css">
#wrapper { width: 600px; margin: 0px auto; }
h1.title, div.navigation { text-align: center; }
#submit-button { display: block; margin: 0px auto; }
div.navigation { margin-top: 3em; }
a, a:visited { color: #333; }
a:hover { color: rgb(34, 85, 196); }
</style>
</head>
<body>
<div id="wrapper">
<h1 class="title">Configuration</h1>
<form class="pure-form pure-form-aligned" id="config-form" method="POST" action="/config">
<fieldset>
<div class="pure-control-group">
<label for="sendNotifications">Send Push Notifications?</label>
<input type="checkbox" id="sendNotifications" name="sendNotifications" />
</div>
<div class="pure-control-group">
<label for="notificationTime">Notification Wait Time (sec)</label>
<input type="text" maxlength="4" id="notificationTime" name="notificationTime" class="pure-input-1-2" />
</div>
<div class="pure-control-group">
<label for="normallyClosed">Normally Closed?</label>
<input type="checkbox" id="normallyClosed" name="normallyClosed" />
</div>
<div class="pure-control-group">
<label for="triggerTimeout">Door Trigger Timeout</label>
<input type="text" maxlength="5" id="triggerTimeout" name="triggerTimeout" class="pure-input-1-2" />
</div>
<div class="pure-control-group">
<label for="blynkToken">Blynk Token</label>
<input type="text" maxlength="32" id="blynkToken" name="blynkToken" class="pure-input-1-2" />
</div>
<input type="button" id="submit-button" class="pure-button pure-button-primary pure-input-1-3" value="Save" />
</fieldset>
</form>
<div class="navigation">
<a href="/">Home</a> | <a href="/wifi.htm">Wireless Configuration</a> | <a href="/upload">Upload File</a> | <a href="/update">Update Software</a>
</div>
</div>
<script type="text/javascript">
function g(i){return document.getElementById(i);};
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 200) {
if (xhr.response.normallyClosed) {
g("normallyClosed").setAttribute("checked", "checked");
}
if (xhr.response.sendNotifications) {
g("sendNotifications").setAttribute("checked", "checked");
}
if (xhr.response.notificationTime) {
g("notificationTime").setAttribute("value", xhr.response.notificationTime);
}
g("triggerTimeout").setAttribute("value", xhr.response.triggerTimeout);
g("blynkToken").setAttribute("value", xhr.response.blynkToken);
} else {
//alert("An unknown error occured collecting settings!");
}
}
}
xhr.open("GET", "/config.json", true);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.send();
// Sanity checks before submitting
g("submit-button").addEventListener("click", function (e) {
g("submit-button").setAttribute("disabled", "disabled");
var sBlynk = g("blynkToken").value;
var iTrigger = g("triggerTimeout").value;
var bError = false;
if (iTrigger <= 0 || iTrigger >= 100000) {
alert("Garage Door Trigger Timeout must be greater than zero, but less than 100000.");
bError = true;
}
if (sBlynk.length != 0 && sBlynk.length != 32) {
alert("The Blynk Token field must either be empty, or a 32-character token.");
bError = true;
}
if (!bError) {
g("config-form").submit();
}
g("submit-button").removeAttribute("disabled");
});
</script>
</body>
</html>
/**
* A web-based garage door opener using a Wemos D1 Mini and a bunch of cool Arduino libraries.
*
* Can be used independently or with this homebridge plugin:
* https://github.com/washcroft/homebridge-http-garagedoorcontroller
**/
//#ifndef ESP8266
// #define ESP8266
//#endif
//#define DEBUG_SERIAL
#ifdef DEBUG_SERIAL
#define DEBUG_BEGIN Serial.begin(115200)
#define DEBUG_PRINT(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_BEGIN
#endif
#include <Arduino.h>
#include <PersWiFiManager.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPUpdateServer.h>
#include <EasySSDP.h>
#include <SPIFFSReadServer.h>
#include <DNSServer.h>
#include <FS.h>
#include <BlynkSimpleEsp8266.h>
#include <ESP8266mDNS.h>
// CONFIG
#define DEVICE_NAME "IOT Garage Door" // Appears in SSDP, AP mode SSID, etc.
#define AP_MODE_PASSWORD "garagedoor" // The password to connect to the device when in AP mode
#define HTTP_PORT 80 // What port should the web server run on?
#define DEBOUNCE_TIME 500 // The amount of time to wait for the contact sensor to become stable in millis
#define TRIGGER_TIME 250 // The amount of time to keep the DOOR_PIN high in millis
#define WATCHDOG_TIMER 120000 // The amount of time to retry connection to the wifi
#define CONTACT_PIN D1 // The pin used by the contact sensor
#define DOOR_PIN D7 // The pin used to open the door
#define VLED_PIN V0 // The virtual pin used by Blynk for the status LED.
#define VBUTTON_PIN V1 // The virtual pin used by Blynk for the open button.
//
// BEGIN
//
SPIFFSReadServer server(HTTP_PORT);
ESP8266HTTPUpdateServer httpUpdater;
DNSServer dnsServer;
PersWiFiManager persWM(server, dnsServer);
MDNSResponder mdns;
// Blynk LED
WidgetLED blynkLED0(VLED_PIN);
//
// BEGIN CONFIGURATION LOGIC
//
/**
* The configuration for this application.
*/
struct Config {
// If this is true, the board considers the door open if the pin is LOW instead of HIGH.
bool normallyClosed = false;
// If notifications should be sent at all. Allows users to disable them
bool sendNotifications = true;
// The amount of time in seconds that the system should wait to send a notification
unsigned int notificationTime = 300;
// The number of millis to wait before enabling the trigger function again.
unsigned int triggerTimeout = 30000;
char blynkToken[33] = {'\0'};
} config;
/**
* Load the configuration from the config.json file if it exists, or save a default version
* of the file.
*/
bool loadConfig() {
// Open the config file in read mode
File file = SPIFFS.open("/config.json", "r");
// Sanity check
if (!file) {
DEBUG_PRINT("Failed to open config file. It may not exist.");
return saveConfig();
}
// Parse the contents of the file
StaticJsonBuffer<512> jsonBuffer;
JsonObject &root = jsonBuffer.parseObject(file);
// Sanity check
if (!root.success()) {
DEBUG_PRINT("Unable to parse the configuration file. Overwriting it.");
return saveConfig();
}
// Copy the values from the JsonObject to the struct
config.normallyClosed = root["normallyClosed"];
if (root.containsKey("sendNotifications")) {
config.sendNotifications = root["sendNotifications"];
}
if (root.containsKey("notificationTime")) {
config.notificationTime = root["notificationTime"];
}
if (root.containsKey("triggerTimeout")) {
config.triggerTimeout = root["triggerTimeout"];
}
if (root.containsKey("blynkToken")) {
strcpy(config.blynkToken, root.get<const char*>("blynkToken"));
}
return true;
}
/**
* Save the config.json file based on the current values of the config struct.
*/
bool saveConfig() {
// Open the file in write mode
File file = SPIFFS.open("/config.json", "w");
// Sanity check
if (!file) {
DEBUG_PRINT("Failed to open the config file for writing.");
return false;
}
StaticJsonBuffer<256> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Convert the config struct into JSON data.
root["normallyClosed"] = config.normallyClosed;
root["triggerTimeout"] = config.triggerTimeout;
root["blynkToken"] = config.blynkToken;
root["sendNotifications"] = config.sendNotifications;
root["notificationTime"] = config.notificationTime;
// Write it to the file
if (root.printTo(file) == 0) {
DEBUG_PRINT("Failed to save config to file.");
}
// Update the blynk config
configureBlynk();
return true;
}
/**
* Save the configuration from the web interface. Simply changes running
* config and calls saveConfig().
*/
void saveConfigFromWeb() {
int iTriggerTimeout = 0;
int iNotificationTime = 0;
if (!server.hasArg("blynkToken")) {
server.send(400, "text/plain", "Missing blynkToken argument.");
return;
} else {
if (server.arg("blynkToken").length() != 0 && server.arg("blynkToken").length() != 32) {
server.send(400, "text/plain", "blynkToken must be either blank or 32 characters in length.");
}
}
if (!server.hasArg("triggerTimeout")) {
server.send(400, "text/plain", "Missing triggerTimeout argument.");
return;
} else {
iTriggerTimeout = server.arg("triggerTimeout").toInt();
if (iTriggerTimeout <= 0) {
server.send(400, "text/plain", "triggerTimeout must be greater than zero.");
return;
}
}
if (!server.hasArg("notificationTime")) {
server.send(400, "text/plain", "Missing notificationTime argument.");
return;
} else {
iNotificationTime = server.arg("notificationTime").toInt();
if (iTriggerTimeout <= 0) {
server.send(400, "text/plain", "notificationTime must be greater or equal to zero.");
return;
}
}
// Set values
config.normallyClosed = server.hasArg("normallyClosed");
config.sendNotifications = server.hasArg("sendNotifications");
config.triggerTimeout = iTriggerTimeout;
config.notificationTime = iNotificationTime;
strcpy(config.blynkToken, server.arg("blynkToken").c_str());
if (saveConfig()) {
server.sendHeader("Location", "/");
server.send(303, "text/plain", "OK");
}
}
/**
* Called when the settings for Blynk change
*/
void configureBlynk() {
Blynk.config(config.blynkToken);
}
//
// END CONFIGURATION LOGIC
//
//
// BEGIN GARAGE DOOR LOGIC
//
unsigned long lastDebounceTime;
unsigned long lastTriggerTime;
unsigned long notificationTimer;
unsigned long wifiWatchdogTimer;
int garageDoorStatus = LOW;
int lastGarageDoorStatus = LOW;
bool canTriggerGarageDoor = true;
bool shouldTriggerGarageDoor = false;
bool shouldNotify = false;
bool isApMode = false;
/**
* Returns true if the garage door is open. Accounts for a NO or NC sensor.
*/
bool isGarageDoorOpen() {
bool isOpen = (garageDoorStatus == HIGH);
if (config.normallyClosed) isOpen = !isOpen;
return isOpen;
}
/**
* Checks the current status of the garage door contact switch and performs some
* debouncing.
*/
void garageDoorCheck() {
int reading = digitalRead(CONTACT_PIN);
if (reading != lastGarageDoorStatus) {
lastDebounceTime = millis();
}
// Check to see if the contact switch has been stable for the debounce time
if ((millis() - lastDebounceTime) > DEBOUNCE_TIME) {
// Ensure the reading has actually changed from the current state
if (reading != garageDoorStatus) {
garageDoorStatus = reading;
if (isGarageDoorOpen()) {
DEBUG_PRINT("GARAGE OPEN");
blynkLED0.on();
if (config.sendNotifications) {
notificationTimer = millis();
shouldNotify = true;
}
} else {
DEBUG_PRINT("GARAGE CLOSED");
blynkLED0.off();
}
}
}
// Set the lastGarageDoorStatus to the current reading so the loop knows what
/// happened previously
lastGarageDoorStatus = reading;
}
/**
* Return the status of the garage door to the web browser.
*/
void getGarageDoorStatus() {
if (isGarageDoorOpen()) {
server.send(200, "text/plain", "OPEN");
} else {
server.send(200, "text/plain", "CLOSED");
}
}
/**
* Trigger the garage door pin to high for a few milliseconds. Relies on
* garageDoorTriggerCheck to disable the pin.
*/
void triggerGarageDoor() {
// If the user clicked the door button, they shouldn't be able to click it
/// again for a certain amount of time.
if (canTriggerGarageDoor && !isApMode) {
// Disable this function until the timeout expires
canTriggerGarageDoor = false;
// Tell the rest of the script that we've triggered the garage door and
// what time it was triggered.
shouldTriggerGarageDoor = true;
lastTriggerTime = millis();
// Set the appropriate pins to high
digitalWrite(DOOR_PIN, HIGH);
digitalWrite(D4, HIGH);
server.send(200, "text/plain", "OK");
} else {
server.send(403, "text/plain", "TEMPORARILY DISABLED");
}
}
/**
* Called in the loop function to check if the garage door should be triggered.
*/
void garageDoorTriggerCheck() {
// If the garage door was triggered, set the pin to low if enough time has elapsed.
if (shouldTriggerGarageDoor && ((unsigned long)millis() - lastTriggerTime) > TRIGGER_TIME) {
shouldTriggerGarageDoor = false;
digitalWrite(DOOR_PIN, LOW);
digitalWrite(D4, LOW);
}
// If the trigger function is disabled, turn it back on after enough time has elapsed.
if (!canTriggerGarageDoor && ((unsigned long)millis() - lastTriggerTime) > config.triggerTimeout) {
canTriggerGarageDoor = true;
}
}
/**
* Called in the loop function to check if we should send a notification
*/
void garageNotificationTimer() {
unsigned long triggerTime = config.notificationTime * 1000;
unsigned long currentMillis = millis();
if (shouldNotify && (currentMillis - notificationTimer >= triggerTime)) {
Blynk.notify("Your garage door is open.");
shouldNotify = false;
}
}
/**
* Called in the loop function to check if we should try to reconnect to the wifi
*/
void wifiWatchdogCheck() {
if (isApMode && ((unsigned long)millis() - wifiWatchdogTimer) > WATCHDOG_TIMER) {
DEBUG_PRINT("Wi-Fi reconnect attempt");
if (!persWM.attemptConnection()) {
DEBUG_PRINT("Failed to reconnect to wifi... Will Retry.");
wifiWatchdogTimer = millis();
}
}
}
/**
* Output the raw status of the garage door sensor. Debugging!
*/
void rawStatus() {
int reading = digitalRead(CONTACT_PIN);
char buf[5];
sprintf(buf, "%d", reading);
server.send(200, "text/plain", buf);
}
//
// END GARAGE DOOR LOGIC
//
//
// BEGIN FILE MANAGEMENT LOGIC
//
const char* uploadForm = "<form method='POST' action='/upload' enctype='multipart/form-data'><input type='file' name='upload'><input type='submit' value='Upload'></form>";
File fsUploadFile;
void handleFileUpload(){
if (isApMode) {
server.send(403, "text/plain", "TEMPORARILY DISABLED");
return;
}
// Sanity check
if(server.uri() != "/upload") return;
// Get the HTTPUpload object
HTTPUpload& upload = server.upload();
// Check the status
if(upload.status == UPLOAD_FILE_START){
String filename = upload.filename;
// Prepend a forward slash if it doesn't have one
if(!filename.startsWith("/")) {
filename = "/" + filename;
}
DEBUG_PRINT("Upload: " + filename);
// Open the file on SPIFFS
fsUploadFile = SPIFFS.open(filename, "w");
// Clear the filename
filename = String();
} else if(upload.status == UPLOAD_FILE_WRITE){
if(fsUploadFile) {
fsUploadFile.write(upload.buf, upload.currentSize);
}
} else if(upload.status == UPLOAD_FILE_END){
if(fsUploadFile) {
fsUploadFile.close();
}
}
}
void handleFileDelete(){
if (isApMode) {
server.send(403, "text/plain", "TEMPORARILY DISABLED");
return;
}
// If there was no argument sent, stop
if(server.args() == 0) {
return server.send(500, "text/plain", "BAD ARG");
}
String path = server.arg(0);
DEBUG_PRINT("Delete: " + path);
// We can't delete the root!
if(path == "/") {
return server.send(500, "text/plain", "BAD PATH");
}
// We can't delete something that doesn't exit
if(!SPIFFS.exists(path)) {
return server.send(404, "text/plain", "FileNotFound");
}
// Remove and return
SPIFFS.remove(path);
server.sendHeader("Location", "/");
server.send(303, "text/plain", "");
// Cleanup
path = String();
}
void handleFileList() {
if (isApMode) {
server.send(403, "text/plain", "TEMPORARILY DISABLED");
return;
}
String path;
if(!server.hasArg("dir")) {
path = "/";
} else {
path = server.arg("dir");
}
Dir dir = SPIFFS.openDir(path);
path = String();
// Loops through a list of files and turns it into JSON.
String output = "[";
while(dir.next()){
File entry = dir.openFile("r");
if (output != "[") output += ',';
bool isDir = false;
output += "{\"type\":\"";
output += (isDir)?"dir":"file";
output += "\",\"name\":\"";
output += String(entry.name()).substring(1);
output += "\"}";
entry.close();
}
output += "]";
// Send the JSON back to the browser
server.send(200, "application/json", output);
}
void bootstrapUploader() {
server.on("/list", HTTP_GET, handleFileList);
server.on("/list.json", HTTP_GET, handleFileList);
server.on("/upload", HTTP_GET, [](){
server.sendHeader("Connection", "close");
server.send(200, "text/html", uploadForm);
});
server.on("/upload", HTTP_POST, []() {
server.sendHeader("Location", "/");
server.send(303, "text/plain", "");
}, handleFileUpload);
server.on("/delete", HTTP_GET, handleFileDelete);
}
//
// END FILE MANAGEMENT LOGIC
//
//
// BEGIN BLYNK LOGIC
//
/**
* Trigger the garage door from virtual pin 1.
*/
BLYNK_WRITE(VBUTTON_PIN) {
int i = param.asInt();
if (i == 1) { triggerGarageDoor(); }
}
/**
* The Blynk cloud is requesting the status of our garage door.
*/
BLYNK_READ(VLED_PIN) {
if (isGarageDoorOpen()) {
blynkLED0.on();
} else {
blynkLED0.off();
}
}
//
// END BLYNK LOGIC
//
void setup() {
DEBUG_BEGIN;
DEBUG_PRINT();
// PIN Setup
pinMode(DOOR_PIN, OUTPUT);
pinMode(D4, OUTPUT); // @note LED is on when the pin is LOW.
// Ran every time wifi is connected
persWM.onConnect([]() {
DEBUG_PRINT("wifi connected");
DEBUG_PRINT(WiFi.localIP());
isApMode = false;
EasySSDP::begin(server, DEVICE_NAME);
mdns.begin("garagedoor", WiFi.localIP());
});
// Ran every time the board is placed in AP mode
persWM.onAp([](){
DEBUG_PRINT("AP MODE");
DEBUG_PRINT(persWM.getApSsid());
isApMode = true;
wifiWatchdogTimer = millis();
});
// Allows serving of files from SPIFFS
SPIFFS.begin();
// Sets network name for AP mode
persWM.setApCredentials(DEVICE_NAME, AP_MODE_PASSWORD);
// Make connecting/disconnecting non-blocking
persWM.setConnectNonBlock(true);
// Load the configuration file
loadConfig();
// Set the default garage door status depending on the normally closed config
/// so we don't trigger a notification for no reason on boot.
if (config.normallyClosed) garageDoorStatus = HIGH;
// Configure Blynk for the first time
configureBlynk();
Blynk.setProperty(VLED_PIN, "label", "Garage Door Open?");
// In non-blocking mode, program will continue past this point without waiting
persWM.begin();
// Setup update server
httpUpdater.setup(&server, "/update");
// Handles uploading
bootstrapUploader();
// Bootstrap garage door API
server.on("/status", getGarageDoorStatus);
server.on("/trigger", triggerGarageDoor);
server.on("/config", HTTP_POST, saveConfigFromWeb);
server.on("/raw", rawStatus);
// Start the HTTPServer
server.begin();
// Notify other devices on the network we're a web server
mdns.addService("HTTP", "TCP", 80);
DEBUG_PRINT("setup complete.");
}
void loop() {
// In non-blocking mode, handleWiFi must be called in the main loop
persWM.handleWiFi();
// Process requests
dnsServer.processNextRequest();
server.handleClient();
// Check the garage door's status
garageDoorCheck();
// If the door was triggered, make sure it gets "untriggered".
garageDoorTriggerCheck();
// If the garage door timer is running, notify
garageNotificationTimer();
// Wifi Watchdog
wifiWatchdogCheck();
// Run Blynk
Blynk.run();
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IOT Garage Door</title>
<link rel="stylesheet"
href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<style type="text/css">
#wrapper { width: 425px; margin: 0px auto;}
#door-status { font-weight: bold; }
p.status, h1.title, div.navigation { text-align: center; }
p.status { margin: 2.75em 0 0.5em 0; }
div.documentation {
margin-top: 3em;
background-color: rgb(245, 244, 244);
padding: 0.5em 0.75em 0.25em 0.75em;
border-radius: 0.25em;
-webkit-box-shadow: inset 3px 3px 3px 0px rgba(0,0,0,0.05);
-moz-box-shadow: inset 3px 3px 3px 0px rgba(0,0,0,0.05);
box-shadow: inset 3px 3px 3px 0px rgba(0,0,0,0.05);
}
div.documentation > h2 { margin: 0; text-align: center; }
div.documentation ul { font-size: 0.9em; }
div.navigation { margin-top: 3em; margin-bottom: 3em; }
a, a:visited { color: #333; }
a:hover { color: rgb(34, 85, 196); }
li { margin-top: 1em; }
@media (max-width: 420px) { #wrapper { width: 310px; } }
</style>
</head>
<body>
<div id="wrapper">
<h1 class="title">IOT Garage Door</h1>
<p class="status">Your garage door is <span id="door-status"></span></p>
<div class="pure-g">
<button id="trigger-door" class="pure-button pure-button-primary pure-u-1">Trigger Garage Door</button>
</div>
<div class="documentation">
<h2>API Documentation</h2>
<ul>
<li><code>GET <strong>/status</strong></code>: <code>OPEN</code> or <code>CLOSED</code> depending on the status of the door sensor.</li>
<li><code>GET <strong>/trigger</strong></code>: Triggers the door to open. May return a 403 if the trigger timeout is still running.</li>
<li><code>GET <strong>/config.json</strong></code>: The unit's configuration information.</li>
<li><code>GET <strong>/raw</strong></code>: The raw status of the garage door sensor. Should be 1 or 0.</li>
<li><code>GET <strong>/list[.json]</strong></code>: Returns a list of files on the filesystem.</li>
<li><code>GET <strong>/delete?file={filename}</strong></code>: Removes a file from the filesystem.</li>
<li><code>POST <strong>/config</strong></code>: Sets the configuration. See <code>/config.htm</code> for names.</li>
<li>
<code>POST <strong>/upload</strong></code>: Sending a file to this address will save it to the filesystem.
<ul><li><code>curl -F "image=@firmware.bin" ip_address/update</code></li></ul>
</li>
<li>
<code>POST <strong>/update</strong></code>: Sending a file to this address will update the firmware.
<ul><li><code>curl -F "image=@firmware.bin" ip_address/update</code></li></ul>
</li>
</ul>
</div>
</div>
<div class="navigation">
<a href="/config.htm">Garage Door Configuration</a> | <a href="/wifi.htm">Wireless Configuration</a>
</div>
<script lang="text/javascript">
function g(i){return document.getElementById(i);};
g("trigger-door").onclick = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 200) {
alert("Triggered!");
} else {
alert("An unknown error occured!");
}
}
}
xhr.open("GET", "/trigger", true);
xhr.send();
};
var ticker = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 200) {
g("door-status").innerHTML = xhr.responseText;
}
setTimeout(ticker, 1000);
}
}
xhr.open("GET", "/status", true);
xhr.send();
};
ticker();
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IOT Garage Door - Wireless Configuration</title>
<link rel="stylesheet"
href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<script>
function g(i){return document.getElementById(i);};
function p(t,l){if(confirm(t)) window.location=l;};
function E(s){return document.createElement(s)};
</script>
<style type="text/css">
.button-error {
background: rgb(202, 60, 60);
color: white;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
#wrapper {
width: 425px;
margin: 0px auto;
}
#scanner-list.scanning {
border: 0.15em solid rgb(214, 213, 213);
border-radius: 3px;
padding: 3px;
list-style: none;
}
#scanner-list li {
clear: both;
margin: 0.75em 0.5em;
}
#scanner-list li span.scanner-ssid {
display: inline-block;
text-decoration: underline;
cursor: pointer;
}
#scanner-list li span.scanner-info {
float: right;
cursor: pointer;
}
div.navigation { margin-top: 2em; }
div.navigation button { margin-top: 0.25em; }
div.navigation .subnavigation {
margin-top: 1.5em;
text-align: center;
font-size: 0.85em;
}
a, a:visited { color: #333; }
a:hover { color: rgb(34, 85, 196); }
</style>
</head>
<body>
<div id="wrapper">
<div class="pure-form">
<ul id="scanner-list"></ul>
<button id="scanner-scan" class="pure-button pure-input-1">&#x21bb; Scan</button>
<form class="pure-form" method="post" action="/wifi/connect">
<fieldset class="pure-group">
<input type="text" id="scanner-form-ssid" name="n" class="pure-input-1" placeholder="SSID" />
<input type="password" name="p" class="pure-input-1" placeholder="password" />
</fieldset>
<button class="pure-button pure-input-1 pure-button-primary" type="submit">Connect</button>
</form>
</div>
<div class="pure-g navigation">
<button class="pure-button button-error pure-u-1" onclick="p('Reboot device?','/wifi/rst')">Reboot</button>
<button class="pure-button button-error pure-u-1" onclick="p('Reset configuration?', '/reset')">Reset Configuration</button>
<div class="subnavigation pure-u-1">
<a href="/">Home</a> | <a href="/config.htm">Garage Door Configuration</a> | <a href="/upload">Upload File</a> | <a href="/update">Update Software</a>
</div>
</div>
</div>
<script type="text/javascript">
// Variable to prevent users from clicking the scan button multiple times in a row
var bPending = false;
g("scanner-scan").onclick = function() {
// If we're already waiting for scan results, stop.
if (bPending) { return; }
var el = g("scanner-list");
el.className = "scanning";
el.innerHTML = "<li>Scanning...</li>";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
bPending = false;
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
el.innerHTML = "";
/**
* 100,1,4sak3n
* 46,1,TC8715D97
*/
xhr.responseText.split("\n").forEach(function (item) {
var aInfo = item.split(",");
var itemEl = E("li");
itemEl.innerHTML = '<span class="scanner-ssid">' + aInfo[2] + '</span>'
+ '<span class="scanner-info">' + aInfo[0] + '%' + ((aInfo[1] > 0) ? "\uD83D\uDD12" : "\u26A0") + '</span>';
itemEl.onclick = function(e) {
g("scanner-form-ssid").value = aInfo[2];
g("scanner-form-ssid").focus();
}
el.appendChild(itemEl);
});
}
}
xhr.open("GET","/wifi/list",true);
xhr.send();
bPending = true;
};
</script>
</body>
</html>
@justincjahn
Copy link
Author

This is not a github repo because I don't have plans to support it. Also, it's pretty easy to hack if someone gains access to your network since there's no password on the config or OTA update. Use at your own risk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment