controller.py
Output
Hover a chart to read values. Click to move playback there.
The task
The car is switched on at t = 0, standing still and pointing along the tape. Get it down the floor and across the finish tape as close to the line as you can. The track is 4 m unless you pick another length. You get 20 s plus 5 s per metre of track, and the run stops if the car gets more than a quarter of the track length off the line (at least 1 m). Errors grow with distance, so a controller that stays close on 16 or 32 m is a better controller.
Your program sees the IMU and sets the two motors. There are no wheel encoders and no position.
Car, power-on and noise
- Car
- A physical car. Its motors, wheels, battery and IMU unit are fixed: car 7 is always car 7, with the same gyro offset and the same lazy left motor.
- Power-on
- One time you switch the car on and let it go. Each one starts the gyro with a slightly different turn-on offset, leaves the caster at a different angle, finds the battery a little more or less charged and the room a little warmer or cooler, and draws a new sequence of sensor noise and floor bumps. Run gives you a new power-on every time, so the same car and code land somewhere slightly different, like on a real floor. Tick Repeat it to replay one power-on exactly, which is handy while debugging.
- Noise
- Scales everything random within a power-on: gyro and accelerometer noise, slow bias wander, the turn-on offset spread, the settling transient and the floor bumps. 1× is a realistic MPU-6050 on a hobby car. At 0× the sensor is noise-free, but the car's own errors (gyro offset, temperature drift, mismatched motors) are still there: those are not noise.
- Benchmark
- Runs cars 1 to 20 on the track length and noise you picked, each with a fixed power-on, so the same code always gets the same score and you can compare versions fairly.
Scores
- Path RMSE
- The root mean square of the car's distance from the line, measured at every centimetre of forward progress, over all runs. It rewards driving straight the whole way, not just crossing the finish in the right place. If a car leaves the track, stops with an error or runs out of time, the rest of the track counts as the off-track distance (a quarter of the track length, at least 1 m). Benchmark and the leaderboard both rank by it.
- Finish RMSE
- The root mean square distance from the line where the car crosses the finish, with a car that does not finish counting as the off-track distance.
- Leaderboard
- Every submission is run on the same 30 hidden cars, each with its own power-on, on the 32 m track at 1× noise. Nobody can see those cars, so there is nothing to tune to except driving straight. The code runs as standard Python 3 in a locked-down sandbox: it only receives IMU readings and only sets the motors. Only the standard library is available, here and there.
Your program
Define loop(), and optionally setup(). These are ready to use, no imports needed:
setup()- Called once at power-on.
loop()- Called at the loop rate, 100 Hz unless you change it.
set_loop_rate(hz)- 1 to 1000 Hz.
now()- Seconds since power-on.
imu.gyro()- (x, y, z) angular rate in °/s. Positive z means turning left.
imu.accel()- (x, y, z) in g. x forward, y left, z up. Parked on a flat floor it reads about (0, 0, 1).
imu.temperature()- On-die temperature sensor, °C.
imu.read()- The whole latest sample:
t,seq,gyro,accel,temp.seqgoes up by one per new sample, so you can tell a fresh reading from a repeated one. imu.configure(dlpf, rate, gyro_range, accel_range)- All optional.
dlpf: 256, 188, 98, 42, 20, 10 or 5 Hz low-pass.rate: output data rate in Hz (rounded to what the chip's divider can do).gyro_range: 250 to 2000 °/s.accel_range: 2 to 16 g. Returns the settings in effect. Power-on default: 256 Hz filter, fastest rate, ±250 °/s, ±2 g. motors.set(left, right)- Duty from −1.0 to 1.0, in steps of 0.001. It holds until you change it. 0 lets the motor coast.
motors.stop()- Same as
motors.set(0, 0). plot(name, value)- Record a value over time. Each name gets its own chart, up to 8.
print(...)- Writes to Output, stamped with the time.
Python 3 runs in your browser with the standard library (math, collections, statistics…). Nothing you write leaves this page unless you copy a share link.
The car
What the parts list says: two TT gear motors (1:48, about 200 rpm at 6 V), 65 mm wheels, 130 mm between the wheels, about 550 g, a swivel caster at the back, an L298N-style motor driver that loses about 1.8 V, a 2-cell Li-ion pack, and an MPU-6050 on a GY-521 board. Each real car is off from these numbers in its own way.
What the simulation includes
- Motors
- Back-EMF, winding resistance, gearbox friction and stiction (a dead band at low duty), rotor inertia, each motor with its own tolerances. The battery sags under load, and the driver drops voltage.
- Wheels and chassis
- Unequal and slightly out-of-round wheels, tyre slip with a traction limit (spin a wheel at launch and the heading jumps), rolling resistance, yaw inertia, a caster that starts at a random angle and shoves the car until it trails straight, floor texture, a slightly sloped floor.
- Gyro
- A zero-rate offset of up to a few °/s per unit, a different turn-on offset each power-on, drift with temperature as the board warms up and the motor driver heats, slow bias wander, white noise of about 0.005 °/s/√Hz, scale and cross-axis error, mounting misalignment, a settling transient after power-on, and motor vibration: a tone at each motor's shaft speed that aliases if you filter wide and sample slowly, plus a bias shift while the motors run.
- Accelerometer
- Offsets, scale error, temperature drift, noise of about 400 µg/√Hz, vibration, gravity from the floor slope.
- Sampling
- The low-pass filter as a 2nd-order filter, the chip's own sample clock error, 16-bit quantisation and saturation at the chosen range.