Lua Transforms

Lua transforms let you write custom logic to process serial data. SerialFlow uses Lua 5.4 via the Luerl interpreter.

Basic Transform

Every Lua transform needs a transform function that receives data and returns the result:

function transform(data)
  -- Your processing logic here
  return data
end

Return nil to drop the message (it won’t continue down the pipeline).

String Manipulation

Lua has powerful string handling built in:

function transform(data)
  -- Convert to uppercase
  local upper = string.upper(data)

  -- Remove leading/trailing whitespace
  local trimmed = data:match("^%s*(.-)%s*$")

  -- Extract a pattern
  local value = data:match("TEMP:(%d+%.?%d*)")

  return trimmed
end

Parsing Sensor Data

A common use case is parsing structured sensor output:

-- Parse "SENSOR:temp=23.5,humidity=45,pressure=1013"
function transform(data)
  local readings = {}

  -- Extract sensor name
  local sensor = data:match("^(%w+):")
  if not sensor then return nil end

  -- Parse key=value pairs
  for key, value in data:gmatch("(%w+)=([%d%.]+)") do
    readings[key] = tonumber(value)
  end

  -- Format as JSON
  return string.format(
    '{"sensor":"%s","temp":%.1f,"humidity":%d}',
    sensor,
    readings.temp or 0,
    readings.humidity or 0
  )
end

Working with State

You can maintain state between messages using global variables:

-- Count messages and add sequence number
local counter = 0

function transform(data)
  counter = counter + 1
  return string.format("[%d] %s", counter, data)
end

Note: State is reset when you edit the script or reload the page.

Built-in Functions

SerialFlow provides additional functions in the sf namespace:

function transform(data)
  -- Get current timestamp
  local ts = sf.timestamp()

  -- Log to console (for debugging)
  sf.log("Processing: " .. data)

  -- Access metadata
  local source = sf.metadata("source")

  return data
end

Error Handling

If your script throws an error, the message is dropped and an error is logged:

function transform(data)
  -- This will fail if data isn't a valid number
  local num = tonumber(data)
  if not num then
    sf.log("Invalid number: " .. data)
    return nil  -- Drop the message gracefully
  end
  return tostring(num * 2)
end

Performance Tips

  1. Avoid complex regex - Use Lua patterns instead
  2. Minimize global state - Keep scripts simple
  3. Return early - Drop invalid messages with return nil
  4. Pre-compile patterns - Store patterns in variables outside the function
-- Good: pattern compiled once
local TEMP_PATTERN = "TEMP:(%d+%.?%d*)"

function transform(data)
  local temp = data:match(TEMP_PATTERN)
  if temp then return temp end
  return nil
end

Next Steps