Lua Scripting

POOM can run Lua scripts straight off the SD card — no rebuild, no toolchain. Drop a .lua file, launch it from the menu, and it runs in its own task with a small, curated API for the screen, buttons, buzzer and LEDs. This is the fastest way to prototype an automation or a tiny app on the device.

What you need: a microSD card in POOM and a text editor. Scripts live under the SD mount point /sdcard. The runner is at THE MAKER → LUA.

The Runtime Model

  • Each run spins up a fresh Lua VM in a dedicated FreeRTOS task; the VM lives only for that run.
  • While a script runs, the menu stops redrawing so your script owns the OLED.
  • Stopping is cooperative: a stop request fires a Lua hook that aborts the script with the error "stopped". Design loops so they can be interrupted (poll a button and call App.exit()).

The Lua API

The VM registers four global tables. This is the whole surface — deliberately small.

App — utilities

CallDoes
App.log(message)Print to the UART console ([lua] ...)
App.sleep(ms)Delay for whole milliseconds
App.beep(freq_hz, duration_ms)Play a tone on the buzzer
App.exit()Stop the current script (raises "stopped")

Oled — 128×64 display (Arduboy-style)

Oled.clear(), Oled.display(), Oled.textSize(size), Oled.setCursor(x, y), Oled.print(value), Oled.fillRect(x,y,w,h,color), Oled.drawRect(...), Oled.drawHLine(x,y,w,color), Oled.width() → 128, Oled.height() → 64. Colors: WHITE=1, BLACK=0, INVERT=2. Nothing shows until you call Oled.display().

Buttons — input events

Buttons.poll(timeout_ms) returns button, event, ts_ms for the next queued press, or nothing on timeout (0 = non-blocking). Constants: Buttons.A, Buttons.B, Buttons.LEFT, Buttons.RIGHT, Buttons.UP, Buttons.DOWN, and event Buttons.SINGLE_CLICK.

Led — WS2812 on-board strip

Led.init(brightness), Led.deinit(), Led.count(), Led.setPixel(idx, r, g, b, w) (0-based), Led.fill(r,g,b,w), Led.clear(), Led.brightness(0..255), Led.show(). The first Led.* call takes the strip over from the built-in rainbow animation and restores it when the script exits.

Experiment 1 — Hello, Lua

Save this as /sdcard/main.lua, then run THE MAKER → LUA. A beeps and lights the LED; B exits.

Oled.clear()
Oled.textSize(1)
Oled.setCursor(0, 0)
Oled.print("Lua OK")
Oled.setCursor(0, 10)
Oled.print("B=STOP")
Oled.display()

while true do
  local btn, ev = Buttons.poll(50)
  if btn == Buttons.B and ev == Buttons.SINGLE_CLICK then
    App.exit()
  end
  if btn == Buttons.A and ev == Buttons.SINGLE_CLICK then
    App.beep(2000, 120)
    Led.init(32)
    Led.setPixel(0, 0, 80, 255)
    Led.show()
  end
end

Observe: the loop yields with Buttons.poll(50), which keeps the script responsive and interruptible. That 50 ms poll is your event loop.

Experiment 2 — Patterns to Reuse

  • Menu / counter: track a number in a Lua variable, draw it, and change it on UP/DOWN.
  • Animation: Led.fill() with values derived from a frame counter, Led.show(), App.sleep(30).
  • Metronome: App.beep() on an interval using App.sleep(); exit on B.
Always give the user an exit. Because stop is cooperative, a script with a tight loop that never polls a button can only be stopped by the runtime hook — include a Buttons.poll + App.exit() path.