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