Browse Source

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
Marcos 4 years ago
parent
commit
adde150009

+ 32 - 0
http/garage_door.html

@@ -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 - 0
http/garage_door.lua

@@ -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

http/garage_door_opener.css → http/garage_door_control.css


+ 2 - 2
http/garage_door_opener.html

@@ -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;

+ 0 - 28
http/garage_door_opener.lua

@@ -1,31 +0,0 @@
-
-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

+ 1 - 1
http/index.html

@@ -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>

+ 1 - 1
httpserver-conf.lua

@@ -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"}
 

+ 1 - 0
init.lua

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