Introduction

ezOS provides a comprehensive Lua API for building applications on the T-Deck Plus hardware. This reference documents all available functions, organized by module.

Memory Model

  • Lua allocations use PSRAM — The Lua VM allocates all memory from the 8MB PSRAM, not the limited 320KB internal SRAM
  • Strings are immutable — Each string operation creates a new string; avoid concatenation in loops
  • Tables use memory — Empty tables take ~40 bytes; consider reusing tables in performance-critical code
  • Garbage collection — Runs automatically, but you can trigger it with ez.system.gc()

Coroutines and Yielding

Many ezOS APIs are designed for coroutine-based async programming:

local function load_async()
    local data = load_module("/scripts/heavy_module.lua")  -- Yields while loading
    process(data)
end
spawn(load_async)

Functions that yield are noted in their documentation. Don't call yielding functions outside of a coroutine context.

Error Handling

Most functions return nil on error rather than throwing. Check return values:

local file = ez.storage.read_file("/path")
if not file then
    print("Failed to read file")
    return
end

Color Format

Display functions use RGB565 format (16-bit color). Use ez.display.rgb(r, g, b) to convert from 8-bit RGB, or use predefined colors from ez.display.colors:

local red = ez.display.colors.RED
local custom = ez.display.rgb(128, 64, 255)

Coordinate Systems

  • Pixel coordinates start at (0, 0) in the top-left corner
  • Character cells are used by text-mode functions like draw_box()
  • Display size: 320×240 pixels, typically 40×15 character cells with medium font

Best Practices

  1. Prefer local variables — Globals are slower and use more memory
  2. Reuse tables — Clear and reuse instead of creating new ones
  3. Batch drawing — Draw everything, then call flush() once per frame
  4. Unload unused modules — Use unload_module() to free memory
  5. Check memory — Use ez.system.get_lua_memory() to monitor usage

328 functions across 16 modules. Click a module to view its documentation.

Message Bus

The message bus provides publish/subscribe communication between components. Use ez.bus.subscribe() to listen for events and ez.bus.post() to publish them.

Subscribing to Messages

local sub_id = ez.bus.subscribe("channel/message", function(data)
    print("New message:", data.text)
end)

-- Later, to unsubscribe:
ez.bus.unsubscribe(sub_id)

Publishing Messages

ez.bus.post("settings/changed", "brightness=200")
ez.bus.post("custom/event", { value = 42, name = "test" })

Available Topics

channel/message → table {channel, sender, sender_name, text, timestamp, is_self} Posted when a new channel message is received
Fired when a message arrives on any subscribed channel. Contains the full message details for display or processing.
Payload: table {channel, sender, sender_name, text, timestamp, is_self}
ez.bus.subscribe("channel/message", function(msg)
print(string.format("[%s] %s: %s",
msg.channel, msg.sender_name, msg.text))
end)
channel/unread → string Format: "channel_name:count" Posted when a channel's unread count changes
Fired when new messages arrive or are marked as read. UI components can update badges or indicators.
Payload: string Format: "channel_name:count"
ez.bus.subscribe("channel/unread", function(data)
local channel, count = data:match("(.+):(%d+)")
self:update_badge(channel, tonumber(count))
end)
mesh/node_count → string Number of nodes as string Posted when the known node count changes
Fired when nodes are discovered or expire from the mesh network. The status bar subscribes to this to update the node count display.
Payload: string Number of nodes as string
ez.bus.subscribe("mesh/node_count", function(count)
self.node_count = tonumber(count) or 0
end)
message/acked → string Message ID that was acknowledged Posted when a sent message is acknowledged
Fired when the recipient confirms receipt of a direct message. Used to update message status indicators (checkmarks).
Payload: string Message ID that was acknowledged
ez.bus.subscribe("message/acked", function(msg_id)
self:mark_delivered(msg_id)
end)
message/received → table {from, from_name, text, timestamp, conversation_id} Posted when a direct message is received
Fired when a private message arrives from another node. The DM screen and notification system subscribe to this.
Payload: table {from, from_name, text, timestamp, conversation_id}
ez.bus.subscribe("message/received", function(msg)
Toast.show("DM from " .. msg.from_name)
end)
screen/popped → string Screen title that was removed Posted when a screen is removed from the navigation stack
Fired after ScreenManager.pop() completes. The previous screen is now active.
Payload: string Screen title that was removed
ez.bus.subscribe("screen/popped", function(title)
print("Left screen: " .. title)
end)
screen/pushed → string Screen title Posted when a screen is pushed onto the navigation stack
Fired after ScreenManager.push() completes. Useful for analytics, logging navigation paths, or updating UI elements that depend on the current screen.
Payload: string Screen title
ez.bus.subscribe("screen/pushed", function(title)
print("Navigated to: " .. title)
end)
screen/replaced → string Format: "old_title>new_title" Posted when the current screen is replaced with another
Fired when ScreenManager.replace() swaps the current screen without pushing to the stack. Useful for tracking screen transitions.
Payload: string Format: "old_title>new_title"
ez.bus.subscribe("screen/replaced", function(data)
local old, new = data:match("(.+)>(.+)")
print("Replaced " .. old .. " with " .. new)
end)
settings/changed → string Format: "setting_name=value" Posted when any setting value is modified
Fired when user changes a setting in the Settings screen. Can be used to react to configuration changes in real-time.
Payload: string Format: "setting_name=value"
ez.bus.subscribe("settings/changed", function(data)
local name, value = data:match("(.+)=(.+)")
if name == "brightness" then
print("Brightness changed to: " .. value)
end
end)
theme/colors → string Color scheme name Posted when the color scheme is changed
Fired when ThemeManager.set_colors() is called. UI components should refresh their color values.
Payload: string Color scheme name
ez.bus.subscribe("theme/colors", function(scheme)
self.bg_color = ThemeManager.colors.background
end)
theme/icons → string Icon pack name Posted when the icon pack is changed
Fired when ThemeManager.set_icon_pack() is called. Components displaying icons should refresh their cached icon references.
Payload: string Icon pack name
ez.bus.subscribe("theme/icons", function(pack)
self:reload_icons()
end)
theme/wallpaper → string Wallpaper name (e.g., "clouds", "mountains", "none") Posted when the wallpaper is changed
Fired when ThemeManager.set_wallpaper() is called. Screens can subscribe to update their rendering if they display the wallpaper.
Payload: string Wallpaper name (e.g., "clouds", "mountains", "none")
ez.bus.subscribe("theme/wallpaper", function(name)
print("Wallpaper changed to: " .. name)
end)