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 --- 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 --- 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 = self.wait_for_ip 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:wait_for_ip () -- no-op -- TODO: soft_failure if too long? end function wakeup:now () local time = rtctime.epoch2cal(rtctime.get()) time.year = nil time.sec = nil return time end function wakeup:sync_time () self.step = self.wait_for_time 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 function wakeup:wait_for_time () -- no-op -- TODO: soft_failure if too long? end local function compare (time, pattern) for key, value in pairs(pattern) do if time[key] ~= value then return false end end return true 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 --- 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 self.udp:close() timer:unregister() wifi.setmode(wifi.NULLMODE, false) gpio.write(4, gpio.HIGH) print("Done") end end end function wakeup:check_time (timer) if compare(self:now(), self.when) then gpio.write(4, gpio.LOW) self.step = start_sendout(5) else print("Not yet time") timer:unregister() end end return wakeup