Test scripts are written in Lua 5.4 and automate simulator interaction for testing. Pass a script file with --script:
edgetx-cli dev simulator --radio "Radiomaster TX16S" --headless --script test.lua --timeout 30s --screenshot result.png| Function | Description |
|---|---|
key.press(key) |
Tap: key down, 100ms pause, key up |
key.longpress(key) |
Long press: key down, 1s pause, key up |
key.down(key) |
Hold a key down |
key.up(key) |
Release a key |
Key arguments accept a KEY constant or a string: key.press(KEY.ENTER) or key.press("ENTER").
KEY constants: KEY.MENU, KEY.EXIT, KEY.ENTER, KEY.PAGEUP, KEY.PAGEDN, KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT, KEY.PLUS, KEY.MINUS, KEY.MODEL, KEY.TELE, KEY.SYS
| Function | Description |
|---|---|
touch.tap(x, y) |
Tap: touch down, 100ms pause, touch up |
touch.longpress(x, y) |
Long press: down, 1s pause, up |
touch.down(x, y) |
Hold touch at coordinates |
touch.release() |
Release touch |
| Function | Description |
|---|---|
trim.press(name) |
Tap: down, 100ms pause, up |
trim.longpress(name) |
Long press: down, 1s pause, up |
trim.down(name) |
Hold trim button |
trim.up(name) |
Release trim button |
trim.get(name) |
Get current trim value |
trim.set(name, value) |
Set trim to an exact value |
trim.range() |
Get trim min/max limits (returns two values) |
Trim arguments accept a TRIM constant, string name, or raw index: trim.press(TRIM.T1) or trim.press("T1").
| Function | Description |
|---|---|
switch(name, state) |
Set switch position (-1, 0, 1) |
analog(name, value) |
Set analog input (0-4096) |
rotary(delta) |
Rotary encoder delta |
Switch and analog accept a SWITCH/INPUT constant, string name, or raw index: switch(SWITCH.SA, -1) or switch("SA", -1).
SWITCH / INPUT / TRIM constants are radio-specific and auto-populated from the radio definition (e.g., SWITCH.SA, SWITCH.SB, INPUT.LH, INPUT.P1, TRIM.T1, TRIM.T4).
| Function | Description |
|---|---|
channel.get(index) |
Get channel output value (1-based, -1024 to 1024) |
channel.mixer(index) |
Get mixer output value (1-based) |
channel.count() |
Number of output channels |
channel.used(index) |
Whether channel is in use (boolean, 1-based) |
channel.mix_count() |
Number of active mixes in the model |
| Function | Description |
|---|---|
logicalswitch.get(index) |
Get logical switch state (true/false, 1-based) |
logicalswitch.count() |
Number of logical switches |
| Function | Description |
|---|---|
gvar.get(gvar, flightmode) |
Get GVar value (both 1-based) |
gvar.count() |
Number of global variables |
gvar.flightmodes() |
Number of flight modes |
gvar.flightmode() |
Current active flight mode (0-based) |
Example:
-- Read all active logical switches
for i = 1, logicalswitch.count() do
if logicalswitch.get(i) then
print("L" .. i .. " is ON")
end
end
-- Read channel outputs
for i = 1, channel.count() do
if channel.used(i) then
print("CH" .. i .. " = " .. channel.get(i))
end
end| Function | Description |
|---|---|
wait(seconds) |
Wait for a duration (float, in seconds) |
screenshot(path) |
Save LCD framebuffer as PNG |
reset() |
Full simulator restart — reloads all scripts, widgets, and resets screen |
reload() |
Reload Lua scripts from SD card (mix, function, telemetry — not widgets) |
exit(code) |
Exit with a process exit code |
print(...) |
Debug logging (Lua standard library) |
Scripts return exit code 0 by default. Use exit(code) to terminate early with a specific exit code. This is useful for CI pipelines where you need to signal pass/fail.
Scripts halt immediately on any error. Error messages include file name, line number, and a description of the problem (e.g., unknown key "BOGUS" (available: MENU, EXIT, ...)). Script errors produce a non-zero exit code.
test.lua:
-- Wait for boot
wait(5)
-- Navigate to the tools menu
key.press(KEY.SYS)
wait(1)
key.press(KEY.PAGEDN)
wait(0.5)
-- Take a screenshot
screenshot("tools-menu.png")edgetx-cli dev simulator \
--radio "Radiomaster TX16S" \
--headless \
--script test.lua \
--timeout 30s \
--screenshot final.pngUse --script - or --script-stdin to read Lua commands from stdin. This enables AI-driven testing and interactive piped scripting. Multi-line constructs (e.g., for/end blocks) are automatically detected and buffered until complete.
# Pipe commands
echo 'print("hello")' | edgetx-cli dev simulator \
--radio "Radiomaster TX16S" --headless --script - --timeout 10s
# Multi-line via stdin
printf 'for i=1,3 do\nprint(i)\nend\n' | edgetx-cli dev simulator \
--radio "Radiomaster TX16S" --headless --script-stdin --timeout 10s
# Exit with a specific code
echo 'exit(42)' | edgetx-cli dev simulator \
--radio "Radiomaster TX16S" --headless --script - --timeout 10s
echo $? # prints 42When an AI agent (e.g. Claude Code) needs to interactively control the simulator — sending commands one at a time, observing the screen, and deciding what to do next — a simple pipe won't work because each shell invocation is a separate process. Instead, use tail -f on a regular file to keep the simulator's stdin open across multiple shell calls.
# Create a command file and log file
touch /tmp/sim-cmds
touch /tmp/sim-log
# Launch the simulator in the background, feeding commands via tail -f
tail -f /tmp/sim-cmds | edgetx-cli dev simulator \
--radio "Radiomaster TX16S" \
--headless \
--script-stdin \
--timeout 120s \
> /tmp/sim-log 2>&1 &
SIM_PID=$!The key insight: each echo 'cmd' >> /tmp/sim-cmds is a separate shell invocation that appends to a regular file. tail -f watches that file for new content and continuously feeds new lines into the simulator's stdin — bridging separate shell calls into one persistent stream.
# Always wait for the simulator to boot before sending navigation commands
echo 'wait(3)' >> /tmp/sim-cmds- Send Lua commands by appending to the command file (see scripting API above)
- Take a screenshot to observe the result
- Read the screenshot PNG to see what happened
- Decide the next action and repeat
# Send a command
echo 'key.press(KEY.SYS)' >> /tmp/sim-cmds
# Wait for the UI to update, then capture a screenshot
echo 'wait(1)' >> /tmp/sim-cmds
echo 'screenshot("/tmp/sim-screen.png")' >> /tmp/sim-cmds
# Give the screenshot time to be written, then read the PNG to observe the result
sleep 1
# (AI agent reads /tmp/sim-screen.png to see the current screen)
# Send the next command based on what was observed
echo 'touch.tap(240, 20)' >> /tmp/sim-cmdsprint() output goes to stdout, which is captured in the log file (/tmp/sim-log). Read the log file to see Lua print output.
For multi-line Lua blocks (e.g. for/end), use a heredoc so all lines arrive together for the block detector:
cat >> /tmp/sim-cmds << 'CMDS'
for i = 1, 5 do
rotary(1)
wait(0.3)
end
CMDS# Tell the simulator to exit with a specific code
echo 'exit(0)' >> /tmp/sim-cmds
# Wait for the process to finish and capture its exit code
wait $SIM_PID
echo $? # prints 0# Kill the simulator if still running
kill $SIM_PID 2>/dev/null
wait $SIM_PID 2>/dev/null
# Remove temp files
rm -f /tmp/sim-cmds /tmp/sim-log /tmp/sim-screen.png-- Helper to navigate down N times
function nav_down(n)
for i = 1, n do
key.press(KEY.DOWN)
wait(0.2)
end
end
wait(3)
key.press(KEY.SYS)
wait(1)
nav_down(5)
screenshot("result.png")A single script demonstrating every available Lua function and constant table: showcase.lua.