]> hydra-www.ietfng.org Git - acmetensortoys-esp-lua_lamp/commitdiff
Add a much better Lampulator
authorNathaniel Wesley Filardo <nwfilardo@gmail.com>
Mon, 13 Aug 2018 23:30:48 +0000 (00:30 +0100)
committerNathaniel Wesley Filardo <nwfilardo@gmail.com>
Mon, 13 Aug 2018 23:32:10 +0000 (00:32 +0100)
This one (ab)uses the love2d.org lua game framework.  Much of the code
is copied (and lightly modified) from the older linux-draw-xpm codebase,
which should probably otherwise be tossed into an incinerator.

linux-draw-love/draw.lua [new file with mode: 0644]
linux-draw-love/main.lua [new file with mode: 0644]
linux-draw-love/net.lua [new file with mode: 0644]

diff --git a/linux-draw-love/draw.lua b/linux-draw-love/draw.lua
new file mode 100644 (file)
index 0000000..431a914
--- /dev/null
@@ -0,0 +1,185 @@
+-- LOVE interface
+limg = require "love.image"
+ltmr = require "love.timer"
+lthr = require "love.thread"
+
+imgd = limg.newImageData(8,4)
+netchan = lthr.getChannel ( "netc" )
+framechan = lthr.getChannel ( "framec" )
+
+-- emulate enough framebuffer functionality, backed by a Lua array <<<
+ws2812 = {}
+ws2812.SHIFT_LOGICAL = 0
+ws2812.SHIFT_CIRCULAR = 1
+
+remotefb = {}
+remotefb.length = 32
+function remotefb.set(self,ix,a1,a2,a3)
+  if ix > self.length then return end
+  if type(a1) == 'string' and a2 == nil and a3 == nil then
+    self[ix] = a1
+  else
+    self[ix] = string.char(math.floor(a1),math.floor(a2),math.floor(a3))
+  end
+end
+function remotefb.size(self) return self.length end
+function remotefb.fade(self,factor,out)
+  local ix, f
+  if not out
+   then f = function(c) return string.char(string.byte(c,1) / factor) end
+   else f = function(c) return string.char(string.byte(c,1) * factor) end
+  end
+  for ix = 1,self.length do self[ix] = self[ix]:gsub(".", f) end
+end
+function remotefb.fill(self,...)
+  local i
+  for i = 1,self.length do self:set(i,...) end
+end
+function remotefb.shift(self,n,m,i,j)
+  if      j == nil then j = self.length
+   elseif j < 0    then j = self.length - j
+                        if j <= 0 then return end
+   elseif j == 0 then return
+   elseif j > self.length then return
+  end
+  if      i == nil then i = 1
+   elseif i < 0    then i = self.length - i
+                        if i <= 0 then return end
+   elseif i == 0 then return
+   elseif i > self.length then return
+  end
+
+  if m == nil then m = remotefb.SHIFT_LOGICAL end
+
+  if n == 0 then return
+  elseif n > 0 then
+    local ix
+    for ix = 1, n do
+      local v = table.remove(self,j)
+      if m == ws2812.SHIFT_LOGICAL then v = string.char(0,0,0) end
+      table.insert(self,i,v)
+    end
+  elseif n < 0 then
+    local ix
+    for ix = 1, -n do
+      local v = table.remove(self,i)
+      if m == ws2812.SHIFT_LOGICAL then v = string.char(0,0,0) end
+      table.insert(self,j,v)
+    end
+  end
+end
+remotefb:fill(0,0,0)
+-- >>>
+-- drawfailsafe and dodraw <<<
+local function drawfailsafe(t,fb,p) fb:fill(0,0,0) end
+function loaddrawfn(name)
+  local f = loadfile (string.format("draw-%s.lua",name))
+  local fn = f and f()
+  if fn
+   then return function(t,fb,p) return fn(t,fb,p) end
+   else return drawfailsafe
+  end
+end
+
+-- dump the ws2812 array into a LOVE image and push it into a channel
+function dodraw()
+  local ix = 0, r, c
+  for r = 0,3 do
+    for c = 0,7 do
+      ix = ix + 1 
+      imgd:setPixel(c, r,
+        math.min(0xFF,string.byte(remotefb[ix],2)*16)/256,
+        math.min(0xFF,string.byte(remotefb[ix],1)*16)/256,
+        math.min(0xFF,string.byte(remotefb[ix],3)*16)/256
+      )
+    end
+  end
+
+  framechan:push(imgd)
+end
+--- >>>
+-- timer queue and tmr emulation <<<
+
+local snooze = 1
+tq = require("core/tq/tq")(nil)
+tq.now = function() return ltmr.getTime() * 1000000 end
+tq.arm = function(self,t,et)
+  snooze = (t + 20) / 1000 -- round up a bit so we don't so often undershoot
+end
+local tqf = tq.fire
+tq.fire = function(self) snooze = nil ; tqf(self) end
+
+-- how backwards is this!?  We are using tq as faked above for tmr support
+-- since it's not obvious to me how to remove pending events in cqueues.
+tmr = {}
+tmr.ALARM_SINGLE = 0
+tmr.ALARM_SEMI   = 1
+tmr.ALARM_AUTO   = 2
+function tmr._start(self)
+  if self.fn then self.tqe = tq:queue(self.period,self.fn) end
+end
+function tmr.stop(self)
+  if self.tqe then tq:dequeue(self.tqe); self.tqe = nil end
+end
+function tmr.unregister(self)
+  self:stop()
+  self.fn = nil
+end
+function tmr.register(self,period,mode,fn)
+  tmr.stop(self)
+
+  self.period = period
+  if mode == tmr.ALARM_AUTO then
+    self.fn = function() tmr._start(self); fn() end
+  else
+    self.fn = fn
+  end
+end
+function tmr.start(self)
+  tmr.stop(self)
+  tmr._start(self)
+end
+function tmr.alarm(self,period,mode,fn)
+  tmr.stop(self)
+  tmr.register(self,period,mode,fn)
+  tmr.start(self)
+end
+function tmr.interval(self, period)
+  self.period = period
+  if self.tqe == nil then return end -- just update interval
+  self:start() -- otherwise, re-schedule
+end
+tmr_mt = { __index = tmr }
+function tmr.create()
+  return setmetatable({}, tmr_mt)
+end
+
+-- >>>
+-- remote timer management <<<
+
+remotetmr = tmr.create()
+remoteqtmrs = {}
+
+function removeremote()
+  local k,v
+
+  -- drop all pending script timers
+  for k,v in pairs(remoteqtmrs) do v:unregister() end
+  remoteqtmrs = {}
+
+  -- and the current remote animation's timer
+  remotetmr:unregister()
+end
+
+-- >>>
+
+while true do
+  local line = netchan:demand(snooze)
+
+  if line then
+    local from, cmd = line:match("^(%S+)%s+(.*)$")
+    if cmd then dofile("lamp-remote.lua")(cmd) end
+  end
+
+  tq:fire()
+end
diff --git a/linux-draw-love/main.lua b/linux-draw-love/main.lua
new file mode 100644 (file)
index 0000000..38119d2
--- /dev/null
@@ -0,0 +1,53 @@
+function love.threaderror(thread, errorstr)
+  print("Thread error!", thread, errorstr)
+  love.event.push('quit')
+end
+
+function love.load()
+  love.window.setTitle("lampulator")
+  love.window.setMode(32, 16, { display = 1, x = 0, y = 0 })
+
+  framechan = love.thread.getChannel ( "framec" );
+  netchan = love.thread.getChannel ( "netc" );
+
+  netthread = love.thread.newThread ( "net.lua" );
+  netthread:start()
+
+  drawthread = love.thread.newThread ( "draw.lua" );
+  drawthread:start()
+end
+
+-- Override the event loop
+function love.run()
+  if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
+  if love.timer then love.timer.step() end
+
+  return function()
+    local dt = 0
+
+    if love.event then
+      local name, a,b,c,d,e,f
+      love.event.pump()
+      for name, a,b,c,d,e,f in love.event.poll() do
+        if name == "quit" then
+          if not love.quit or not love.quit() then
+            return a or 0
+          end
+        end
+        love.handlers[name](a,b,c,d,e,f)
+      end
+    end
+
+    -- XXX We have to poll the threads periodically to catch errors
+    local imgd = framechan:demand(1)
+    if imgd then
+      local img = love.graphics.newImage(imgd)
+      love.graphics.clear(love.graphics.getBackgroundColor())
+      love.graphics.draw(img, 0, 0, 0, 4, 4)
+    end
+    love.graphics.present()
+
+    if love.timer then dt = love.timer.step() end
+
+  end
+end
diff --git a/linux-draw-love/net.lua b/linux-draw-love/net.lua
new file mode 100644 (file)
index 0000000..cbe674d
--- /dev/null
@@ -0,0 +1,10 @@
+-- Pull lines off stdin and push them into a LOVE channel
+lev  = require "love.event"
+ltmr = require "love.timer"
+lthr = require "love.thread"
+
+netchan = lthr.getChannel ( "netc" )
+for line in io.lines() do
+  netchan:push(line)
+end
+lev.push("quit")