Program structure¶
Every MARLIN program answers three questions: what hardware do I have, what should it do, and when should it do it. This page shows where each answer goes in the file, and why the shape has to be that way.
Most MARLIN programs look like this:
from marlin import Robot, Thruster
robot = Robot()
robot.configure(
sensors={},
effectors={"M1": Thruster},
)
def autonomous(robot):
while robot.running:
...
def driver(robot):
while robot.running:
...
robot.run(autonomous=autonomous, driver=driver)
This page explains the ideas behind that shape.
One Robot object¶
Create one Robot object near the top of your program:
You read all of these from the robot object:
- configured sensors
- configured effectors
- controller input
- battery voltage
- E-Stop state
- robot-link status
- overtemp state.
Configuration happens once¶
Call configure() once before run():
Configuration tells MARLIN what hardware you expect to exist. MARLIN sends that configuration through the extension and checks the robot's response before your program can run. Nothing on the robot moves until the two match.
If configuration fails, do not catch the error and keep driving. Fix the declared ports or the physical robot configuration.
Callbacks describe match behaviour¶
robot.run() accepts callbacks: functions you
define and MARLIN calls for you:
| Callback | Purpose |
|---|---|
initialize(robot) |
Setup and checks before the match starts |
autonomous(robot) |
Behaviour during the Autonomous Period |
driver(robot) |
Behaviour during the Driver Controlled Period |
You can omit callbacks you do not need.
initialize is for setup only: reading sensors, printing, and working out
values the other callbacks need. Commanding a thruster or servo there does
nothing, because the match has not started and MARLIN idles every output as
soon as initialize returns. Anything that positions your robot goes at the
top of autonomous or driver. See
Match lifecycle.
There is no callback for the pause phase between the two periods, or for the phase after the match ends. MARLIN handles the robot on its own then: it idles every effector and keeps the connection alive, without running any of your code.
Use while robot.running¶
Phase callbacks usually need a
while loop:
def driver(robot):
while robot.running:
power = robot.controller.joystick("ONE").value
robot.thruster("M1").set_duty(power)
robot.running is true only while the phase that called your function is
still going. When that phase ends, robot.running becomes false, the while
loop stops on its own, and your function returns. You do not need to check the
clock or break out of the loop yourself.
flowchart LR
phaseStart["Driver Controlled Period starts"]
invoke["MARLIN calls driver(robot)<br/>robot.running is now True"]
looping["Your while loop repeats<br/>fifty times per second"]
phaseEnd["Phase ends<br/>robot.running becomes False"]
returned["Loop exits, your function returns<br/>MARLIN idles every effector"]
phaseStart --> invoke --> looping --> phaseEnd --> returned
looping --> looping
This is important: if your callback returns early, MARLIN idles all effector setpoints and does not call that same callback again during the same phase.
Reads are cached¶
Controller, sensor, and status reads return the latest cached value:
forward = robot.controller.joystick("ONE").value
reading = robot.imu("S1").read()
voltage = robot.battery_voltage
A read never waits. MARLIN keeps the most recent value it received from the robot, and hands you that value immediately. Your loop keeps running at full speed instead of pausing for the robot to answer.
For sensors, always check ok:
reading = robot.imu("S1").read()
if reading.ok:
gyro_z = reading.gyro_z
else:
robot.thruster("M1").set_duty(0)
Commands are remembered, not sent one by one¶
Calling an effector method does not send a message by itself. It records the value you want, and MARLIN keeps sending that value to the robot until you change it:
Because the value is repeated for you, a thruster set to 25% stays at 25% even
if your loop does not call set_duty() again. You never build or send messages
yourself.
MARLIN idles every effector when a callback returns or when run() exits.
Thrusters stop, and servos return to 90°.
Safety controls are outside your code¶
Your program can read robot.estopped, but it cannot trigger or clear E-Stop:
E-Stop belongs to the MARLIN extension and the pool dashboard, so safety remains independent of your Python code.