Overhaul garage door example (#93)

* script for controlling garage that is aware of door states

* Overhaul garage door example

Hardware now calls for reed relay in order to be able to tell the state
of the switch. This way, we can have distinct open/close operations
that error if the door is already in that state. Allows for potential
better integration with home automation systems.

* Prepare for push to GitHub

* Restore defaults

* Make HTTP auth realm match zeroconf networking name
This commit is contained in:
Marcos 2017-07-01 15:18:11 -05:00 committed by GitHub
parent a96ebc6196
commit adde150009
8 changed files with 190 additions and 36 deletions

32
http/garage_door.html Normal file
View File

@ -0,0 +1,32 @@
<html>
<head>
<title>Garage control</title>
</head>
<body>
<div align="center">
<h2>Door 1</h2>
<form action="garage_door.lua" method="post"/>
<input type="hidden" name="door" value="1"/>
<input type="submit" name="action" value="status"/>
<input type="submit" name="action" value="open"/>
<input type="submit" name="action" value="close"/>
<input type="submit" name="action" value="toggle"/>
</form>
<h2>Door 2</h2>
<form action="garage_door.lua" method="post"/>
<input type="hidden" name="door" value="2"/>
<input type="submit" name="action" value="status"/>
<input type="submit" name="action" value="open"/>
<input type="submit" name="action" value="close"/>
<input type="submit" name="action" value="toggle"/>
</form>
</div>
</body>
</html>

153
http/garage_door.lua Normal file
View File

@ -0,0 +1,153 @@
-- garage_door_open.lua
-- Part of nodemcu-httpserver, example.
-- Author: Marcos Kirsch
--[[
This example assumed you have a Wemos D1 Pro to control a two-door garage.
For each garage door, a Wemos relay shield is used to simulate a button (connect relay in
parallel with the actual physical button) and a reed switch is used in order to know
whether a door is currently open or closed (install switch so that it is in the closed
position when your garage door is closed).
You can configure which GPIO pins you use for each function by modifying variable
pinConfig below.
]]--
local function pushTheButton(connection, pinConfig)
-- push the button!
gpio.write(pinConfig["controlPin"], gpio.HIGH)
gpio.mode(pinConfig["controlPin"], gpio.OUTPUT, gpio.FLOAT)
tmr.delay(300000) -- in microseconds
gpio.mode(pinConfig["controlPin"], gpio.INPUT, gpio.FLOAT)
gpio.write(pinConfig["controlPin"], gpio.LOW)
end
local function readDoorStatus(pinConfig)
-- When the garage door is closed, the reed relay closes, grounding the pin and causing us to read low (0).
-- When the garage door is open, the reed relay is open, so due to pullup we read high (1).
gpio.write(pinConfig["statusPin"], gpio.HIGH)
gpio.mode(pinConfig["statusPin"], gpio.INPUT, gpio.PULLUP)
if gpio.read(pinConfig["statusPin"]) == 1 then return 'open' else return 'closed' end
end
local function sendResponse(connection, httpCode, errorCode, action, pinConfig, message)
-- Handle nil inputs
if action == nil then action = '' end
if pinConfig == nil then
pinConfig = {}
pinConfig["door"] = 0
pinConfig["controlPin"] = 0
pinConfig["statusPin"] = 0
end
if message == nil then message = '' end
connection:send("HTTP/1.0 "..httpCode.." OK\r\nContent-Type: application/json\r\nCache-Control: private, no-store\r\n\r\n")
connection:send('{"error":'..errorCode..', "door":'..pinConfig["door"]..', "controlPin":'..pinConfig["controlPin"]..', "statusPin":'..pinConfig["statusPin"]..', "action":"'..action..'", "message":"'..message..'"}')
end
local function sendStatus(connection, pinConfig)
connection:send("HTTP/1.0 200 OK\r\nContent-Type: application/json\r\nCache-Control: private, no-store\r\n\r\n")
connection:send('{"error":0, "door":'..pinConfig["door"]..', "controlPin":'..pinConfig["controlPin"]..', "statusPin":'..pinConfig["statusPin"]..', "action":"status"'..', "status":"'..readDoorStatus(pinConfig)..'"}')
end
local function openDoor(connection, pinConfig)
-- errors if door is already open.
local doorStatus = readDoorStatus(pinConfig)
if doorStatus == 'open' then
return false
else
pushTheButton(connection, pinConfig)
return true
end
end
local function closeDoor(connection, pinConfig)
-- errors if door is already closed.
local doorStatus = readDoorStatus(pinConfig)
if doorStatus == 'closed' then
return false
else
pushTheButton(connection, pinConfig)
return true
end
end
return function (connection, req, args)
-- The values for pinConfig depend on how your Wemo D1 mini Pro is wired.
-- Adjust as needed.
pinConfig = {}
pinConfig["1"] = {}
pinConfig["1"]["door"] = 1
pinConfig["1"]["controlPin"] = 2
pinConfig["1"]["statusPin"] = 5
pinConfig["2"] = {}
pinConfig["2"]["door"] = 2
pinConfig["2"]["controlPin"] = 1
pinConfig["2"]["statusPin"] = 6
-- Make this work with both GET and POST methods.
-- In the POST case, we need to extract the arguments.
print("method is " .. req.method)
if req.method == "POST" then
local rd = req.getRequestData()
for name, value in pairs(rd) do
args[name] = value
end
end
-- validate door input
if args.door == nil then
sendResponse(connection, 400, -1, args.action, pinConfig[args.door], "No door specified")
return
end
if pinConfig[args.door] == nil then
sendResponse(connection, 400, -2, args.action, pinConfig[args.door], "Bad door specified")
return
end
-- perform action
if args.action == "open" then
if(openDoor(connection, pinConfig[args.door])) then
sendResponse(connection, 200, 0, args.action, pinConfig[args.door], "Door opened")
else
sendResponse(connection, 400, -3, args.action, pinConfig[args.door], "Door is already open")
end
return
end
if args.action == "close" then
if(closeDoor(connection, pinConfig[args.door])) then
sendResponse(connection, 200, 0, args.action, pinConfig[args.door], "Door closed")
else
sendResponse(connection, 400, -4, args.action, pinConfig[args.door], "Door is already closed")
end
return
end
if args.action == "toggle" then
pushTheButton(connection, pinConfig[args.door])
sendResponse(connection, 200, 0, args.action, pinConfig[args.door], "Pushed the button")
return
end
if args.action == "status" then
sendStatus(connection, pinConfig[args.door])
return
end
-- everything else is error
sendResponse(connection, 400, -5, args.action, pinConfig[args.door], "Bad action")
end

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="garage_door_opener.css">
<link rel="stylesheet" type="text/css" href="garage_door_control.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<meta charset="UTF-8">
<title>Garage Remote</title>
@ -11,7 +11,7 @@
function pushTheButton(door)
{
var url = "/garage_door_opener.lua?door=" + door;
var url = "/garage_door.lua?action=toggle&door=" + door;
xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = processRequest;

View File

@ -1,31 +0,0 @@
-- garage_door_opener.lua
-- Part of nodemcu-httpserver, example.
-- Author: Marcos Kirsch
local function pushTheButton(connection, pin)
-- push the button!
-- The hardware in this case is a Wemos D1 Pro with two relay shields.
-- The first relay is controlled with D1.
-- The second one was modified to be controlled with D2.
gpio.write(pin, gpio.HIGH)
gpio.mode(pin, gpio.OUTPUT, gpio.FLOAT)
tmr.delay(300000) -- in microseconds
gpio.mode(pin, gpio.INPUT, gpio.FLOAT)
gpio.write(pin, gpio.LOW)
-- Send back JSON response.
connection:send("HTTP/1.0 200 OK\r\nContent-Type: application/json\r\nCache-Control: private, no-store\r\n\r\n")
connection:send('{"error":0, "message":"OK"}')
end
return function (connection, req, args)
print('Garage door button was pressed!', args.door)
if args.door == "1" then pushTheButton(connection, 1)
elseif args.door == "2" then pushTheButton(connection, 2)
else
connection:send("HTTP/1.0 400 OK\r\nContent-Type: application/json\r\nCache-Control: private, no-store\r\n\r\n")
connection:send('{"error":-1, "message":"Bad door"}')
end
end

View File

@ -27,7 +27,7 @@
<li><a href="zipped.html.gz">Zipped</a>: Same exact file as served above. Server is smart enough to treat the .gz extension correctly (static but gzipped)</li>
<li><a href="args.lua">Arguments</a>: Parses arguments passed in the URL and prints them. (Lua)</li>
<li><a href="post.lua">Post</a>: A form that uses POST method. Displays different content based on HTTP method. (Lua)</li>
<li><a href="garage_door_opener.html">Garage door opener</a>: Control GPIO lines via the server. (Lua)</li>
<li><a href="garage_door.html">Garage door opener</a>: Control GPIO lines via the server. Or try this <a href="garage_door_control.html">simpler and nicer UI</a>. (Lua)</li>
<li><a href="node_info.lua">NodeMCU info</a>: Shows some basic NodeMCU(Lua)</li>
<li><a href="file_list.lua">List all server files</a>: Displays a list of all the server files. (Lua)</li>
<li><a href="upload.html">Upload</a>: Update, remove, list files on the server. Beware security implications. By <a href="https://github.com/ATAMAH">ATAMAH</a>.</li>

View File

@ -9,7 +9,7 @@ local auth = {}
-- Set to true if you want to enable.
auth.enabled = false
-- Displayed in the login dialog users see before authenticating.
auth.realm = "nodemcu-httpserver"
auth.realm = "nodemcu"
-- Add users and passwords to this table. Do not leave this unchanged if you enable authentication!
auth.users = {user1 = "password1", user2 = "password2", user3 = "password3"}

View File

@ -4,7 +4,7 @@ local wifiConfig = {}
-- Possible modes: wifi.STATION : station: join a WiFi network
-- wifi.SOFTAP : access point: create a WiFi network
-- wifi.wifi.STATIONAP: both station and access point
-- wifi.STATIONAP : both station and access point
wifiConfig.mode = wifi.STATION
if (wifiConfig.mode == wifi.SOFTAP) or (wifiConfig.mode == wifi.STATIONAP) then