STARLEAP / SPIKE PRIME PYTHON / 2026–27
Build 2026.09.22-090630-0400
On this page
- Step 1: Read The Parked Bike
- Step 2: Select Your Tested Tilt Axis
- Step 3: Remember The Parked Angle
- Step 4: Measure Change In Either Direction
- Step 5: Make One Condition
- Step 6: Hear The Alarm
- Step 7: Remember Whether It Is Armed
- Step 8: Start Disarmed And Read The Buttons
- Step 9: Arm Once, From A Fresh Parked Position
- Step 10: Add A Disarm Button
- Step 11: Prove The State Changes
- Teacher Check
Project 21: Smart Bike Security Alarm
You need: a LEGO SPIKE Prime set, the Smart Bike (Bike and Biker), its hub, and the SPIKE App Python editor. Keep the support wheels. Finish the sensor calibration in Project 17 first. This project does not run the motors.
Make a parked-bike alarm. Arm it with the right hub button, gently tilt the bike to trigger one beep, and use the left button to disarm it. It detects a change in tilt, not every possible movement: a bike can move across a floor without changing its tilt.
Stop any driving program before starting. Support the bike by its frame when tilting it. Gray code is already there; dark code is the edit. Keep the final runloop.run(main()) throughout.
Step 1: Read The Parked Bike
Create a fresh Python project named Bike Security Alarm:
from hub import motion_sensor
import runloop
async def main():
while True:
angles = motion_sensor.tilt_angles()
print(angles)
await runloop.sleep_ms(100)
runloop.run(main())
Run the program. The console shows three angles, and the bike stays still. Gently raise the front, then return it to its parked position. Stop the program before editing.
Step 2: Select Your Tested Tilt Axis
Add this assignment below the imports. Use the axis you verified in Project 17: 1 or 2, depending on the hub's mounting. The shown value is an example:
tilt_axis = 1
Replace the two lines that read and print the whole tuple:
angle = motion_sensor.tilt_angles()[tilt_axis] / 10
print(angle)
Run the program and gently raise the front again. The selected angle should change. Dividing by 10 converts decidegrees to degrees. If it barely changes, stop and check the axis before continuing.
Step 3: Remember The Parked Angle
Replace main() with this version:
async def main():
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
while True:
angle = motion_sensor.tilt_angles()[tilt_axis] / 10
change = angle - baseline
print(change)
await runloop.sleep_ms(100)
Park the bike and run the program. It should begin near zero. Gently tilt it and return it to the parked position. baseline is read once before the loop; each new reading is compared with that remembered position.
Step 4: Measure Change In Either Direction
Replace the subtraction line:
change = abs(angle - baseline)
Run the program. Front-up and back-up changes should both produce positive amounts. abs() gives the size of a difference without its sign. We care how far it tilted, not which way.
Step 5: Make One Condition
Add a threshold beside tilt_axis, outside main():
tilt_axis = 1
tilt_limit = 10
Keep your tested axis if it is 2; do not change it just to match the gray example. Replace print(change) inside the loop:
if change > tilt_limit:
print("tilt detected")
else:
print("parked")
Run the program. Gently tilt past the threshold, then return. A change of exactly 10 does not pass >; more than 10 does. Tiny sensor changes around the parked position should not trigger it.
Step 6: Hear The Alarm
Replace the first import:
from hub import motion_sensor, sound
Add a beep under the detection message, at the same indentation:
if change > tilt_limit:
print("tilt detected")
await sound.beep(700, 200, 100)
Run the program. When you hold the bike tilted, the beep repeats because the loop tests the condition again. The beep is 700 Hz, lasts 200 milliseconds, and uses volume 100. Return to the parked position and stop the program.
Step 7: Remember Whether It Is Armed
Add a variable after the baseline, before the loop:
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
armed = True
while True:
Change the condition and add a line after the beep:
if armed and change > tilt_limit:
print("tilt detected")
await sound.beep(700, 200, 100)
armed = False
Remove the old else: and print("parked") lines; they would misleadingly label a disarmed, tilted bike as parked.
Run the program. Tilt beyond the limit and keep holding it. Only one beep should sound. and requires both facts: it is armed, and the change is large. After the beep, False makes later tests fail. Restarting currently arms it again.
Step 8: Start Disarmed And Read The Buttons
Replace the first import and change the existing initial state:
from hub import motion_sensor, sound, button
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
armed = False
Add this test at the beginning of the loop, before angle = ...:
while True:
if button.pressed(button.RIGHT):
armed = True
print("armed")
angle = motion_sensor.tilt_angles()[tilt_axis] / 10
Run the program. Tilting before pressing right should do nothing. Park the bike, tap right, then tilt it after letting go. A held right button can re-arm repeatedly in this temporary version. We will fix that next.
Step 9: Arm Once, From A Fresh Parked Position
Replace just the right-button block with these lines:
if button.pressed(button.RIGHT):
while button.pressed(button.RIGHT):
await runloop.sleep_ms(50)
await runloop.sleep_ms(500)
baseline = motion_sensor.tilt_angles()[tilt_axis] / 10
armed = True
print("armed")
Run the program. Park the bike, press and release right, then keep it still for half a second. When armed prints, gently tilt it. Holding right should no longer repeatedly re-arm the alarm. The fresh baseline records where the bike is parked each time.
Fix it: If pressing the button immediately triggers a beep, keep the frame still during the half-second settling pause. This simple angle subtraction is for gentle tilts near the calibrated pose, not a full upside-down rotation.
Step 10: Add A Disarm Button
Replace the start of the right-button block with a left-first choice. Keep all the existing right-button body below the new elif:
if button.pressed(button.LEFT):
armed = False
elif button.pressed(button.RIGHT):
while button.pressed(button.RIGHT):
await runloop.sleep_ms(50)
Run the program. Arm, press left, release it, then tilt: there should be no beep. Arm again and tilt without pressing left: it should beep once. If both buttons are held when this choice is checked, the left branch wins and does not arm the alarm.
Button input is checked between readings; it is not checked during the short beep or the arming pause. Release right and let arming finish before testing left.
Step 11: Prove The State Changes
Test this sequence without editing code:
| Your action | Expected result |
|---|---|
| Start the program, then tilt | No beep; starts disarmed |
| Park, press and release right, wait | Fresh baseline; armed |
| Gently tilt more than the limit | One beep; becomes disarmed |
| Return and tilt again | No second beep |
| Park and arm again, then press left | Disarmed before a tilt |
Try one small adjustment to tilt_limit, then repeat the tests. Explain the tradeoff between detecting smaller changes and triggering on small bumps.
Teacher Check
Show your teacher the test sequence. Point to where armed becomes True, the two places it becomes False during the loop, and the baseline update. Explain why the alarm cannot detect every theft or movement.
Save your program and stop it. Optional next project: Project 22 records a whole series of slope readings.