Robot code for Team Lebob's 2025 RoboCup Junior Rescue Line entry. Written in Python for a LEGO SPIKE Prime hub running PyBricks firmware.
Lebob is an independent, not-for-profit robotics team from Perth, Western Australia. This is a past season: the code here is what we ran in 2025 and is not under active development. Our current work is FIRST Tech Challenge (team 29550).
The whole run is a state machine in main.py. On every loop the robot reads both
colour sensors and the ultrasonic sensor, picks a state, and acts on it.
| State | Trigger | Behaviour |
|---|---|---|
line |
Both sensors on black or white | PD line following on the reflectance difference between the two sensors |
green left / green right |
A green marker confirmed under one sensor | Stop, turn into the marker, curve back onto the line |
obstacle |
Ultrasonic reads under the threshold on consecutive samples | Arc around the obstacle, then hunt for the line again |
spill |
Green evacuation zone entered | Find the can, push it out of the zone, exit and reacquire the line |
stop |
End of run | Cut the motors |
Some detail worth knowing:
- Line following is PD (
LINE_KP,LINE_KD) on the left/right reflectance error, with a separate corner branch for when the error spikes pastCORNER_ERR_THRESHOLDand a slow mode when both sensors go black. - Can finding in the evacuation zone sweeps the ultrasonic sensor across an arc,
segments the readings, and takes the midpoint of the nearest valid segment. There
is also a micro-oscillation search (
find_can_angle_micro_oscillation) for when the coarse sweep is ambiguous, and a full 360° variant. - Gyro is used for straight driving and turns via
DriveBase.use_gyro(True), with a fallback if the IMU fails to come up.gridlock_*helpers snap the heading to the nearest cardinal and drive intersection to intersection for grid sections. - Line reacquisition after leaving the zone or an obstacle oscillates in small steps until it confirms black under a sensor, rather than assuming it drove back onto the line.
main.py The run. Ports, CONSTANTS, the Robot class, the state machine.
main2.py Earlier, simpler version kept for reference.
main_copy.txt Snapshot of main.py from before the evacuation-zone rewrite.
run.py Wrapper so `python run.py` downloads and runs main.py over BLE.
calibrate_motors.py Measures left/right drive bias and prints trim values.
calibrate_colors.py Prints HSV/reflectance readings so thresholds can be retuned.
requirements.txt Host-side tooling: pybricksdev, bleak, pyserial.
Ports as wired in main.py:
| Port | Device |
|---|---|
| E | Left drive motor (reversed) |
| C | Right drive motor |
| D | Left colour sensor |
| A | Right colour sensor |
| B | Ultrasonic sensor |
| F | Arm motor |
Drive base: 56 mm wheels, 112 mm axle track. Hub orientation PrimeHub(Axis.Z, Axis.X).
Flash the hub with PyBricks firmware at https://code.pybricks.com (Install Firmware), then:
pip install -r requirements.txt
python run.py # downloads and runs main.py over BluetoothOr run main.py straight from the PyBricks web IDE. The .vscode/launch.json config
does the same thing for whichever file is open.
Everything tunable lives in the CONSTANTS dict at the top of main.py. The values
that moved most between practice runs:
LINE_KP,LINE_KD,LINE_MAX_TURN_RATE: line following aggressiveness.LINE_BLACK_REF_THRESHOLD,GREEN_HUE_MIN/MAX,GREEN_S_MIN: colour thresholds. Re-runcalibrate_colors.pyunder the venue's lighting before trusting these.LEFT_MOTOR_TRIM,RIGHT_MOTOR_TRIM,BASE_TURN_BIAS,STRAIGHT_TURN_TRIM: mechanical bias correction.calibrate_motors.pyproduces the first two.ULTRASONIC_THRESHOLD,OBSTACLE_DETECT_CONFIRM: obstacle sensitivity. The*_CONFIRMconstants all mean "this many consecutive readings before we believe it", which is what stopped most of our false triggers.
Past-season code, kept public so other Rescue Line teams can read it. It is not tidy, commit messages and stray comments included. It is what actually ran.
Team Lebob · Perth, Western Australia · https://lebob.com.au