diff --git a/flash.sh b/flash.sh index 8cd9d08..41b8821 100755 --- a/flash.sh +++ b/flash.sh @@ -12,9 +12,9 @@ function reset_flash() { esptool.py --port /dev/ttyUSB0 write_flash -fm qio 0x10000 firmware/0x10000.bin } -#reset_flash +reset_flash -CONN_DELAY=1000 +CONN_DELAY=5000 nodemcu-tool --connection-delay $CONN_DELAY remove lfs.img nodemcu-tool --connection-delay $CONN_DELAY upload sources/lfs.img diff --git a/sources/_init.lua b/sources/_init.lua index 7c30848..79a028d 100644 --- a/sources/_init.lua +++ b/sources/_init.lua @@ -1,197 +1,163 @@ -- Never change these unless the board changes -red = 7 -green = 5 +started = false +light_pin = 5 +motion_pin = 7 -pins = {toggle_red=red, toggle_green=green} -lights = {} -lights[green] = false -lights[red] = false +--turn_off_timeout = 1000*60*1 -- for turning off if it triggered via sensor +turn_off_timer = tmr.create() -function calc_duty_cycle(duty_cycle_factor) - if duty_cycle_factor <= 1 then - return 1023 - else - return 1023 / duty_cycle_factor - end -end +timeout_settings = {["turn_off_timeout"]=1000*60*30, ["turn_off_timeout_minutes"]=30} -function is_table(v) - local is_it_a_table, _ = pcall(function() return v["is_table"] end) - return is_it_a_table -end +gpio.mode(motion_pin, gpio.INT) -function show_pair(key, value) - -- TODO handle case of array and then call back to show_list - if is_table(value) then - return "("..tostring(key) .. " . " .. tostring(show_table(value)) .. ")" - else - return "("..tostring(key) .. " . " .. tostring(value) .. ")" +function set_turn_off_timeout(new_timeout) + if new_timeout > 0 and new_timeout < 1440 then + timeout_settings["turn_off_timeout_minutes"] = new_timeout + timeout_settings["turn_off_timeout"] = 1000*60*new_timeout end end -function show_table(t) - result = "" - for key, value in pairs(t) do - result = result .. show_pair(key, value) +function calc_duty_cycle(duty_cycle_factor) + if duty_cycle_factor < 1 then + return 0 + else + return 1023 / duty_cycle_factor end - return "(" .. result .. ")" end -function show_list(ts) - if is_table(12) or (not is_table({green=12})) then - return "" - end - result = "" - for _, t in ipairs(ts) do - if is_table(t) then - result = result .. show_table(t) - else - result = result .. tostring(t) - end - end - return "(" .. result .. ")" -end +global_duty_cycle = calc_duty_cycle(1) -function make_timer(params) - -- interval is in milliseconds - -- pin is the gpio number - -- params {pin=pin, frequency=frequency, duty_cycle=duty_cycle, step=step, interval=interval, delay=delay} - - local duty_cycle_t = params["duty_cycle"] - local direction = params["step"] - local delay = 0 - print("timer created!") - print(duty_cycle_t) - local timer = tmr.create() - timer:register(params["interval"], tmr.ALARM_AUTO, function() - if delay > 0 then - delay = delay - 1 - return - end +function debounce (func) + local last = 0 + local delay = 500000 - if (direction > 0 and ((duty_cycle_t + direction) >= 1023)) then - delay = params["delay"] - direction = -math.abs(direction) - end + return function (...) + local now = tmr.now() + local delta = now - last - if ((direction <= 0) and ((duty_cycle_t + direction) < 0)) then - delay = params["delay"] - direction = math.abs(direction) + if delta < 0 then + delta = delta + 2147483647 end - if delay > 0 then + if delta < delay then return end - duty_cycle_t = duty_cycle_t + direction - - -- if this is running then it's turned on - lights[params["pin"]] = true - pwm.setduty(params["pin"], duty_cycle_t) - end) - return timer + last = now + return func(...) + end end -- pwm.setup(pin, frequency, duty cycle) -- max duty cycle is 1023, min 1 -pwm.setup(green, 500, 1023) -pwm.setup(red, 500, 1023) -pwm.start(green) -pwm.start(red) - -timers = {red=false, green=false} - --- cron job which checks if auto mode is enabled --- if auto mode is enabled, it cycles through various timers --- runs the current timer for one period (cron job runs once every period of time) --- then stops and unregisters that timer, and moves on to the next, etc --- if auto mode is disabled, it does nothing --- UI allows you to configure auto mode period maybe --- UI could also allow to configure the specific settings but this might be too much work - -auto_mode_enabled = true - -function make_timer_params(pin, duty_cycle, interval, frequency, step, delay) - duty_cycle = duty_cycle or 1023 - interval = interval or 20 - step = step or 1 - frequency = frequency or 500 - delay = delay or 200 - return {pin=pin, frequency=frequency, duty_cycle=duty_cycle, step=step, interval=interval, delay=delay} +pwm.setup(light_pin, 500, 1023) +pwm.start(light_pin) + +current_mode = nil + +pins = {toggle_light_pin=light_pin} +lights = {} + +if gpio.read(light_pin) == 1 then + lights[light_pin] = true +else + lights[light_pin] = false end -function unregister_timers() - if timers[green] then - timers[green]:unregister() +function turn_light_on(pin, duty_cycle, reset_timeout) + lights[pin] = true + pwm.setduty(pin, duty_cycle) + if reset_timeout then + turn_off_timer:stop() + turn_off_timer:unregister() end - if timers[red] then - timers[red]:unregister() - end - duty_cycle = 1023 - pwm.setduty(red, duty_cycle) - pwm.setduty(green, duty_cycle) end -timer_states = { - {green=make_timer_params(green, 1022, 200, 1000, 1021, 5), red=make_timer_params(red, 1022, 200, 1000, -1021, 5)}, - {green=make_timer_params(green, 1023, 5), red=make_timer_params(red, 1, 5)}, - --{green=make_timer_params(green, 1022, 100, 1000, -1021, 5), red=make_timer_params(red, 1022, 100, 1000, 1021, 5)}, - --{green=make_timer_params(green, 1023, 5, 1000), red=make_timer_params(red, 1, 5, 1000)}, - --{green=make_timer_params(green, 1023, 5), red=make_timer_params(red, 1023, 5)}, - --{green=make_timer_params(green, 1, 5), red=make_timer_params(red, 1023, 5)}, - --{green=make_timer_params(green, 1), red=make_timer_params(red, 1023)}, - } - -function start_auto_mode() - -- this is enabled by default - local current_state = 1 - local number_of_states = table.getn(timer_states) - print("Making cronjob, number of states = " .. number_of_states) - cron.reset() - unregister_timers() - cron.schedule("*/1 * * * *", function(e) - if auto_mode_enabled then - print("Auto mode enabled, switching modes, current mode = " .. current_state) - - unregister_timers() - - -- TODO iter8 thru them instead - timers[green] = make_timer(timer_states[current_state]["green"]) - timers[red] = make_timer(timer_states[current_state]["red"]) - timers[green]:start() - timers[red]:start() - - current_state = (current_state % number_of_states) + 1 +function to_percent(a, b) + return (a*b)/100 +end - end - end) +function get_duty_cycle_percentage(pin) + local d = pwm.getduty(pin) + return (d*100)/1023 end -function turn_light_on(pin, duty_cycle) - if not lights[pin] and not auto_mode_enabled then +function set_light_brightness(pin, percentage) + --print("percentage = " .. tostring(percentage)) + local duty_cycle = to_percent(1023, percentage) + --print("set light to " .. tostring(duty_cycle)) + pwm.setduty(pin, duty_cycle) + global_duty_cycle = duty_cycle + if duty_cycle > 0 then lights[pin] = true - pwm.setduty(pin, duty_cycle) + else + lights[pin] = false end end function turn_light_off(pin) - if lights[pin] and not auto_mode_enabled then + if lights[pin] then pwm.setduty(pin, 0) lights[pin] = false end end function toggle_light(pin) - if auto_mode_enabled then - return - end - print("Toggling " .. tostring(pin)) - local duty_cycle = calc_duty_cycle(1) - print("duty_cycle = ".. duty_cycle) + --print("Toggling " .. tostring(pin)) if lights[pin] then turn_light_off(pin) else - turn_light_on(pin, duty_cycle) + turn_light_on(pin, global_duty_cycle, true) + end +end + +function sensor_trigger_on(level, ts, evcount) + if level == gpio.HIGH and gpio.read(light_pin) == 0 then + --print("sensor pin is high") + turn_light_on(light_pin, global_duty_cycle, true) + turn_off_timer:register(timeout_settings["turn_off_timeout"], tmr.ALARM_SINGLE, function() + turn_light_off(light_pin) + end) + turn_off_timer:start() + end +end + +gpio.trig(motion_pin, "up", sensor_trigger_on) + +function is_table(v) + local is_it_a_table, _ = pcall(function() return v["is_table"] end) + return is_it_a_table +end + +function show_pair(key, value) + -- TODO handle case of array and then call back to show_list + if is_table(value) then + return "("..tostring(key) .. " . " .. tostring(show_table(value)) .. ")" + else + return "("..tostring(key) .. " . " .. tostring(value) .. ")" + end +end + +function show_table(t) + result = "" + for key, value in pairs(t) do + result = result .. show_pair(key, value) + end + return "(" .. result .. ")" +end + +function show_list(ts) + if is_table(12) or (not is_table({light_pin=12})) then + return "" + end + result = "" + for _, t in ipairs(ts) do + if is_table(t) then + result = result .. show_table(t) + else + result = result .. tostring(t) + end end + return "(" .. result .. ")" end print("Booted up") @@ -226,7 +192,8 @@ end function get_info(group) local info = node.info(group) - local result = "" + local result = "
" .. tostring(group) .. "
" + result = result .. "" for key, value in pairs(info) do result = result .. "" end @@ -239,24 +206,38 @@ function compose(f, g) end function gen_select(name, id, options) - local result = "" for key, value in pairs(options) do - result = result .. "" + result = result .. "" end return result .. "" end -function gen_form(name, endpoint, fields, gen_inputs) - local result = "

" .. name .. "

" +function gen_range_select(name, id, options) + local min = options["min"] + local max = options["max"] + local value = options["value"] + return "" +end + +function gen_num_select(name, id, options) + local min = options["min"] + local max = options["max"] + local value = options["value"] + return "" +end + +function gen_form(form_name, name, endpoint, fields, gen_inputs) + local result = "

" .. form_name .. "

" result = result .. gen_inputs(name, endpoint, fields) - return result .. "
" + return result .. "
" end function gen_buttons(name, endpoint, fields) - local result = "

" .. name .. "

" + local result = "

" .. name .. "

" for key, value in pairs(fields) do - result = result .. "" - result = result .. "" .. "status here" .. "" + result = result .. "" + result = result .. "" .. "status here" .. "" end return result end @@ -266,7 +247,9 @@ function startup() nil, function(sec, usec, server, info) print('synced ntp ', sec, usec, server) - start_auto_mode() + if not started then + started = true + end end, function() print('failed to sync ntp') @@ -290,17 +273,20 @@ function startup() if params["toggle"] ~= nil then toggle_light(pins[params["toggle"]]) end - elseif req.url == "/toggle_mode" then - local params = extract_formdata(urldecode(chunk)) - if params["toggle_mode"] == "mode_manual" then - unregister_timers() - auto_mode_enabled = false - elseif params["toggle_mode"] == "mode_auto" then - start_auto_mode() - auto_mode_enabled = true - end elseif req.url == "/reboot" then node.restart() + elseif req.url == "/set_brightness" then + local params = extract_formdata(urldecode(chunk)) + if params["set_brightness"] ~= nil then + local percentage = tonumber(params["set_brightness"]) + set_light_brightness(light_pin, percentage) + end + elseif req.url == "/set_timeout" then + local params = extract_formdata(urldecode(chunk)) + if params["set_timeout"] ~= nil then + local new_timeout = tonumber(params["set_timeout"]) + set_turn_off_timeout(new_timeout) + end end end if not chunk then @@ -310,10 +296,13 @@ function startup() res:send_header("Content-Type", "text/html") res:send_header("Connection", "close") - local toggle_mode = gen_form("Toggle Mode", "toggle_mode", {["mode_manual"]="Manual", ["mode_auto"]="Auto"}, gen_select) - local toggle_lights_form = gen_form("Toggle Lights Form", "toggle", {["toggle_red"]="Red", ["toggle_green"]="Green"}, gen_select) + local current_dc_percent = tostring(get_duty_cycle_percentage(light_pin)) - res:send("
Uptime: ".. tostring(tmr.time()) .. " seconds" .. toggle_lights_form .. toggle_mode .. "
" .. get_info("hw") .. get_info("build_config") .. get_info("sw_version") .. show_list(timer_states) .. "
") + local toggle_lights_form = gen_form("Lights", "Toggle Lights", "toggle", {["toggle_light_pin"]="Light"}, gen_select) + local set_brightness_form = gen_form("Brightness", "Set Brightness (%)", "set_brightness", {["min"]="0", ["max"]="100", ["value"]=current_dc_percent}, gen_range_select) + local set_timeout_form = gen_form("Timeout", "Set Timeout (minutes)", "set_timeout", {["min"]="0", ["max"]="1440", ["value"]=timeout_settings["turn_off_timeout_minutes"]}, gen_num_select) + + res:send("

Light Config

" .. toggle_lights_form .. set_brightness_form .. set_timeout_form .. "
" .. get_info("hw") .. get_info("build_config") .. get_info("sw_version") .. "
") res:send("\r\n") elseif req.url == "/toggle" then res:send(nil, 303) @@ -321,6 +310,18 @@ function startup() res:send_header("Connection", "close") res:send("switching light\r\n") res:send("\r\n") + elseif req.url == "/set_brightness" then + res:send(nil, 303) + res:send_header("Location", "/") + res:send_header("Connection", "close") + res:send("setting brightness\r\n") + res:send("\r\n") + elseif req.url == "/set_timeout" then + res:send(nil, 303) + res:send_header("Location", "/") + res:send_header("Connection", "close") + res:send("setting timeout\r\n") + res:send("\r\n") else res:send(nil, 303) res:send_header("Location", "/") @@ -335,15 +336,23 @@ function startup() end function connect_wifi() + print("Turning lights off") + + if gpio.read(light_pin) == 1 then + lights[light_pin] = true + else + lights[light_pin] = false + end + + turn_light_off(light_pin) + + --wifi.sta.clearconfig() print("Trying to connect to wifi with captive portal") - wifi.sta.disconnect() - -- wifi.sta.clearconfig() - enduser_setup.start( + enduser_setup.start("Reading Light", function() - if wifi.sta.getip() ~= nil then - print("Connected to WiFi as:" .. wifi.sta.getip()) - tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup) - end + print("in connection function") + --print("Connected to WiFi as:" .. wifi.sta.getip()) + tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup) end, function(err, str) print("enduser_setup: Err #" .. err .. ": " .. str)
" .. tostring(group) .. "
Uptime: ".. tostring(tmr.time()) .. " seconds
" .. tostring(key) .. "" .. tostring(value) .. "