Skip to content

Serial Communication

The Serial system provides RS-232 / USB-CDC serial port communication for talking to external hardware like Arduino, microcontrollers, sensors, and other serial devices. You can enumerate available ports, open connections with configurable baud rates and line settings, send binary data, and receive incoming bytes via callbacks.

Platform Support

Platform Support Notes
Windows Full Enumerates COM ports via Windows Setup API
Linux Full Enumerates /dev/ttyUSB*, /dev/ttyACM*, /dev/ttyS*, /dev/ttyAMA*
Android Stub All operations return failure. No ports enumerated.
Dolphin / 3DS Stub Same as Android

Scrip Example

Create a Scripts/SerialGameManager.lua

SerialGameManager = {}

SerialGameManager.instance = nil

function SerialGameManager:Create()
    self.connection = nil
end

function SerialGameManager:Start()

    self.connection = Serial.Connect("COM3", {
    baud        = 115200,
    dataBits    = 8,
    stopBits    = 1,
    parity      = "none",  
    flowControl = false,
})
if self.connection == 0 then error("open failed") end
    Log.Debug("SerialGameManager:Start() Connected to Serial Port : " .. tostring(self.connection))

    Serial.StartReceive(self.connection)

    Serial.RegisterMessageFunction(self.connection, "buttonPushed", function(message)
        self:RunFunction()
    end)

    Serial.RegisterMessageFunction(self.connection, "unknownCommand", function(message)
        Log.Debug("SerialGameManager: Received 'unknownCommand' from Serial Device: " .. tostring(message))
    end)


end

function SerialGameManager:RunFunction()
        Log.Debug("SerialGameManager: Received 'buttonPushed' from Serial Device")

end


function SerialGameManager:Tick(deltaTime)

    if Input.IsKeyJustDown(Key.Space) then
        if self.connection then
            Log.Debug("SerialGameManager:Tick() Sending 'blink(5)' Command to Serial Device")
        local written = Serial.Send(self.connection, "blink(5)\n")
        end
    end
end


function SerialGameManager:Destroy()

if self.connection then
Serial.StopReceive(self.connection)
Serial.Disconnect(self.connection)
self.connection = nil
end
end


Reference Example

-- Find an Arduino
local port = nil
for _, p in ipairs(Serial.EnumeratePorts()) do
    if p.description:find("Arduino") then port = p.name; break end
end
assert(port, "no Arduino found")

-- Connect at 115200 baud
local h = Serial.Connect(port, { baud = 115200 })

-- Listen for incoming data
Serial.SetMessageCallback(function(handle, data)
    Log.Debug("Received: " .. data)
end)
Serial.StartReceive(h)

-- Send a command
Serial.Send(h, "hello\n")

-- When done
Serial.StopReceive(h)
Serial.Disconnect(h)

Lua API Reference

Serial.EnumeratePorts()

Returns a list of available serial ports on the system.

local ports = Serial.EnumeratePorts()
for i, p in ipairs(ports) do
    Log.Debug(i .. ": " .. p.name .. " - " .. p.description)
end

Each entry has:

Field Type Description
name string System port name ("COM3", "/dev/ttyUSB0")
description string Human-readable label ("USB Serial Port (COM3)")

Serial.Connect(portName, [config])

Opens a serial port and returns a handle. Returns 0 on failure.

Parameter Type Description
portName string Port name from EnumeratePorts()
config table Optional configuration (defaults below)

Config fields:

Field Type Default Description
baud number 9600 Baud rate
dataBits number 8 Data bits (5-8)
stopBits number 1 Stop bits (1 or 2)
parity string "none" "none", "odd", "even", "mark", "space"
flowControl boolean false Enable RTS/CTS hardware flow control
local h = Serial.Connect("COM3", {
    baud        = 115200,
    dataBits    = 8,
    stopBits    = 1,
    parity      = "none",
    flowControl = false,
})
if h == 0 then
    Log.Error("Failed to open COM3")
end

Serial.Disconnect(handle)

Closes a serial port. Stops any active receive loop and releases the handle.

Serial.Disconnect(h)

Serial.IsConnected(handle)

Returns true if the given handle is currently connected.

if Serial.IsConnected(h) then
    Serial.Send(h, "PING\n")
end

Serial.Send(handle, data)

Sends data to an open serial port. Returns the number of bytes written, or -1 on error. Lua strings are binary-safe, so null bytes and raw binary data work.

-- Send text
Serial.Send(h, "PING\n")

-- Send raw bytes
Serial.Send(h, string.char(0xAA, 0x55, 0x01, 0x02))

Serial.SendLine(handle, data)

Sends data with a newline (\n) appended automatically. Returns the number of bytes written (including the newline), or -1 on error.

-- These are equivalent:
Serial.SendLine(h, "PING")
Serial.Send(h, "PING\n")

Serial.StartReceive(handle)

Starts a background read loop for the port. Incoming data is delivered to your message callback on the main thread each frame.

Serial.StartReceive(h)

Serial.StopReceive(handle)

Stops the background read loop. Data already buffered but not yet delivered will still be dispatched.

Serial.StopReceive(h)

Serial.IsReceiving(handle)

Returns true if the background receive loop is currently active for the given handle.

if not Serial.IsReceiving(h) then
    Serial.StartReceive(h)
end

Serial.RegisterMessageFunction(handle, pattern, callback)

Registers a callback that fires when incoming data exactly matches the given pattern string. Returns a matcher ID that can be used with UnregisterMessageFunction.

Parameter Type Description
handle integer Serial connection handle
pattern string Exact string to match against incoming messages
callback function Called with the matched message as its argument
Serial.StartReceive(h)

local id = Serial.RegisterMessageFunction(h, "buttonPushed", function(message)
    Log.Debug("Button was pushed!")
end)

-- Later, to stop matching:
Serial.UnregisterMessageFunction(h, id)

Serial.RegisterREGEXMessageFunction(handle, pattern, callback)

Like RegisterMessageFunction, but the pattern is a regular expression instead of an exact match. Returns a matcher ID.

Parameter Type Description
handle integer Serial connection handle
pattern string Regex pattern to match against incoming messages
callback function Called with the matched message as its argument
-- Match any temperature reading like "TEMP:23.5"
Serial.RegisterREGEXMessageFunction(h, "^TEMP:[0-9.]+$", function(message)
    local value = message:match("TEMP:(.+)")
    Log.Debug("Temperature: " .. value)
end)

Serial.UnregisterMessageFunction(handle, matcherId)

Removes a previously registered message matcher by its ID.

Parameter Type Description
handle integer Serial connection handle
matcherId integer ID returned by RegisterMessageFunction or RegisterREGEXMessageFunction
local id = Serial.RegisterMessageFunction(h, "ping", function(msg)
    Log.Debug("pong")
end)

-- Stop listening for "ping"
Serial.UnregisterMessageFunction(h, id)

Callbacks

-- Called when data arrives on any port with an active receive loop.
-- handle: which port the data came from
-- data: raw byte string (use #data for byte count)
Serial.SetMessageCallback(function(handle, data)
    Log.Debug(string.format("Port #%d: %d bytes", handle, #data))
end)

-- Called when a port is successfully connected
Serial.SetConnectCallback(function(handle, portName)
    Log.Debug("Connected to " .. portName)
end)

-- Called when a port disconnects (manual or device unplugged)
Serial.SetDisconnectCallback(function(handle)
    Log.Debug("Port #" .. handle .. " disconnected")
end)

Callbacks are global, not per-handle. Use the handle argument to identify which port the event belongs to.

Per-Script Callback

If a Script component defines OnSerialMessage, it will be called automatically on every active script when data arrives. No registration needed.

function MyScript:OnSerialMessage(handle, data)
    Log.Debug("Got " .. #data .. " bytes from port #" .. handle)
end

Node Graph Nodes

Action Nodes (Serial category)

Node Inputs Outputs
Serial Enumerate Ports flow flow, int count, string firstPort
Serial Connect flow, string portName, int baud flow, int handle, bool success
Serial Disconnect flow, int handle flow
Serial Send Message flow, int handle, string data flow, int bytesWritten
Serial Start Receive flow, int handle flow
Serial Stop Receive flow, int handle flow

Event Nodes (Event category)

Node Outputs
On Serial Message flow, int handle, string data
On Serial Connected flow, int handle, string portName
On Serial Disconnected flow, int handle

Node Graph Example

[Start Event] --> [Serial Connect "COM3" 115200] --> [Serial Start Receive (handle)]

[On Serial Connected]    --> [Debug Log: "connected to ${portName}"]
[On Serial Message]      --> [Debug Log: "got: ${data}"]
[On Serial Disconnected] --> [Debug Log: "disconnected"]

Things to Know

  • Callbacks are global. SetMessageCallback installs one function for all ports. Check the handle argument to route messages from different devices.
  • Binary-safe strings. Serial.Send(h, "\0\xff\x01") sends exactly those bytes. The callback data string preserves null bytes; use #data for byte count.
  • Auto-disconnect. If a device is physically unplugged, the disconnect callback fires automatically on the next frame.
  • Non-standard baud rates. On Windows, whether unusual baud rates work depends on the USB-serial driver (FTDI and CP210x support up to 3+ Mbaud; generic USB-CDC may cap at 115200). On Linux, only standard rates (1200, 2400, ..., 921600) are mapped; unknown rates fall back to 9600.

Examples

Arduino Echo Test

local port = nil
for _, p in ipairs(Serial.EnumeratePorts()) do
    if p.description:find("Arduino") then port = p.name; break end
end
assert(port, "no Arduino found")

local h = Serial.Connect(port, { baud = 115200 })
Serial.SetMessageCallback(function(_, data)
    Log.Debug("<< " .. data)
end)
Serial.StartReceive(h)
Serial.Send(h, "hello\n")

Multi-Device Routing

local sensors = {}
local display = nil

for _, p in ipairs(Serial.EnumeratePorts()) do
    if p.description:find("Sensor") then
        local h = Serial.Connect(p.name, { baud = 9600 })
        Serial.StartReceive(h)
        sensors[h] = p.name
    elseif p.description:find("Display") then
        display = Serial.Connect(p.name, { baud = 115200 })
    end
end

Serial.SetMessageCallback(function(handle, data)
    if sensors[handle] then
        Log.Debug("Sensor " .. sensors[handle] .. ": " .. data)
        if display then
            Serial.Send(display, data)  -- Forward to display
        end
    end
end)

Command-Response Protocol

local h = Serial.Connect("COM4", { baud = 57600 })

Serial.SetMessageCallback(function(_, data)
    local value = tonumber(data)
    if value then
        Log.Debug("Temperature: " .. value .. " C")
    end
end)
Serial.StartReceive(h)

-- Request a reading every tick (throttle as needed)
function MyScript:Tick()
    Serial.Send(h, "READ_TEMP\n")
end