local wakeup = require("+wakeup") --- Builds and returns a Wake-on-LAN magic packet for [[mac]] address. local function magic_packet (mac) local data = "" for octet in mac:gmatch("%x%x") do data = data .. string.char(tonumber(octet, 16)) end if #data ~= 6 then error(string.format("Invalid MAC\t%q", mac), 2) end return string.char(0xff):rep(6) .. data:rep(16) end --- Creates a new counter that has :up() method that returns true until it gets called N [[times]]. Then it returns only --- false. local function new_counter (times) return { count = 0, times = times, up = function (self) self.count = self.count + 1 return self.count <= self.times end, } end --- Perform the most basic preparations right after device booted up. function wakeup:boot () gpio.write(4, gpio.HIGH) gpio.mode(4, gpio.OUTPUT) self.data = magic_packet(self.mac) self.step = self.connect end --- This is the current state action of the wake-up state machine. It is a callable that has [[(self, [timer]) -> nil]] --- signature. Steps are called sequentially, usually by a tmr.timer. wakeup.step = wakeup.boot --- Will tick for N [[ticks]] and then switch into a state [[after]]. local function start_timeout (ticks, after) local self = new_counter(ticks) return function (wakeup) if not self:up() then wakeup.step = after end end end --- Marks a failure that is pointless to attempt to recover from, e.g., if provided configuration is incomplete. function wakeup:hard_failure (timer) print("Aborting...") return timer:unregister() end --- Marks a failure that device may recover from by restarting itself. function wakeup:soft_failure () node.restart() end function wakeup:connect () self.step = start_timeout(10, self.soft_failure) wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function () self.step = self.sync_time end) wifi.setmode(wifi.STATION, false) wifi.sta.connect() end function wakeup:sync_time () self.step = start_timeout(10, self.soft_failure) sntp.sync( self.sntp, function () self.step = self.check_time end, function (code, err) print("SNTP sync failed", code, err) self.step = self.soft_failure end) end --- Creates new state machine action that sends out Wake-on-LAN magic packets to configured host N [[times]]. local function start_sendout (times) local self = new_counter(times) self.udp = net.createUDPSocket() return function (wakeup, timer) if self:up() then print("Waking up", wakeup.mac, wakeup.addr) self.udp:send(wakeup.port, wakeup.addr, wakeup.data) else print("Done") timer:interval(10000) self.udp:close() gpio.write(4, gpio.HIGH) wakeup.step = start_timeout(6, wakeup.check_time) end end end --- Current UTC wall clock time. local function now () local time = rtctime.epoch2cal(rtctime.get()) return {hour = time.hour, min = time.min} end --- Time in minutes to next occurrence of [[pattern]] starting from [[time]]. local function upcoming (pattern, time) local hours = 0 if pattern.hour then hours = pattern.hour - time.hour if hours < 0 then hours = hours + 24 end end local minutes = 0 if pattern.min then minutes = pattern.min - time.min if minutes < 0 then minutes = minutes + 60 if hours > 0 then hours = hours - 1 end end end return hours * 60 + minutes end function wakeup:check_time (timer) local left = upcoming(self.when, now()) if left == 0 then timer:interval(500) gpio.write(4, gpio.LOW) self.step = start_sendout(5) elseif left > 5 then timer:interval(1000) print(string.format("Need to wait total %d minutes", left)) left = left - 5 if left > 50 then left = 50 end self.left = left self.step = start_timeout(3, self.sleep) print(string.format("Going to sleep %d minutes", left)) else timer:interval(30000) print(string.format("Wake-up in %d minutes", left)) end end function wakeup:sleep (timer) wifi.setmode(wifi.NULLMODE, false) timer:unregister() rtctime.dsleep(self.left * 60e6, 1) end return wakeup