Browse Source

updates that remove sonar stuff

master
Wesley Kerfoot 2 years ago
parent
commit
521c7ea101
  1. 2
      credentials.lua.example
  2. BIN
      firmware/firmware.bin
  3. BIN
      firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin
  4. 15
      flash.sh
  5. 355
      init.lua
  6. 145
      libs/fifosocktest.lua

2
credentials.lua.example

@ -1,2 +0,0 @@
SSID="something"
PASSWORD="yourpassword"

BIN
firmware/firmware.bin

Binary file not shown.

BIN
firmware/nodemcu-release-19-modules-2021-11-10-19-19-06-float.bin

Binary file not shown.

15
flash.sh

@ -1,11 +1,14 @@
nodemcu-tool reset nodemcu-tool reset
esptool.py erase_flash esptool.py --port /dev/ttyUSB0 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 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 while [[ $? != 0 ]]; do
nodemcu-tool upload *.lua libs/* nodemcu-tool --connection-delay $CONN_DELAY upload *.lua libs/*
done done
#
echo 'dofile("init.lua")' | nodemcu-tool terminal echo 'dofile("init.lua")' | nodemcu-tool --connection-delay $CONN_DELAY terminal

355
init.lua

@ -1,293 +1,184 @@
-- Never change these unless the board changes -- Never change these unless the board changes
echo_pin = 2 trig_pin = 5
trig_pin = 3
light_pin = 5
use_sonar = false
light_on = false light_on = false
if adc.force_init_mode(adc.INIT_ADC) function reset_light()
then gpio.write(trig_pin, gpio.LOW)
node.restart() tmr.delay(100000)
return -- don't bother continuing, the restart is scheduled gpio.write(trig_pin, gpio.HIGH)
tmr.delay(100000)
gpio.write(trig_pin, gpio.LOW)
end 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() function toggle_light()
if light_on then print("toggling")
gpio.write(light_pin, gpio.HIGH) reset_light()
else
gpio.write(light_pin, gpio.LOW)
end
light_on = not light_on light_on = not light_on
file.putcontents("./light_status", light_on and "on" or "off")
end end
function turn_light_on() function turn_light_on()
gpio.write(light_pin, gpio.LOW) if not light_on then
end toggle_light()
function turn_light_off()
gpio.write(light_pin, gpio.HIGH)
end end
function tablelen(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end end
samples = {} function turn_light_off()
distance = {start_v=0.0, end_v=0.0} if light_on then
toggle_light()
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
end end
return (sum / count)
end end
function stdev(t) light_status = file.getcontents("./light_status")
-- http://lua-users.org/wiki/SimpleStats if light_status == "on" then
local m turn_light_on()
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 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 end
function stderr(t) function get_time()
-- based on the definition of standard error local t = tmr.time()
return stdev(t) / math.sqrt(tablelen(t)) local hours = t/3600
local seconds_leftover = t % 3600
return tostring(hours) .. " hours, " .. tostring(minutes_leftover)
end end
function measure(level, ts, evcount) function urldecode(url)
if level == 1 then if url == nil 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 return
end end
url = url:gsub("+", " ")
-- See https://www.adafruit.com/product/165 url = url:gsub("%%(%x%x)", hex_to_char)
local temperature = ((adc.read(0)*100)-50)/1000 return url
-- 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
function get_info(group)
local info = node.info(group)
local result = "<table><thead><tr><th colspan='2'>" .. tostring(group) .. "</th></thead><tbody>"
for key, value in pairs(info) do
result = result .. "<tr><td>" .. tostring(key) .. "</td><td>" .. tostring(value) .. "</td></tr>"
end end
distance = {start_v=0, end_v=0}
end
end
local s_num = tablelen(samples)
if s_num >= sample_rate then return result .. "</tbody></table>"
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 = {}
end
end end
function trig()
gpio.write(trig_pin, gpio.HIGH)
tmr.delay(11)
gpio.write(trig_pin, gpio.LOW)
end
-- load credentials, 'SSID' and 'PASSWORD' declared and initialize in there
dofile("credentials.lua")
function startup() 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 if file.open("init.lua") == nil then
print("init.lua deleted or renamed") print("init.lua deleted or renamed")
else else
print("Running") cron_error = nil
file.close("init.lua") file.close("init.lua")
print("Starting up") print("Starting up")
sntp.sync(nil,
function(sec, usec, server, info)
print("sync'd")
tm = rtctime.epoch2cal(rtctime.get())
print(tm["hour"]) require("httpserver").createServer(8080, function(req, res)
print(tm["min"]) --print("+R", req.method, req.url, node.heap())
if tm["hour"] >= 12 or tm["hour"] < 2 then req.ondata = function(self, chunk)
if tm["hour"] == 12 and tm["min"] < 30 then --print("+B", chunk and #chunk, node.heap())
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)
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())
print(req.url) print(req.url)
if not chunk then if chunk ~= nil then
-- reply
res:send(nil, 200)
res:send_header("Connection", "close")
if req.url == "/toggle" then if req.url == "/toggle" then
toggle_light() toggle_light()
elseif req.url == "/on" then elseif req.url == "/on" then
turn_light_on() turn_light_on()
elseif req.url == "/off" then elseif req.url == "/off" then
turn_light_off() turn_light_off()
elseif req.url == "/toggle_sonar" then elseif req.url == "/add_job" then
use_sonar = not use_sonar 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 end
res:send("The light is " .. (light_on and "on\n" or "off\n")) if not ran then
res:finish() print("post_data = " .. post_data)
cron_error = error_msg
end 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
end)
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("<style>.button{text-decoration:underline;}.body{padding:0; margin:0;}.par{display:flex;flex-direction:row;}.a{margin: auto;width:50%;}.b{margin: auto;width:50%;}</style><html><body><div class='par'><div class='a'><span>Uptime: ".. tostring(tmr.time()) .. " seconds</span><form action='/add_job' method='post'><div class='button'><label for='name'>Enter a cron expression (UTC)</label><input type='text' name='cron' id='cron' required></div><div><input type='submit' value='Add cron expression'></div></form><br/><form action='/clear_jobs' method='post'><button style=\"color:black;\">Clear Jobs</button></form><br/><form action='/toggle' method='post'><button style=\"color:black;\">Toggle</button><span style=\"color:" .. color .. ";\"> ".. toggle_status .. "</span></form><br/><form action='/reset_light' method='post'><button style=\"color:black;\">Reset Light</button></form><br/><form action='/reboot' method='post'><button style=\"color:red;\">Reboot</button></form><br/></div><div class='b'>" .. get_info("hw") .. get_info("build_config") .. get_info("sw_version") .. "</div></div></body></html>")
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 end
else
-- Define WiFi station event callbacks res:send(nil, 303)
wifi_connect_event = function(T) res:send_header("Location", "/")
print("Connection to AP("..T.SSID..") established!") res:send_header("Connection", "close")
print("Waiting for IP address...") res:send("\r\n")
if disconnect_ct ~= nil then disconnect_ct = nil end
end end
res:finish()
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 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 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 end)
--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
end end
end end
if disconnect_ct == nil then enduser_setup.start(
disconnect_ct = 1 function()
else if wifi.sta.getip() ~= nil then
disconnect_ct = disconnect_ct + 1 print("Connected to WiFi as:" .. wifi.sta.getip())
end tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup)
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 end
end,
-- Register WiFi Station event callbacks function(err, str)
wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, wifi_connect_event) print("enduser_setup: Err #" .. err .. ": " .. str)
wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, wifi_got_ip_event) end,
wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, wifi_disconnect_event) print
)
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

145
libs/fifosocktest.lua

@ -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")
Loading…
Cancel
Save