pcall in Dobot Lua: Error Handling That Does Not Stop Your Robot Cell

An unhandled Lua error terminates the robot program, immediately: the cell stops, the part is stuck in the gripper and the shift calls maintenance. Yet Lua ships a tool that prevents exactly that: pcall. Using DobotStudio examples, this article shows how to protect motion commands, retry errors deliberately, define custom error objects and make faults centrally visible, so a runtime error does not become a production stop.

Why error handling works differently in a robot program

In a desktop application, a crashed process is annoying. In a robot cell it means: the program aborts mid-cycle, axes stop in an undefined position, workpiece and gripper are in a state nobody planned. The restart does not just cost minutes, it usually needs a person to jog the robot free by hand.

Typical error sources in daily Dobot work: a workpiece sits crooked and the motion runs against a limit switch, a TCP connection to the camera system drops, a file on the SD card cannot be opened, a sensor returns an unexpected value. None of that has to end the program if the error is caught in the right place.

pcall in 60 seconds

pcall stands for "protected call": the given function is executed, but an error inside it does not end the program. Instead pcall returns two values: a success flag and, on failure, the error message.

-- Basic pattern: protected call
local ok, err = pcall(function()
    risky_function()
end)

if not ok then
    print("Error caught: " .. tostring(err))
    -- program keeps running, we decide what happens
end

Protecting motion commands

The most practical entry point is a wrapper around the motion commands. Instead of calling MovJ and MovL directly, wrap them in a function that catches errors, logs them and brings the cell into a defined state:

-- Global.lua: protected motion with logging
function safe_move(name, motion)
    local ok, err = pcall(motion)
    if not ok then
        log_local("ERROR", "Motion '" .. name .. "' failed: " .. tostring(err))
        DO(GRIPPER, OFF)   -- release the workpiece in a controlled way
        Sync()
        return false
    end
    return true
end

-- Usage in the cycle
if not safe_move("Approach tray", function() MovJ(P_TRAY) end) then
    return  -- end the cycle cleanly instead of aborting mid-motion
end

Retrying with restraint: bounded retries

Not every error is final. A dropped network connection to the camera system or a briefly bouncing sensor is often back a second later. Such cases justify a retry, but always with an upper bound and a wait, otherwise the program hammers on relentlessly in the failure case:

-- Bounded retry: max. 3 attempts, 1 s pause
function with_retry(name, attempts, pause_ms, fn)
    for attempt = 1, attempts do
        local ok, err = pcall(fn)
        if ok then return true end
        log_local("WARN", name .. " attempt " .. attempt .. "/" .. attempts
            .. " failed: " .. tostring(err))
        Wait(pause_ms)
    end
    log_local("ERROR", name .. " failed for good after " .. attempts .. " attempts")
    return false
end

-- Example: camera trigger via TCP, tolerates short dropouts
local ok = with_retry("Camera trigger", 3, 1000, function()
    local socket = TCPCreate(false, CAMERA_IP, CAMERA_PORT)
    TCPStart(socket, 0)
    TCPWrite(socket, "TRIGGER")
    TCPDestroy(socket)
end)

Using error() deliberately: custom error objects

pcall catches not only runtime errors but also everything you raise yourself with error(). That makes plausibility checks elegant: the check throws, the central handler decides. A table is allowed as the error value, so the error carries structured information instead of just text:

-- The check throws a structured error object
function check_part()
    if DI(SENSOR_PART) ~= ON then
        error({ code = "NO_PART", text = "No workpiece at position" })
    end
end

local ok, err = pcall(check_part)
if not ok then
    if type(err) == "table" and err.code == "NO_PART" then
        -- expected case: feeder empty, wait for refill in an orderly way
        log_local("WARN", err.text)
        wait_for_refill()
    else
        -- everything else is a real error
        log_local("ERROR", tostring(type(err) == "table" and err.text or err))
        move_to_home()
    end
end

Making errors visible: central logging with Leif

Caught errors that only land on the robot console are seen by nobody. Only central logging turns error handling into an early-warning system: if retries pile up at one station, a mechanical problem is announcing itself long before the cell actually stops. How to extend the log_local function from these examples to ship messages to Leif is shown step by step in the article Logging with Dobot Lua. In Leif you then see all WARN and ERROR messages of your robots live, with alerts to Microsoft Teams, and the incident timeline replays camera footage and program line for every message.

Checklist for robust Dobot programs

  • Run every motion command through a pcall wrapper, never bare in the cycle
  • On failure, first bring gripper and workpiece into a defined state
  • Retries always with an upper bound and a wait, then move to the safe state in an orderly way
  • Separate expected states (feeder empty, door open) from real errors using error() objects
  • Log every caught error, WARN for retryable, ERROR for final
  • Collect messages centrally so clusters stand out before the cell stops

Frequently asked questions

What exactly does pcall do in Lua?

pcall executes a function in protected mode. If an error occurs inside, the program does not abort; instead pcall returns false and the error message. Without an error it returns true plus the function's return values.

When do I use xpcall instead of pcall?

xpcall additionally accepts your own error handler, which runs at the moment of the error and can capture a stack trace with debug.traceback, for example. For most robot programs pcall is enough; xpcall pays off when you want to know exactly where things blew up.

Does pcall slow down the robot program?

The overhead of a pcall call is negligible compared to a robot motion in the millisecond range. In tight loops without motions, do not wrap every tiny operation individually; wrap sensible blocks instead.

Let us talk.

Free initial consultation: We will get back to you as soon as possible.