diff --git a/README-old.md b/README-old.md new file mode 100644 index 0000000..321fa9b --- /dev/null +++ b/README-old.md @@ -0,0 +1,21 @@ +# GARAGE + +This project uses an [ESP2866](http://www.esp8266.com) with [nodeMCU](http://nodemcu.com/index_cn.html) firmware to control a garage door. + +Controlling a garage door from a microcontroller is easy. All you need to do is emulate a push button with a relay and wire it in parallel with the real push button on your garage door motor. + +# Hardware + +I bought a [kit from eBay](http://www.ebay.com/itm/281519483801?_trksid=p2059210.m2749.l2649) that came with most of what I needed: +* A cheap CH430 USB to Serial TTL adapter with 3.3V logic +* A bunch of female to female jumper wires and jumpers +* A somewhat useful carrying board that makes access to GPIO pins impossible as soldered. +* AM1117 5V to 3.3V power supply with 800 mA capacity. +* Female USB cable +* ESP-01 board, which gives access to 2 GPIO pins. + +Separately, I bought [2 relays](http://www.ebay.com/itm/2pcs-3V-Relay-High-Level-Driver-Module-optocouple-Relay-Moduele-for-Arduino-/141523155660?) that can handle way more voltage and current than I need, but are handy because I can drive them with 3.3V logic of the ESP2866. + +# Open issues + +* When the ESP2866 powers up, both GPIO pins are in input mode, which the relay reads as logic high. That's not good, as it would trigger a garage door opening! I need to invert the logic. What's the easiest and cheapest way to do this? diff --git a/b64.lua b/b64.lua new file mode 100755 index 0000000..179897e --- /dev/null +++ b/b64.lua @@ -0,0 +1,213 @@ +#!/usr/local/bin/lua +-- http://lua-users.org/wiki/BaseSixtyFour +-- working lua base64 codec (c) 2006-2008 by Alex Kloss +-- compatible with lua 5.1 +-- http://www.it-rfc.de +-- licensed under the terms of the LGPL2 + +-- bitshift functions (<<, >> equivalent) +-- shift left +function lsh(value,shift) + return (value*(2^shift)) % 256 +end + +-- shift right +function rsh(value,shift) + return math.floor(value/2^shift) % 256 +end + +-- return single bit (for OR) +function bit(x,b) + return (x % 2^b - x % 2^(b-1) > 0) +end + +-- logic OR for number values +function lor(x,y) + result = 0 + for p=1,8 do result = result + (((bit(x,p) or bit(y,p)) == true) and 2^(p-1) or 0) end + return result +end + +-- encryption table +local base64chars = { + [0]='A', + [1]='B', + [2]='C', + [3]='D', + [4]='E', + [5]='F', + [6]='G', + [7]='H', + [8]='I', + [9]='J', + [10]='K', + [11]='L', + [12]='M', + [13]='N', + [14]='O', + [15]='P', + [16]='Q', + [17]='R', + [18]='S', + [19]='T', + [20]='U', + [21]='V', + [22]='W', + [23]='X', + [24]='Y', + [25]='Z', + [26]='a', + [27]='b', + [28]='c', + [29]='d', + [30]='e', + [31]='f', + [32]='g', + [33]='h', + [34]='i', + [35]='j', + [36]='k', + [37]='l', + [38]='m', + [39]='n', + [40]='o', + [41]='p', + [42]='q', + [43]='r', + [44]='s', + [45]='t', + [46]='u', + [47]='v', + [48]='w', + [49]='x', + [50]='y', + [51]='z', + [52]='0', + [53]='1', + [54]='2', + [55]='3', + [56]='4', + [57]='5', + [58]='6', + [59]='7', + [60]='8', + [61]='9', + [62]='-', + [63]='_' +} + +-- function encode +-- encodes input string to base64. +function enc(data) + local bytes = {} + local result = "" + for spos=0,string.len(data)-1,3 do + for byte=1,3 do bytes[byte] = string.byte(string.sub(data,(spos+byte))) or 0 end + result = string.format('%s%s%s%s%s',result,base64chars[rsh(bytes[1],2)],base64chars[lor(lsh((bytes[1] % 4),4), rsh(bytes[2],4))] or "=",((#data-spos) > 1) and base64chars[lor(lsh(bytes[2] % 16,2), rsh(bytes[3],6))] or "=",((#data-spos) > 2) and base64chars[(bytes[3] % 64)] or "=") + end + return result +end + +-- decryption table +local base64bytes = { + ['A']=0, + ['B']=1, + ['C']=2, + ['D']=3, + ['E']=4, + ['F']=5, + ['G']=6, + ['H']=7, + ['I']=8, + ['J']=9, + ['K']=10, + ['L']=11, + ['M']=12, + ['N']=13, + ['O']=14, + ['P']=15, + ['Q']=16, + ['R']=17, + ['S']=18, + ['T']=19, + ['U']=20, + ['V']=21, + ['W']=22, + ['X']=23, + ['Y']=24, + ['Z']=25, + ['a']=26, + ['b']=27, + ['c']=28, + ['d']=29, + ['e']=30, + ['f']=31, + ['g']=32, + ['h']=33, + ['i']=34, + ['j']=35, + ['k']=36, + ['l']=37, + ['m']=38, + ['n']=39, + ['o']=40, + ['p']=41, + ['q']=42, + ['r']=43, + ['s']=44, + ['t']=45, + ['u']=46, + ['v']=47, + ['w']=48, + ['x']=49, + ['y']=50, + ['z']=51, + ['0']=52, + ['1']=53, + ['2']=54, + ['3']=55, + ['4']=56, + ['5']=57, + ['6']=58, + ['7']=59, + ['8']=60, + ['9']=61, + ['-']=62, + ['_']=63, + ['=']=nil +} + +-- function decode +-- decode base64 input to string +function dec(data) + local chars = {} + local result="" + for dpos=0,string.len(data)-1,4 do + for char=1,4 do chars[char] = base64bytes[(string.sub(data,(dpos+char),(dpos+char)) or "=")] end + result = string.format( + '%s%s%s%s', + result, + string.char(lor(lsh(chars[1],2), rsh(chars[2],4))), + (chars[3] ~= nil) and string.char(lor(lsh(chars[2],4), + rsh(chars[3],2))) or "", + (chars[4] ~= nil) and string.char(lor(lsh(chars[3],6) % 192, + (chars[4]))) or "" + ) + end + return result +end + +-- command line if not called as library +if (arg ~= nil) then + local func = 'enc' + for n,v in ipairs(arg) do + if (n > 0) then + if (v == "-h") then print "base64.lua [-e] [-d] text/data" break + elseif (v == "-e") then func = 'enc' + elseif (v == "-d") then func = 'dec' + else print(_G[func](v)) end + end + end +else + module('base64',package.seeall) +end diff --git a/b64.py b/b64.py new file mode 100644 index 0000000..b452c10 --- /dev/null +++ b/b64.py @@ -0,0 +1,10 @@ +import argparse +import base64 + +# Load this source file and strip the header. +initial_data = 'Marcos' + +encoded_data = base64.b64encode(initial_data) + +num_initial = len(initial_data) +print encoded_data diff --git a/escape.py b/escape.py new file mode 100644 index 0000000..2985b1a --- /dev/null +++ b/escape.py @@ -0,0 +1,23 @@ +# esta es una prueba para modificar luatool.py +# http://stackoverflow.com/a/18935765/316875 +from string import maketrans + +def escapeString(a_string): + translationTable = maketrans({"-": r"\-", "]": r"\]", "\\": r"\\", "^": r"\^", "$": r"\$", "*": r"\*", ".": r"\."}) + escaped = a_string.translate(translationTable) + return escaped + +print escapeString("Marcos") + + +#!/usr/bin/python +''' + +intab = "aeiou" +outtab = "12345" +trantab = maketrans(intab, outtab) + +str = "this is string example....wow!!!"; +print str.translate(trantab); + +''' diff --git a/garage.lua b/garage.lua new file mode 100644 index 0000000..b639e07 --- /dev/null +++ b/garage.lua @@ -0,0 +1,50 @@ +print('Welcome to GARAGE') +print(' Created by Marcos Kirsch') + +require "webServer" + +pinGarage = 4 -- GPIO2 +clientTimeoutInSeconds = 10 +port = 80 + +-- Prepare pins +function preparePin(pin) + -- Pins start out configured for input, and the relay has a pulldown resistor + -- in order to prevent from activating on reset. Makes ure to set pin to low + -- BEFORE setting to output, less the relay see it as a toggle. + gpio.write(pin, gpio.LOW) + gpio.mode(pin, gpio.OUTPUT) +end +preparePin(pinGarage) + +-- This functions emulates pushing the button for opening/closing the garage door. +function pushTheButton(pin) + gpio.write(pin, gpio.HIGH) + delayInMicroseconds = 500000 -- half a second should be enough + tmr.delay(delayInMicroseconds) + gpio.write(pin, gpio.LOW) +end + +-- Read the "garage remote" HTML that is served +--file.open("remote.html", "r") +--html = file.read() + +webServer.start(port, clientTimeoutInSeconds) + +-- +--server = net.createServer(net.TCP, clientTimeoutInSeconds) server:listen(port, function(connection) +-- --if server == nil +-- -- print("Server listening on port " .. port) +-- -- return +-- --end +-- connection:on("receive",function(connection,payload) +-- print(payload) -- for debugging only +-- --generates HTML web site +-- connection:send(httpHeader200 .. html) +-- +-- pushTheButton(pinGarage) +-- connection:on("sent",function(connection) connection:close() end) +-- end) +--end) + + diff --git a/httpserver.lua b/httpserver.lua new file mode 100644 index 0000000..eb592d4 --- /dev/null +++ b/httpserver.lua @@ -0,0 +1,79 @@ +-- httpserver +-- Author: Marcos Kirsch +-- This is a very simple HTTP server designed to work on nodemcu (http://nodemcu.com) +-- It can handle GET and POST. +require "printTable" + + +httpserver = {} + + +-- Starts web server in the specified port. +--function httpserver.start(port, clientTimeoutInSeconds, debug) +-- -- Server constants +-- server = net.createServer(net.TCP, clientTimeoutInSeconds) server:listen(port, private.handleRequest) +--end + + +httpserver.private = {} -- not part of the public API + +function httpserver.private.onReceive(connection, payload) + print(payload) -- for debugging + + -- parse payload and decide what to serve. + parsedRequest = private.parseRequest(payload) + httpserver.private.printTable(parsedRequest, 3) + + --generates HTML web site + httpHeader200 = "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nCache-Control: private, no-store\r\n\r\n" + html = "

Hola mundo

" + connection:send(httpHeader200 .. html) +end + +function httpserver.private.handleRequest(connection) + connection:on("receive", onReceive) + connection:on("sent",function(connection) connection:close() end) +end + +-- given an HTTP request, returns the method (i.e. GET) +function httpserver.private.getRequestMethod(request) + -- HTTP Request Methods. + -- HTTP servers are required to implement at least the GET and HEAD methods + -- http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods + httpMethods = {"GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH"} + method = nil + for i=1,#httpMethods do + found = string.find(request, httpMethods[i]) + if found == 1 then + break + end + end + return (httpMethods[found]) +end + +-- given an HTTP request, returns a table with all the information. +function httpserver.private.parseRequest(request) + parsedRequest = {} + -- First get the method + + parsedRequest["method"] = httpserver.private.getRequestMethod(request) + if parsedRequest["method"] == nil then + return nil + end + -- Now get each value out of the header, skip the first line + lineNumber = 0 + for line in request:gmatch("[^\r\n]+") do + if lineNumber ~=0 then + -- tag / value are of the style "Host: 10.0.7.15". Break them up. + found, valueIndex = string.find(line, ": ") + if found == nil then + break + end + tag = string.sub(line, 1, found - 1) + value = string.sub(line, found + 2, #line) + parsedRequest[tag] = value + end + lineNumber = lineNumber + 1 + end + return parsedRequest +end diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..e758ccc --- /dev/null +++ b/init.lua @@ -0,0 +1,23 @@ +-- Tell the chip to connect to the access point + +print('Welcome') +wifi.setmode(wifi.STATION) +print('set mode=STATION (mode='..wifi.getmode()..')') +print('MAC: ',wifi.sta.getmac()) +print('chip: ',node.chipid()) +print('heap: ',node.heap()) +wifi.sta.config("Internet","") + +-- Wait until WiFi connection is established + +tmr.alarm(0, 2000, 1, function() + if wifi.sta.getip() == nil then + print("Connecting to AP...") + else + print('IP: ',wifi.sta.getip()) + tmr.stop(0) + end +end) + + +--dofile("garage.lua") diff --git a/printTable.lua b/printTable.lua new file mode 100644 index 0000000..f8cee3d --- /dev/null +++ b/printTable.lua @@ -0,0 +1,30 @@ +-- Print anything - including nested tables +-- Based on but modified from: +-- http://lua-users.org/wiki/TableSerialization +function printTable (tt, indent, done) + done = done or {} + indent = indent or 0 + if tt == nil then + io.write("nil\n") + else + if type(tt) == "table" then + for key, value in pairs (tt) do + io.write(string.rep (" ", indent)) -- indent it + if type (value) == "table" and not done [value] then + done [value] = true + io.write(string.format("[%s] => table\n", tostring (key))); + io.write(string.rep (" ", indent+4)) -- indent it + io.write("(\n"); + table_print (value, indent + 7, done) + io.write(string.rep (" ", indent+4)) -- indent it + io.write(")\n"); + else + io.write(string.format("[%s] => %s\n", + tostring (key), tostring(value))) + end + end + else + io.write(tt .. "\n") + end + end +end diff --git a/remote.html b/remote.html new file mode 100644 index 0000000..8e3631e --- /dev/null +++ b/remote.html @@ -0,0 +1,10 @@ + +Garage + +

Garage

+
+ + +
+ + diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..1b48808 --- /dev/null +++ b/test.lua @@ -0,0 +1,25 @@ +-- figuring out how to parse http header +require "webServer" +--require "printTable" +--require "b64" + +sep = "\r\n" +requestForGet = + "GET /index.html HTTP/1.1" .. sep .. + "Host: 10.0.7.15" .. sep .. + "Accept-Encoding: gzip, deflate" .. sep .. + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" .. sep .. + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.3.18 (KHTML, like Gecko) Version/8.0.3 Safari/600.3.18" .. sep .. + "Accept-Language: en-us" .. sep .. + "Cache-Control: max-age=0" .. sep .. + "Connection: keep-alive" .. sep .. + "" +--print(enc(requestForGet)) +--print(dec(enc(requestForGet))) + +parsedRequest = webServer.private.parseRequest(requestForGet) + +--printTable(parsedRequest, 3) +--printTable(nodemcu-http-server, 3) +--parsedRequest = webServer.parseRequest(requestForGet) + diff --git a/testfile.txt b/testfile.txt new file mode 100644 index 0000000..0605c24 --- /dev/null +++ b/testfile.txt @@ -0,0 +1,59 @@ +testfile + +local foo = bar[i] + +_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP"); + +00000 + +00010 +0123456789 +00020 +01234567890123456789 +00030 +012345678901234567890123456789 +00040 +0123456789012345678901234567890123456789 +00050 +01234567890123456789012345678901234567890123456789 +00060 +012345678901234567890123456789012345678901234567890123456789 +00070 +0123456789012345678901234567890123456789012345678901234567890123456789 +00080 +01234567890123456789012345678901234567890123456789012345678901234567890123456789 +00090 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00110 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00120 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00130 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00140 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00150 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00160 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00170 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00180 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00190 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00200 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00210 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00220 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00230 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00240 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00250 +012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +00260 +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +