diff --git a/credentials.lua.example b/credentials.lua.example
deleted file mode 100644
index 9b92e9d..0000000
--- a/credentials.lua.example
+++ /dev/null
@@ -1,2 +0,0 @@
-SSID="something"
-PASSWORD="yourpassword"
diff --git a/firmware/firmware.bin b/firmware/firmware.bin
new file mode 100644
index 0000000..732656b
Binary files /dev/null and b/firmware/firmware.bin differ
diff --git a/firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin b/firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin
deleted file mode 100644
index 34eb8b9..0000000
Binary files a/firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin and /dev/null differ
diff --git a/flash.sh b/flash.sh
index cd851cd..0fc2385 100755
--- a/flash.sh
+++ b/flash.sh
@@ -1,11 +1,14 @@
nodemcu-tool reset
-esptool.py erase_flash
-esptool.py --port /dev/ttyUSB0 write_flash -fm qio 0x00000 firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin
+esptool.py --port /dev/ttyUSB0 erase_flash
+esptool.py --port /dev/ttyUSB0 write_flash -fm qio 0x00000 firmware/firmware.bin
-nodemcu-tool upload *.lua libs/*
+CONN_DELAY=1000
+
+nodemcu-tool --connection-delay $CONN_DELAY upload *.lua libs/*
+#nodemcu-tool upload *.lua libs/*
while [[ $? != 0 ]]; do
- nodemcu-tool upload *.lua libs/*
+ nodemcu-tool --connection-delay $CONN_DELAY upload *.lua libs/*
done
-
-echo 'dofile("init.lua")' | nodemcu-tool terminal
+#
+echo 'dofile("init.lua")' | nodemcu-tool --connection-delay $CONN_DELAY terminal
diff --git a/init.lua b/init.lua
index 0de7604..07e7025 100644
--- a/init.lua
+++ b/init.lua
@@ -1,293 +1,184 @@
-- Never change these unless the board changes
-echo_pin = 2
-trig_pin = 3
-light_pin = 5
-
-use_sonar = false
+trig_pin = 5
light_on = false
-if adc.force_init_mode(adc.INIT_ADC)
-then
- node.restart()
- return -- don't bother continuing, the restart is scheduled
+function reset_light()
+ gpio.write(trig_pin, gpio.LOW)
+ tmr.delay(100000)
+ gpio.write(trig_pin, gpio.HIGH)
+ tmr.delay(100000)
+ gpio.write(trig_pin, gpio.LOW)
end
-print("System voltage (mV):", adc.readvdd33(0))
-
-gpio.mode(light_pin, gpio.OUTPUT)
-gpio.mode(echo_pin, gpio.INT) -- interrupt mode
-gpio.mode(trig_pin, gpio.OUTPUT)
-gpio.write(light_pin, gpio.HIGH)
-
function toggle_light()
- if light_on then
- gpio.write(light_pin, gpio.HIGH)
- else
- gpio.write(light_pin, gpio.LOW)
- end
+ print("toggling")
+ reset_light()
light_on = not light_on
+ file.putcontents("./light_status", light_on and "on" or "off")
end
function turn_light_on()
- gpio.write(light_pin, gpio.LOW)
+ if not light_on then
+ toggle_light()
+ end
end
function turn_light_off()
- gpio.write(light_pin, gpio.HIGH)
-end
-
-function tablelen(T)
- local count = 0
- for _ in pairs(T) do count = count + 1 end
- return count
-end
-
-samples = {}
-distance = {start_v=0.0, end_v=0.0}
-
-sample_rate = 4 -- number of samples it requires before triggering, recommended <= 5, >=3
-max_stderr = 0.5 -- margin of error allowed before triggering the light, recommended <= 1
-distance_max = 30 -- threshold for how far an object must be to trigger it in cm
-distance_min = 10 -- minimum distance (below this amount don't trigger it), recommended >= 5, <= 20
-
--- Get the mean value of a table
-function mean(t)
- -- http://lua-users.org/wiki/SimpleStats
- local sum = 0
- local count = 0
-
- for k,v in pairs(t) do
- if type(v) == 'number' then
- sum = sum + v
- count = count + 1
- end
+ if light_on then
+ toggle_light()
end
- return (sum / count)
end
-function stdev(t)
- -- http://lua-users.org/wiki/SimpleStats
- local m
- local vm
- local sum = 0
- local count = 0
- local result
-
- m = mean(t)
-
- for k, v in pairs(t) do
- if type(v) == 'number' then
- vm = v - m
- sum = sum + (vm * vm)
- count = count + 1
- end
- end
+light_status = file.getcontents("./light_status")
+if light_status == "on" then
+ turn_light_on()
+end
+print("Booted up")
- result = math.sqrt(sum / (count-1))
+gpio.mode(trig_pin, gpio.OUTPUT)
+gpio.write(trig_pin, gpio.LOW)
- return result
+function hex_to_char(x)
+ return string.char(tonumber(x, 16))
end
-function stderr(t)
- -- based on the definition of standard error
- return stdev(t) / math.sqrt(tablelen(t))
+function get_time()
+ local t = tmr.time()
+ local hours = t/3600
+ local seconds_leftover = t % 3600
+ return tostring(hours) .. " hours, " .. tostring(minutes_leftover)
end
-function measure(level, ts, evcount)
- if level == 1 then
- distance["start_v"] = ts
- else
- -- check if there's a low voltage but no starting high voltage
- if distance["start_v"] > 0 then
- distance["end_v"] = ts
-
- if distance["start_v"] >= distance["end_v"] then
- distance = {start_v=0.0, end_v=0.0} -- bad reading
- return
- end
-
- -- See https://www.adafruit.com/product/165
- local temperature = ((adc.read(0)*100)-50)/1000
-
- -- See http://hyperphysics.phy-astr.gsu.edu/hbase/Sound/souspe.html
- local speed_of_sound = (331.4 + 0.6*temperature)/10000
-
- -- See https://randomnerdtutorials.com/esp8266-nodemcu-hc-sr04-ultrasonic-arduino/
- local d = ((distance["end_v"] - distance["start_v"]) * speed_of_sound) / 2.0
-
- -- Only care about values between 10 and 50
- if math.floor(d) > distance_min and d < distance_max then
- table.insert(samples, d)
- local s = stderr(samples)
- if s > max_stderr and tablelen(samples) == (sample_rate - 1) then -- start over if it would cause stderr to be too great
- samples = {}
- distance = {start_v=0.0, end_v=0.0}
- end
-
- end
- distance = {start_v=0, end_v=0}
- end
+function urldecode(url)
+ if url == nil then
+ return
end
+ url = url:gsub("+", " ")
+ url = url:gsub("%%(%x%x)", hex_to_char)
+ return url
+end
- local s_num = tablelen(samples)
-
- if s_num >= sample_rate then
- local distance_mean = mean(samples)
- local s = stderr(samples)
- if s < max_stderr and distance_mean < distance_max and distance_mean > distance_min then
- print("stder = " .. s)
- print("distance = " .. distance_mean)
- if not light_on then
- toggle_light()
- end
- end
- samples = {}
+function get_info(group)
+ local info = node.info(group)
+ local result = "
" .. tostring(group) .. " |
"
+ for key, value in pairs(info) do
+ result = result .. "" .. tostring(key) .. " | " .. tostring(value) .. " |
"
end
-end
-function trig()
- gpio.write(trig_pin, gpio.HIGH)
- tmr.delay(11)
- gpio.write(trig_pin, gpio.LOW)
+ return result .. "
"
end
--- load credentials, 'SSID' and 'PASSWORD' declared and initialize in there
-dofile("credentials.lua")
function startup()
-
+ sntp.sync(
+ nil,
+ function(sec, usec, server, info)
+ print('synced ntp ', sec, usec, server)
+ end,
+ function()
+ print('failed to sync ntp')
+ end,
+ 1 -- auto-repeat sync
+ )
if file.open("init.lua") == nil then
print("init.lua deleted or renamed")
else
- print("Running")
+ cron_error = nil
file.close("init.lua")
-
print("Starting up")
- sntp.sync(nil,
- function(sec, usec, server, info)
- print("sync'd")
-
- tm = rtctime.epoch2cal(rtctime.get())
-
- print(tm["hour"])
- print(tm["min"])
-
- if tm["hour"] >= 12 or tm["hour"] < 2 then
- if tm["hour"] == 12 and tm["min"] < 30 then
- return
- end
- turn_light_on()
- end
-
- if tm["hour"] >= 2 and tm["hour"] < 12 then
- turn_light_off()
- end
- cron.schedule("0 02 * * *", function(e) -- 9 pm EST is 2 UTC
- print("Turning light off")
- turn_light_off()
- end)
+ require("httpserver").createServer(8080, function(req, res)
+ --print("+R", req.method, req.url, node.heap())
- cron.schedule("30 12 * * *", function(e) -- 7:30 am EST is 12:30 UTC
- print("Turning light on")
- turn_light_on()
- end)
-
- if use_sonar then
- tmr.create():alarm(61, tmr.ALARM_AUTO, trig)
- gpio.trig(echo_pin, "both", measure)
- end
- end,
- nil, 1)
-
- require("httpserver").createServer(80, function(req, res)
- print("+R", req.method, req.url, node.heap())
-
- req.onheader = function(self, name, value) -- luacheck: ignore
- print("+H", name, value)
- end
- -- setup handler of body, if any
- req.ondata = function(self, chunk) -- luacheck: ignore
- print("+B", chunk and #chunk, node.heap())
+ req.ondata = function(self, chunk)
+ --print("+B", chunk and #chunk, node.heap())
print(req.url)
- if not chunk then
- -- reply
- res:send(nil, 200)
- res:send_header("Connection", "close")
+ if chunk ~= nil then
if req.url == "/toggle" then
toggle_light()
elseif req.url == "/on" then
turn_light_on()
elseif req.url == "/off" then
turn_light_off()
- elseif req.url == "/toggle_sonar" then
- use_sonar = not use_sonar
+ elseif req.url == "/add_job" then
+ post_data = urldecode(chunk)
+ if string.len(post_data) > 6 then
+ local a, b = string.find(post_data, "=")
+ local cron_expression = post_data:sub(a+1)
+
+ local ran, error_msg = pcall(cron.schedule, cron_expression, toggle_light)
+ print(error_msg)
+ else
+ ran = false
+ error_msg = "invalid"
+ end
+
+ if not ran then
+ print("post_data = " .. post_data)
+ cron_error = error_msg
+ end
+ elseif req.url == "/reset_light" then
+ reset_light()
+ elseif req.url == "/clear_jobs" then
+ cron.reset()
+ elseif req.url == "/reboot" then
+ cron.reset()
+ node.restart()
+ end
+ end
+ if not chunk then
+ local toggle_status = light_on and "On" or "Off"
+ local color = light_on and "green" or "black"
+ -- reply
+ if req.url == "/" then
+ res:send(nil, 200)
+ res:send_header("Content-Type", "text/html")
+ res:send_header("Connection", "close")
+ res:send("Uptime: ".. tostring(tmr.time()) .. " seconds " .. get_info("hw") .. get_info("build_config") .. get_info("sw_version") .. "
")
+ res:send("\r\n")
+ elseif req.url == "/toggle" or req.url == "/reset_light" then
+ res:send(nil, 303)
+ res:send_header("Location", "/")
+ res:send_header("Connection", "close")
+ res:send("switching light\r\n")
+ res:send("\r\n")
+ elseif req.url == "/add_job" then
+ if cron_error then
+ res:send(nil, 400)
+ res:send_header("Content-Type", "text/html")
+ res:send_header("Connection", "close")
+ res:send(cron_error .. "\r\n")
+ res:send("\r\n")
+ cron_error = nil
+ else
+ res:send(nil, 303)
+ res:send_header("Location", "/")
+ res:send_header("Connection", "close")
+ res:send("\r\n")
+ end
+ else
+ res:send(nil, 303)
+ res:send_header("Location", "/")
+ res:send_header("Connection", "close")
+ res:send("\r\n")
end
-
- res:send("The light is " .. (light_on and "on\n" or "off\n"))
res:finish()
end
end
- end)
+ end)
end
end
--- Define WiFi station event callbacks
-wifi_connect_event = function(T)
- print("Connection to AP("..T.SSID..") established!")
- print("Waiting for IP address...")
- if disconnect_ct ~= nil then disconnect_ct = nil end
-end
-
-wifi_got_ip_event = function(T)
- -- Note: Having an IP address does not mean there is internet access!
- -- Internet connectivity can be determined with net.dns.resolve().
- print("Wifi connection is ready! IP address is: "..T.IP)
- print("Startup will resume momentarily, you have 3 seconds to abort.")
- print("Waiting...")
- tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup)
- mdns.register("smartlight", {hardware='NodeMCU'})
-end
-
-wifi_disconnect_event = function(T)
- if T.reason == wifi.eventmon.reason.ASSOC_LEAVE then
- --the station has disassociated from a previously connected AP
- return
- end
- -- total_tries: how many times the station will attempt to connect to the AP. Should consider AP reboot duration.
- local total_tries = 75
- print("\nWiFi connection to AP("..T.SSID..") has failed!")
-
- --There are many possible disconnect reasons, the following iterates through
- --the list and returns the string corresponding to the disconnect reason.
- for key,val in pairs(wifi.eventmon.reason) do
- if val == T.reason then
- print("Disconnect reason: "..val.."("..key..")")
- break
+enduser_setup.start(
+ function()
+ if wifi.sta.getip() ~= nil then
+ print("Connected to WiFi as:" .. wifi.sta.getip())
+ tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup)
end
- end
-
- if disconnect_ct == nil then
- disconnect_ct = 1
- else
- disconnect_ct = disconnect_ct + 1
- end
- if disconnect_ct < total_tries then
- print("Retrying connection...(attempt "..(disconnect_ct+1).." of "..total_tries..")")
- else
- wifi.sta.disconnect()
- print("Aborting connection to AP!")
- disconnect_ct = nil
- end
-end
-
--- Register WiFi Station event callbacks
-wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, wifi_connect_event)
-wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, wifi_got_ip_event)
-wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, wifi_disconnect_event)
-
-print("Connecting to WiFi access point...")
-wifi.setmode(wifi.STATION)
-wifi.sta.config({ssid=SSID, pwd=PASSWORD})
--- wifi.sta.connect() not necessary because config() uses auto-connect=true by default
+ end,
+ function(err, str)
+ print("enduser_setup: Err #" .. err .. ": " .. str)
+ end,
+ print
+)
diff --git a/libs/fifosocktest.lua b/libs/fifosocktest.lua
deleted file mode 100644
index 4ac9f21..0000000
--- a/libs/fifosocktest.lua
+++ /dev/null
@@ -1,145 +0,0 @@
---
--- Set verbose to 0 for quiet output (either the first assertion failure or
--- "All tests OK"), to 1 to see the events ("SEND", "SENT", "CHECK") without
--- the actual bytes, or to 2 to see the events with the bytes.
---
-local verbose = 0
-
-local vprint = (verbose > 0) and print or function() end
-
---
--- Mock up enough of the nodemcu tmr structure, but pretend that nothing
--- happens between ticks. This won't exercise the optimistic corking logic,
--- but that's probably fine.
--- luacheck: push ignore
-tmr = {}
-tmr.ALARM_SINGLE = 0
-function tmr.create()
- local r = {}
- function r:alarm(_i, _t, cb) vprint("TMR") cb() end
- return r
-end
--- luacheck: pop
-
---
--- Mock up enough of the nodemcu net.socket type; have it log all the sends
--- into this "outs" array so that we can later check against it.
---
-local outs = {}
-local fakesock = {
- cb = nil,
- on = function(this, _, cb) this.cb = cb end,
- send = function(this, s) vprint("SEND", (verbose > 1) and s) table.insert(outs, s) end -- luacheck: no unused
-}
-local function sent() vprint("SENT") fakesock.cb() end
-
--- And wrap a fifosock around this fake socket
-local fsend = (require "fifosock").wrap(fakesock)
-
--- Verify that the next unconsumed output is as indicated
-local function fcheck(x)
- vprint ("CHECK", (verbose > 1) and x)
- assert (#outs > 0)
- assert (x == outs[1])
- table.remove(outs, 1)
-end
-
--- Enqueue an empty function to prevent coalescing within the fifosock
-local function nocoal() fsend(function() return nil end) end
-
--- Send and check, for when the string should be sent exactly as is
-local function fsendc(x) fsend(x) fcheck(x) end
-
--- Check that there are no more outputs
-local function fchecke() vprint("CHECKE") assert (#outs == 0) end
-
---
--- And now for the tests, which start easy and grow in complexity
---
-
-fsendc("abracadabra none")
-sent() ; fchecke()
-
-fsendc("abracadabra three")
-fsend("short")
-fsend("string")
-fsend("build")
-sent() ; fcheck("shortstringbuild")
-sent() ; fchecke()
-
--- Hit default FSMALLLIM while building up
-fsendc("abracadabra lots small")
-for i = 1, 32 do fsend("a") end -- luacheck: no unused
-nocoal()
-for i = 1, 4 do fsend("a") end -- luacheck: no unused
-sent() ; fcheck(string.rep("a", 32))
-sent() ; fcheck(string.rep("a", 4))
-sent() ; fchecke()
-
--- Hit string length while building up
-fsendc("abracadabra overlong")
-for i = 1, 10 do fsend(string.rep("a",32)) end -- luacheck: no unused
-sent() ; fcheck(string.rep("a", 320))
-sent() ; fchecke()
-
--- Hit neither before sending a big string
-fsendc("abracadabra mid long")
-for i = 1, 6 do fsend(string.rep("a",32)) end -- luacheck: no unused
-fsend(string.rep("b", 256))
-nocoal()
-for i = 1, 6 do fsend(string.rep("c",32)) end -- luacheck: no unused
-sent() ; fcheck(string.rep("a", 192) .. string.rep("b", 256))
-sent() ; fcheck(string.rep("c", 192))
-sent() ; fchecke()
-
--- send a huge string, verify that it coalesces
-fsendc(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 260))
-sent() ; fchecke()
-
--- send a huge string, verify that it coalesces save for the short bit at the end
-fsend(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256) .. string.rep("d",256))
-fsend("e")
-fcheck(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256))
-sent() ; fcheck(string.rep("d",256) .. "e")
-sent() ; fchecke()
-
--- send enough that our 4x lookahead still leaves something in the queue
-fsend(string.rep("a",512) .. string.rep("b", 512) .. string.rep("c", 512))
-fcheck(string.rep("a",512) .. string.rep("b", 512))
-sent() ; fcheck(string.rep("c",512))
-sent() ; fchecke()
-
--- test a lazy generator
-do
- local ix = 0
- local function gen() vprint("GEN", ix); ix = ix + 1; return ("a" .. ix), ix < 3 and gen end
- fsend(gen)
- fsend("b")
- fcheck("a1")
- sent() ; fcheck("a2")
- sent() ; fcheck("a3")
- sent() ; fcheck("b")
- sent() ; fchecke()
-end
--- test a completion-like callback that does send text
-do
- local ix = 0
- local function gen() vprint("GEN"); ix = 1; return "efgh", nil end
- fsend("abcd"); fsend(gen); fsend("ijkl")
- assert (ix == 0)
- fcheck("abcd"); assert (ix == 0)
- sent() ; fcheck("efgh"); assert (ix == 1); ix = 0
- sent() ; fcheck("ijkl"); assert (ix == 0)
- sent() ; fchecke()
-end
--- and one that doesn't
-do
- local ix = 0
- local function gen() vprint("GEN"); ix = 1; return nil, nil end
- fsend("abcd"); fsend(gen); fsend("ijkl")
- assert (ix == 0)
- fcheck("abcd"); assert (ix == 0)
- sent() ; fcheck("ijkl"); assert (ix == 1); ix = 0
- sent() ; fchecke() ; assert (ix == 0)
-end
-print("All tests OK")