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

371
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
toggle_light()
end
end end
function turn_light_off() function turn_light_off()
gpio.write(light_pin, gpio.HIGH) if light_on then
end toggle_light()
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
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 end
local sum = 0 print("Booted up")
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
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 return
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
end end
url = url:gsub("+", " ")
url = url:gsub("%%(%x%x)", hex_to_char)
return url
end
local s_num = tablelen(samples) function get_info(group)
local info = node.info(group)
if s_num >= sample_rate then local result = "<table><thead><tr><th colspan='2'>" .. tostring(group) .. "</th></thead><tbody>"
local distance_mean = mean(samples) for key, value in pairs(info) do
local s = stderr(samples) result = result .. "<tr><td>" .. tostring(key) .. "</td><td>" .. tostring(value) .. "</td></tr>"
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() return result .. "</tbody></table>"
gpio.write(trig_pin, gpio.HIGH)
tmr.delay(11)
gpio.write(trig_pin, gpio.LOW)
end 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"])
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 require("httpserver").createServer(8080, function(req, res)
print("Turning light off") --print("+R", req.method, req.url, node.heap())
turn_light_off()
end)
cron.schedule("30 12 * * *", function(e) -- 7:30 am EST is 12:30 UTC req.ondata = function(self, chunk)
print("Turning light on") --print("+B", chunk and #chunk, node.heap())
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
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 end
res:send("The light is " .. (light_on and "on\n" or "off\n"))
res:finish() res:finish()
end end
end end
end)
end)
end end
end end
-- Define WiFi station event callbacks enduser_setup.start(
wifi_connect_event = function(T) function()
print("Connection to AP("..T.SSID..") established!") if wifi.sta.getip() ~= nil then
print("Waiting for IP address...") print("Connected to WiFi as:" .. wifi.sta.getip())
if disconnect_ct ~= nil then disconnect_ct = nil end tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup)
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
end end
end end,
function(err, str)
if disconnect_ct == nil then print("enduser_setup: Err #" .. err .. ": " .. str)
disconnect_ct = 1 end,
else print
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

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