STARLEAP / SPIKE PRIME PYTHON / 2026–27
Build 2026.09.22-090630-0400
On this page
- Step 1: Read One Angle
- Step 2: Calibrate Level And Sign
- Step 3: Save The Readings In A List
- Step 4: Count What Was Saved
- Step 5: Find The Lowest And Highest Slopes
- Step 6: Find The Steepest Tilt In Either Direction
- Step 7: Stop Sampling At A Large Tilt
- Step 8: Print Rows For A Graph
- Step 9: Test A Motor Pulse Separately
- Step 10: Add Short Rides Between Readings
- Step 11: Compare Two Routes
- Teacher Check
Project 22: Smart Bike Route Data Recorder
You need: a LEGO SPIKE Prime set, the Smart Bike (Bike and Biker), motors C and E, support wheels, the SPIKE App Python editor, and a wide, low, stable slope board on the floor. Finish Project 17's axis and sign calibration first.
Record a short series of slope readings, find the lowest and highest values, then compare two routes. Start with stationary tests. Powered movement comes only after your teacher checks the readings. This records tilt; it does not measure electrical power, energy, or distance.
Create a fresh project. Every variable needed here is defined below. Gray lines are already there; dark lines are the edit. Stop the program before each change.
Step 1: Read One Angle
Name your project Bike Route Recorder and type:
from hub import motion_sensor
import runloop
tilt_axis = 1
async def main():
for sample in range(20):
angle = motion_sensor.tilt_angles()[tilt_axis] / 10
print(angle)
await runloop.sleep_ms(250)
runloop.run(main())
Use your tested tilt_axis from Project 17, which may be 2 instead of 1. Run the program with the motors still. Gently raise and lower the front. It takes 20 readings with a quarter-second pause after each. That is roughly five seconds, plus the time spent reading and printing.
Step 2: Calibrate Level And Sign
Add your tested sign beside the axis. Use 1 or -1 so front-up will be positive:
tilt_axis = 1
tilt_sign = 1
Replace main():
async def main():
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
for sample in range(20):
angle = motion_sensor.tilt_angles()[tilt_axis] / 10
slope = (angle - baseline) * tilt_sign
print(slope)
await runloop.sleep_ms(250)
Run the program starting on level ground. Front-up should be positive, back-up negative, and level close to zero. Stop and fix the axis or sign if those observations disagree. Do not move the baseline assignment inside the loop.
Step 3: Save The Readings In A List
Insert an empty list after the baseline:
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
samples = []
for sample in range(20):
Add one line immediately after calculating slope:
slope = (angle - baseline) * tilt_sign
samples.append(slope)
print(slope)
After the loop, at four spaces, add:
print("saved readings")
print(samples)
Run the program. You should see individual readings while it runs and a bracketed list at the end. append() adds one value to the end of the list. The list starts fresh each run.
Step 4: Count What Was Saved
Add this after print(samples), outside the loop:
print("sample count")
print(len(samples))
Run the program. The count should be 20. len() gives the number of saved values, not the largest angle. Keep the loop count at 20 for the rest of the lesson.
Step 5: Find The Lowest And Highest Slopes
Add these lines after the sample count:
if len(samples) > 0:
print("lowest slope")
print(min(samples))
print("highest slope")
print(max(samples))
Run the program while gently tilting both ways. A downhill reading can be the lowest because it is negative. max() finds the most positive reading, not necessarily the steepest tilt. The if avoids asking for a minimum or maximum of an empty list.
Step 6: Find The Steepest Tilt In Either Direction
Add this inside the summary's if, after print(max(samples)):
steepest = max(abs(min(samples)), abs(max(samples)))
print("steepest tilt size")
print(steepest)
For example, if the lowest reading is -12 and the highest is 7, the steepest size is 12. abs() removes each sign before the outer max() compares their sizes.
Run the program. Make the downhill tilt larger than the uphill tilt. Check that the steepest value follows the larger size, not just the positive reading.
Step 7: Stop Sampling At A Large Tilt
Add this definition beside your other top-level settings:
tilt_sign = 1
tilt_limit = 20
Keep your own tested sign. Inside the loop, insert this after print(slope) and before the pause:
print(slope)
if abs(slope) > tilt_limit:
print("tilt limit: recording ended")
break
await runloop.sleep_ms(250)
Run the program and gently tilt past 20 degrees while holding the stationary bike by its frame. break exits the recording loop, then the summary still runs. The out-of-range reading was appended before the check, so it appears in the count and summary. Exactly 20 does not pass >.
Step 8: Print Rows For A Graph
Add this after the whole summary block, at four spaces inside main():
print("sample,slope_degrees")
for index in range(len(samples)):
print(str(index) + "," + str(samples[index]))
Run the program. index starts at zero; samples[index] selects that saved reading. str() converts each number to text, and + joins it with a comma into one row. Copy only the header and rows into a spreadsheet if available, or plot the values on paper.
Use sample number on the horizontal axis and slope degrees on the vertical axis. These rows are not exact timestamps or distance measurements.
Step 9: Test A Motor Pulse Separately
Save the recorder. Create a separate Python project named Bike Recorder Motor Test:
from hub import port
import motor_pair
import runloop
bike_motor = port.C
rider_motor = port.E
async def main():
motor_pair.pair(motor_pair.PAIR_1, bike_motor, rider_motor)
await motor_pair.move_for_time(
motor_pair.PAIR_1, 200, 0, velocity=120)
motor_pair.stop(motor_pair.PAIR_1)
runloop.run(main())
bike_motor names the bike motor on C; rider_motor names the rider motor on E. Put these variables after the imports so commands can use names instead of port letters.
Run the program once on a clear, level floor. It should make a short forward pulse and stop. Have your teacher check the build, direction, support wheels, and room for 20 pulses before combining the programs. Fix a failed motor test before continuing.
Step 10: Add Short Rides Between Readings
Reopen Bike Route Recorder. Replace the first import and add the motor-pair import. Keep import runloop with the imports. Add the two port variables just below all imports, before tilt_axis:
from hub import motion_sensor, port
import motor_pair
import runloop
bike_motor = port.C
rider_motor = port.E
At the start of main(), before the baseline, insert:
async def main():
motor_pair.pair(motor_pair.PAIR_1, bike_motor, rider_motor)
motor_pair.stop(motor_pair.PAIR_1)
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
Replace only the loop's await runloop.sleep_ms(250) with these lines. Keep them after the tilt check and its break:
await motor_pair.move_for_time(
motor_pair.PAIR_1, 200, 0, velocity=120)
await runloop.sleep_ms(50)
Add an explicit stop after the loop, before print("saved readings"):
motor_pair.stop(motor_pair.PAIR_1)
print("saved readings")
Before you run: Start level, with the whole short route clear. Use only gentle slopes and a level starting approach. Keep the support wheels and the floor underneath the board. Do not touch hub buttons on a moving bike.
Run the program. It records an angle, checks it, then makes a short pulse. At most 20 pulses run; a large tilt ends the loop before the next pulse. The guard cannot detect a change during a pulse and is not continuous tip protection. Use the app's stop control if needed.
Step 11: Compare Two Routes
Run once on a flat route. Then run on a gentle route with a level start and a low rise. Keep the speed, pulse length, sample count, axis and sign unchanged. Recalibrate by starting each run level.
Save the printed rows from each run and sketch or graph them. Mark the highest and lowest values. A single reading is a sample: a bump between samples might be missed. The last reading happens before the last pulse; this is a series of sampled positions, not a continuous trace of every part of the ride.
Fix it: If every result is near zero on a clear slope, check the chosen axis. If uphill is negative, check the sign. If the list only contains one value, check that samples = [] is above the loop. If the motor still moves after a guard message, check that the movement is below break and outside the if block.
Teacher Check
Show your teacher:
- level, uphill and downhill readings with the correct signs
- the list, its count, its lowest/highest readings and steepest size
- a stationary test where the tilt guard shortens the recording
- two sets of graph rows from different routes
- why sample number is neither exact elapsed time nor distance
Save and stop the program. Your data describes orientation; do not label requested motor speed or tilt as measured electrical power.