Skip to content

Problems while the program runs

Your program started and your callbacks are being called. These are the failures you hit from there on: the robot stopping, an error raised from an API call, or data you cannot trust.

The robot does not respond and robot.overtemp is True

The robot brain's board reached 50 °C and stopped itself. It will not accept commands again until the board cools to 45 °C.

Read robot.overtemp to detect this state. Let the board cool. The extension brings the robot back once the temperature drops below 45 °C; your program does not need to do anything else.

robot.estopped is True and nothing moves

The robot is e-stopped. E-Stop authority lives in the MARLIN extension and the pool dashboard. Your Python code cannot set or clear E-Stop.

Read robot.estopped to detect this state. Clear the E-Stop from the extension or the pool dashboard, not from your program.

A callback stops running

What you see: the robot does the first thing you asked, then sits still for the rest of the period even though the phase clock is still counting down.

MARLIN calls autonomous(robot) and driver(robot) once each, at the start of their phase. When that function returns, whether from an explicit return or by simply reaching its last line, MARLIN treats the phase's work as finished: it does not call the function again, and it idles every effector until the next phase begins.

So this drives for a few milliseconds, not for the whole period:

def autonomous(robot):
    robot.thruster("M1").set_duty(30)   # returns immediately, then idle

Anything that should keep happening belongs inside the phase loop, which runs until the pool server ends the phase:

def autonomous(robot):
    while robot.running:
        robot.thruster("M1").set_duty(30)

robot.running becomes false when the phase ends, so the loop exits on its own. See Lifecycle for the full phase sequence.

A motor or servo set in initialize() never moves

What you see: the program runs and the match starts normally, but the one thing you did in initialize(), such as stowing an arm or centring a servo, never happened. There is no error.

initialize() runs before the match starts, so MARLIN is not sending effector commands to the robot yet, and it idles every effector the moment initialize() returns. The command is discarded either way:

def initialize(robot):
    robot.servo("M2").angle(0)   # discarded: the match has not started

Move anything that positions the robot to the top of the phase that needs it. It still runs once, before the loop:

def autonomous(robot):
    robot.servo("M2").angle(0)   # runs once, before the loop
    while robot.running:
        ...

initialize() is still the place for setup that is not movement: reading sensors, printing, and working out values your callbacks need. See Lifecycle.

An accessor raises KeyError

The requested port was not declared in configure():

robot.sensor("S6")
robot.effector("M1")

Use the same port spelling as the configuration mapping.

A typed accessor raises TypeError

robot.thruster(port) and robot.servo(port) verify the configured type. Use the accessor matching the declaration, or use robot.effector(port) when code intentionally accepts either type.

AttributeError on robot.imu.read or robot.radiobeacon.read

robot.imu and robot.radiobeacon are methods, not properties. Missing the call parentheses:

robot.imu.read()          # wrong: forgot to call robot.imu("S1")

reads the bound method object itself and then looks for .read on that, which does not exist, producing a confusing AttributeError. Call the method first, then read from what it returns:

robot.imu("S1").read()    # correct

robot.imu() or robot.radiobeacon() raises LookupError

You called the accessor without naming a port:

reading = robot.imu().read()      # no port given

Name the port you wired the sensor to:

reading = robot.imu("S1").read()

Leaving the port out asks MARLIN to guess, and it only can when exactly one sensor of that type is configured. With none configured, or with two, there is no single right answer and you get LookupError. Naming the port removes the guess, so write it every time.

If the port is named and MARLIN still cannot find the sensor, it was never declared. Add it to robot.configure(), on whichever port it is actually wired to, and you will get KeyError until you do:

robot.configure(
    sensors={"S1": IMU},
    effectors={"M1": Thruster},
)

Naming the port also gives you working editor completion.

A thruster or servo will not go past a certain point

set_duty(), angle(), and micros() each clamp to their range: set_duty takes -100 to 100, angle takes 0 to 180, and micros takes 500 to 2500. A value past either end is pinned to that end and sent, so asking for 150 duty runs the thruster at 100 and nothing reports a problem.

If a thruster seems stuck at full power, or a mix turns less sharply than the sticks suggest, print the value you are passing before you send it. A mix that clamps on one side only will skew the difference between two thrusters. See Range clamping.

A command prints is not a usable number

Typical message:

[API] M1: set_duty(nan) is not a usable number, so the setpoint is left as it was. Check the maths feeding it.

nan ("not a number") is the one value MARLIN cannot clamp, so the effector holds whatever setpoint it already had. It usually comes from arithmetic on a missing reading, such as dividing by zero or using a sensor value without checking reading.ok first. The message prints once per setter, not once per loop.

joystick() raises ValueError

Typical message:

Unknown joystick 'FIVE'. Expected one of ONE, TWO, THREE, FOUR.

There are exactly four joystick axes, so MARLIN rejects any other name instead of returning an axis that always reads zero. Use "ONE", "TWO", "THREE", or "FOUR". See Controller input for which physical axis each one is.

The robot creeps with the sticks released, or never reaches full speed

joystick() reads a raw ADC value, and no gimbal is perfect: released, it sits a little off the middle of its range, and pushed hard over it stops short of the electrical end. So an untouched stick can read a few counts instead of 0, and a stick at the stop can read 90 instead of 100.

Click Calibrate Sticks in the Control Station. It measures the resting position and the real travel of each stick and saves both into project.marlin. See Calibrate Sticks. Calibration is per project, so a fresh project starts uncalibrated.

If one axis still tops out short after calibrating, its sweep did not reach the edge all the way round; the status bar names the joystick to run again.

A sensor reading has ok == False

The sample is missing, marked invalid, or more than 250 ms old. Inspect reading.age, check robot.link_ok, and use a safe fallback. Do not command motion based on stale sensor values.

Controller input and robot telemetry travel through different parts of the system. The Controller can continue reporting sticks and buttons while the Controller-to-robot link is down. Treat link_ok == False as a robot-link failure even if controller values still change.