Browse Source

updates that remove sonar stuff

master
Wesley Kerfoot 1 year 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. 371
      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
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

371
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 = "<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
function trig()
gpio.write(trig_pin, gpio.HIGH)
tmr.delay(11)
gpio.write(trig_pin, gpio.LOW)
return result .. "</tbody></table>"
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("<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
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
)

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