STARLEAP / SPIKE PRIME PYTHON / 2026–27

Build 2026.09.22-090630-0400

On this page

Project 09: Parking Approach Alarm

You need: a LEGO SPIKE Prime set and the SPIKE App Python editor. Keep Driving Base 2 from Project 08, including its forward-facing distance-sensor attachment: left wheel motor on Port C, right wheel motor on Port D, and distance sensor on Port F. Keep the same wall or bin target.

Goal

Make a parking alarm with slow, medium, and fast beeps as the robot approaches a wall. First test the sounds with the robot still. Then build the distance choices one at a time. Add movement after those choices work.

Step 1: Start With A Fresh Code Outline

Create a new Python project in the SPIKE App named Parking Approach Alarm. Keep Driving Base 2 assembled from Project 08. Type this outline into the new project:

CODE
from hub import light_matrix, port
import distance_sensor
import motor_pair
import runloop

left_wheel = port.C
right_wheel = port.D
distance_port = port.F

async def main():
    pass

runloop.run(main())

The imports make our tools available. The three port variables match the robot's wires. main() is where we will add instructions, and the final line starts it.

pass is a placeholder that does nothing. Python needs something inside the function while we build the program, so pass lets this outline run.

Run the program.

Observe: The program ends without moving or beeping. That is expected. If you see an error, check the imports, capitalization, colon, and the four spaces before pass.

Using the code: gray lines are already there; dark lines are the edit. Keep the shown indentation: four spaces inside main(), eight inside its loop, and twelve inside a choice in that loop. A line that moves back to the left ends the indented part above it.

Before the next test: lift the drive wheels. Stop the program before moving wires or changing the build. Save after each successful step so you can continue next time.

Step 2: Keep The Robot Still

Replace only the pass line with these two instructions inside main(). Keep the imports and port variables above it and the final runloop.run(main()) below it.

EDIT VIEW · Gray = already there; dark = add or change
async def main():
    motor_pair.pair(motor_pair.PAIR_1, left_wheel, right_wheel)
    motor_pair.stop(motor_pair.PAIR_1)

The first line groups the two wheel motors as a pair. The second tells that pair to stop. We will keep this stop at the beginning of main() as we add the alarm.

Run the program.

Observe: The wheels stay still. The program ends without beeping. This gives us a quiet place to test the new sound helper.

Step 3: Define A Beep With A Fixed Pause

Change the first import to include sound:

CODE
from hub import light_matrix, port, sound

Add this helper above main() and below your variables. Start async def at the left edge. Its two instructions begin four spaces in.

CODE
async def warning_beep():
    await sound.beep(700, 80, 100)
    await runloop.sleep_ms(700)

The helper makes one beep, then waits 700 milliseconds. The empty parentheses mean we are not asking a call to supply a value yet. Keep async and await: this helper waits for the sound and the pause to finish.

Run the program.

Observe: You still hear nothing. Defining a function gives it a name and instructions. We have not called it yet.

Step 4: Call The Helper Once

Add one line at the end of main(), four spaces in:

EDIT VIEW · Gray = already there; dark = add or change
async def main():
    motor_pair.pair(motor_pair.PAIR_1, left_wheel, right_wheel)
    motor_pair.stop(motor_pair.PAIR_1)
    await warning_beep()

Run the program.

Observe: You hear one beep. The call runs the helper's instructions. After its pause, the program ends. await means this part of the program waits for the helper to finish.

Fix it: If there is no sound, check that the call is inside main() and that the final runloop.run(main()) line is still at the left edge.

Step 5: Hear The Fixed-Pause Limit

Add two more calls directly below the first, at the same indentation:

EDIT VIEW · Gray = already there; dark = add or change
    await warning_beep()
    await warning_beep()
    await warning_beep()

Run the program.

Observe: You hear three beeps with the same pause between them. Each call runs the same two instructions.

In the helper, change only await runloop.sleep_ms(700) to await runloop.sleep_ms(150). Leave the sound.beep line unchanged.

Run the program again.

Observe: Both pauses between the three beeps are shorter. Changing the fixed number changes every call. We need a way for one call to choose a long pause and another call to choose a short pause.

Change the helper's pause back to 700, then run the program once more to hear the original spacing.

Step 6: Give The Helper A Parameter

We can give each call control of the pause. Make both edits below before running.

First, add the name gap_ms inside the helper's parentheses. Replace the fixed pause number with that name:

EDIT VIEW · Gray = already there; dark = add or change
async def warning_beep(gap_ms):
    await sound.beep(700, 80, 100)
    await runloop.sleep_ms(gap_ms)

gap_ms is a parameter: a name for a value the helper receives when it is called. Here it controls the pause after a beep, in milliseconds. It does not change the sound's pitch or length.

Next, put 700 inside the parentheses of all three calls in main():

CODE
    await warning_beep(700)
    await warning_beep(700)
    await warning_beep(700)

700 is an argument: the value this call supplies. For warning_beep(700), the helper uses 700 as gap_ms, so its last line waits 700 milliseconds.

Run the program.

Observe: The beeps sound just like Step 5's original version. We changed how the pause is supplied; each call still supplies the same value.

Fix it: If the program reports a missing argument, find a leftover warning_beep() call with empty parentheses. It now needs a pause value.

Step 7: Let One Call Choose A Shorter Pause

Change only the middle call's argument:

EDIT VIEW · Gray = already there; dark = add or change
    await warning_beep(700)
    await warning_beep(150)
    await warning_beep(700)

Predict: Which two beeps will be closer together?

Run the program.

Observe: There is a long pause after the first beep and a short pause after the second. The third call also waits after its beep, but there is no fourth beep to mark the end of that pause.

The helper is defined once. Each call supplies its own argument. Try 350 in the middle call, then run the program again. That pause should fall between the long and short pauses you just heard. Keep the helper itself unchanged.

Step 8: Start With Two Distance Choices

Keep the helper and its parameter. Replace only main() with this version. This replaces the three sound-test calls with our first sensor loop. Keep your imports, variables, and final start line.

EDIT VIEW · Gray = already there; dark = add or change
async def main():
    motor_pair.pair(motor_pair.PAIR_1, left_wheel, right_wheel)
    motor_pair.stop(motor_pair.PAIR_1)
    while True:
        distance = distance_sensor.distance(distance_port)
        print(distance)
        if distance == -1:
            motor_pair.stop(motor_pair.PAIR_1)
            print("no reading")
        else:
            print("valid reading")
        await runloop.sleep_ms(100)

if tests the first condition. else handles the remaining case. Here, -1 means the sensor has no usable reading; it is not a distance to the wall.

Run the program and move a flat target in front of the sensor.

Observe: A usable distance prints valid reading. When the sensor reports -1, the program prints no reading. The robot stays still and silent. Stop the program before the next edit.

Indentation check: if and else line up eight spaces in. Their instructions begin twelve spaces in. The final pause moves back to eight spaces, so it runs after either choice on each trip through the loop. Keep it there in the next steps.

Step 9: Add The Stop Choice

First, define the stopping distance. Add the dark line directly below distance_port, above the helper and outside both functions. Start it at the left edge, with no indentation. Keep the helper's existing body below its gray header.

EDIT VIEW · Gray = already there; dark = add or change
distance_port = port.F
too_close = 80

async def warning_beep(gap_ms):

too_close is a variable storing our stopping distance: 80 millimeters. The assignment gives the name a value; the condition below reads that value.

Next, insert this new elif just before the existing else. Change the text inside that else to clear. Keep the first if distance == -1 block above this edit. Make both edits before running.

EDIT VIEW · Gray = already there; dark = add or change
        elif distance <= too_close:
            motor_pair.stop(motor_pair.PAIR_1)
            print("stop")
            light_matrix.show_image(light_matrix.IMAGE_YES)
            break
        else:
            print("clear")
        await runloop.sleep_ms(100)

elif means "otherwise, if." This condition is tested only when the first if was false. <= means "less than or equal to," so this branch handles a valid distance of 80 millimeters or less. break ends the loop after the success image.

Run the program with the target farther than 80 millimeters away. Move it slowly closer while watching the printed distance.

Observe: The console changes from clear to stop at 80 or less. The success image appears and the loop ends. Move the target back and run the program again for another test.

Step 10: Add One Warning Zone

First, add too_far below too_close in the same variable block. It stores 300 millimeters, the outer edge of the warning zone. Keep these assignments at the left edge, above the helper.

EDIT VIEW · Gray = already there; dark = add or change
too_close = 80
too_far = 300

async def warning_beep(gap_ms):

Insert one elif directly before the final else. Keep the no-reading and stop choices above it:

EDIT VIEW · Gray = already there; dark = add or change
        elif distance <= too_far:
            print("slow beep")
            await warning_beep(700)
        else:
            print("clear")
        await runloop.sleep_ms(100)

The stop choice already catches 80 or less. This new choice therefore handles distances above 80 and up to 300. The remaining else handles distances above 300.

Run the program. Start beyond 300, then move the target to about 250 millimeters.

Observe: The robot stays still. clear is silent; slow beep repeats the helper with a 700-millisecond pause. Move to 80 or less to check that the stop choice still ends the loop.

Step 11: Split Off A Fast-Beep Zone

First, add close_distance between the existing cutoffs above the helper. It stores 130 millimeters, the outer edge of the fast-beep zone. Only the dark assignment is new:

EDIT VIEW · Gray = already there; dark = add or change
too_close = 80
close_distance = 130
too_far = 300

Insert a new elif immediately before elif distance <= too_far. Keep the existing slow-beep branch below it:

EDIT VIEW · Gray = already there; dark = add or change
        elif distance <= close_distance:
            print("fast beep")
            await warning_beep(150)
        elif distance <= too_far:
            print("slow beep")
            await warning_beep(700)

Run the program with the target around 100 millimeters away. Then move it to about 250.

Observe: Around 100, you get fast beeps. Around 250, you still get slow beeps. The stop and clear choices still work.

Why this order matters: A distance of 100 is less than both 130 and 300. Python uses the first true branch in this chain and skips the later branches for that reading. The smaller cutoff must come first. Keep elif aligned with if; adding a separate if would start a second chain.

Step 12: Add The Middle Zone

First, add medium_distance to the same variable block above the helper. It stores 200 millimeters, the outer edge of the medium-beep zone. Your four cutoff definitions should now look like this:

EDIT VIEW · Gray = already there; dark = add or change
too_close = 80
close_distance = 130
medium_distance = 200
too_far = 300

These four numbers are distances in millimeters. The arguments in warning_beep(...) are pauses in milliseconds; they control something different.

Next, insert one more branch between the fast-beep and slow-beep branches:

EDIT VIEW · Gray = already there; dark = add or change
        elif distance <= close_distance:
            print("fast beep")
            await warning_beep(150)
        elif distance <= medium_distance:
            print("medium beep")
            await warning_beep(350)
        elif distance <= too_far:
            print("slow beep")
            await warning_beep(700)

Run the program. Try readings near 100, 160, and 250 millimeters.

Observe: You hear fast, medium, and slow spacing. Only one branch responds to each reading. A smaller argument gives a shorter pause after the beep.

Check The Whole Chain

Read your choices from top to bottom. There is one if, four elif lines, and one final else. They should appear in this order:

Reading in millimetersFirst matching choiceResult
-1if distance == -1No reading; stop motors, no beep
0 through 80elif distance <= too_closeStop, success image, end loop
81 through 130elif distance <= close_distanceFast beep; argument 150
131 through 200elif distance <= medium_distanceMedium beep; argument 350
201 through 300elif distance <= too_farSlow beep; argument 700
Above 300elseClear; no beep

Predict: For a reading of 160, which conditions are false before Python reaches the matching branch? Which argument reaches gap_ms?

Run the program to check. Then test the stop choice and a missing reading. Keep the final 100-millisecond loop pause; during a warning, it comes after the helper's beep and chosen pause.

Step 13: Add Movement To The Tested Choices

Before you run: keep the wheels lifted for this edit. Leave both motor_pair.stop lines in the no-reading and stop choices. Leave the stop at the beginning of main() too.

Add one movement line after the print in each of the four choices below. These are separate edits to branches you already tested. Make all four edits before running; keep the rest of the chain and the final loop pause.

Inside the fast-beep choice:

EDIT VIEW · Gray = already there; dark = add or change
            print("fast beep")
            motor_pair.move(motor_pair.PAIR_1, 0, velocity=80)
            await warning_beep(150)

Inside the medium-beep choice:

EDIT VIEW · Gray = already there; dark = add or change
            print("medium beep")
            motor_pair.move(motor_pair.PAIR_1, 0, velocity=120)
            await warning_beep(350)

Inside the slow-beep choice:

EDIT VIEW · Gray = already there; dark = add or change
            print("slow beep")
            motor_pair.move(motor_pair.PAIR_1, 0, velocity=160)
            await warning_beep(700)

Inside the final else:

EDIT VIEW · Gray = already there; dark = add or change
            print("clear")
            motor_pair.move(motor_pair.PAIR_1, 0, velocity=200)

Run the program with the wheels lifted and move the target through the zones.

Observe: The wheels turn forward and slow down as the target gets closer. A reading of 80 or less, or a missing reading, stops them. If a direction is wrong, stop and check the Project 08 wiring and wheel test before continuing.

Step 14: Tune A Slow Approach

Stop the program. Put the robot on a clear floor with the sensor facing the wall. Run the program and keep the stop control ready.

The robot approaches forward. The beeps imitate a reversing vehicle's warning pattern. The wheels keep moving during each beep and pause; the sensor is checked again on the next trip through the loop. A long pause lets the robot travel farther before that next check.

Change one thing at a time, then run the program after each change:

Physical Challenge: Alarm Parking

Use the Parking Wall setup from the Challenge Setups sheet.

Teacher Check

Show your teacher:

Before You Put The Kit Away

Save your program. Stop it before moving wires or changing the build. Keep your place for next time.