I love building stuff with my son, and one of our latest projects (admittedly more apt for me than for him as he’s too young to be building these kits), was the Lego buggy 42124. This is a great kit that is fun to build but it is let down by the fact that it is controlled using a smartphone. I don’t like using smartphone remote controls and I definitely don’t want my son looking at a screen at a young age, so I set out to figure out a way to use a hardware remote control.
Turns out it’s quite easy to do. I used Pybricks to add custom code to the Lego hub, then ordered a Lego remote to pair with the buggy.
The code I used can be found below. I set the left red button to “ludicrous mode” which enables the buggy to function at full speed. Otherwise, I set it to run at 50% speed since my son is too young to control it at max speed, especially indoors. This way we can both use it and have some fun while using the appropriate speeds.
Lego Buggy (42124)
from pybricks.pupdevices import Motor, Remote
from pybricks.parameters import Port, Direction, Stop, Button
from pybricks.hubs import TechnicHub
from pybricks.tools import wait
# Initialize the motors.
steer = Motor(Port.B)
front = Motor(Port.A, Direction.COUNTERCLOCKWISE)
# Connect to the remote.
remote = Remote()
# Initialize the hub.
hub = TechnicHub()
# Read the current settings
old_kp, old_ki, old_kd, _, _ = steer.control.pid()
# Set new values
steer.control.pid(kp=old_kp*4, kd=old_kd*0.4)
# Find the steering endpoint on the left and right.
# The middle is in between.
left_end = steer.run_until_stalled(-200, then=Stop.HOLD)
right_end = steer.run_until_stalled(200, then=Stop.HOLD)
# We are now at the right. Reset this angle to be half the difference.
# That puts zero in the middle.
steer.reset_angle((right_end - left_end)/2)
steer.run_target(speed=200, target_angle=0, wait=False)
# Set steering angle for the buggy
steer_angle = (((right_end - left_end)/2)-5)
print('steer angle:',steer_angle)
# Now we can start driving!
while True:
# Check which buttons are pressed.
pressed = remote.buttons.pressed()
# Choose the steer angle based on the right controls.
if Button.LEFT_PLUS in pressed:
steer.run_target(1400, -steer_angle, Stop.HOLD, False)
elif Button.LEFT_MINUS in pressed:
steer.run_target(1400, steer_angle, Stop.HOLD, False)
else:
steer.track_target(0)
# Top speed controls
top_speed = 50
if Button.LEFT in pressed:
top_speed = 100
# Choose the drive speed based on the left controls.
drive_speed = 0
if Button.RIGHT_PLUS in pressed:
drive_speed += top_speed
if Button.RIGHT_MINUS in pressed:
drive_speed -= top_speed
if Button.RIGHT in pressed:
print('Battery voltage:',(hub.battery.voltage())/1000,"V")
wait(100)
# Apply the selected speed.
front.dc(drive_speed)
# Wait.
wait(10)
Lego Top Gear Rally Car (42109)
I also bought the Lego Top Gear Rally Car (42109) and used similar code with a second remote I bought. Now we can race the cars against each other. I can adjust the speed of each through code to adapt it to our different abilities.
Here’s the code I used on this car. I added some things like changing the remote light buttons and naming the remote so that the car would connect to a specific remote out of the two I have, and thus avoid confusion. I also correspondingly changed the hub’s light color.
from pybricks.pupdevices import Motor, Remote
from pybricks.parameters import Port, Direction, Stop, Button, Color
from pybricks.hubs import TechnicHub
from pybricks.tools import wait
# Initialize the motors.
steer = Motor(Port.B)
front = Motor(Port.D, Direction.COUNTERCLOCKWISE)
# Connect to the remote and set the light on the remote
remote = Remote('topgear', timeout=None)
remote.light.on(Color.RED)
# Print the current name of the remote.
print(remote.name())
# Choose a new name.
remote.name('topgear')
# Initialize the hub.
hub = TechnicHub()
hub.light.on(Color.RED)
# Read the current settings
old_kp, old_ki, old_kd, _, _ = steer.control.pid()
# Set new values
steer.control.pid(kp=old_kp*4, kd=old_kd*0.4)
# Set initial top speed value
top_speed = 100
# Find the steering endpoint on the left and right.
# The middle is in between.
left_end = steer.run_until_stalled(-200, then=Stop.HOLD)
right_end = steer.run_until_stalled(200, then=Stop.HOLD)
# We are now at the right. Reset this angle to be half the difference.
# That puts zero in the middle.
steer.reset_angle((right_end - left_end)/2)
steer.run_target(speed=200, target_angle=0, wait=False)
# Set steering angle for the buggy
steer_angle = (((right_end - left_end)/2)-5)
# Now we can start driving!
while True:
# Check which buttons are pressed.
pressed = remote.buttons.pressed()
# Choose the steer angle based on the right controls.
if Button.LEFT_PLUS in pressed:
steer.run_target(1400, -steer_angle, Stop.HOLD, False)
elif Button.LEFT_MINUS in pressed:
steer.run_target(1400, steer_angle, Stop.HOLD, False)
else:
steer.track_target(0)
# Top speed controls
if Button.LEFT in pressed:
top_speed = 75
if Button.RIGHT in pressed:
top_speed = 100
if ((Button.RIGHT in pressed) and (Button.LEFT in pressed)):
top_speed = 40
# Choose the drive speed based on the left controls.
drive_speed = 0
if Button.RIGHT_PLUS in pressed:
drive_speed -= top_speed
if Button.RIGHT_MINUS in pressed:
drive_speed += top_speed
# Print battery voltage
if Button.RIGHT in pressed:
print('Battery voltage:',(hub.battery.voltage())/1000,"V")
wait(100)
# Apply the selected speed.
front.dc(drive_speed)
# Wait.
wait(10)
Lego 4×4 Extreme Off-Roader (42099)
from pybricks.pupdevices import Motor, Remote
from pybricks.parameters import Port, Direction, Stop, Button
from pybricks.tools import wait
# Initialize the motors.
steer = Motor(Port.C)
front = Motor(Port.A, Direction.COUNTERCLOCKWISE)
rear = Motor(Port.B, Direction.COUNTERCLOCKWISE)
# Lower the acceleration so the car starts and stops realistically.
front.control.limits(acceleration=1000)
rear.control.limits(acceleration=1000)
# Connect to the remote.
remote = Remote()
# Find the steering endpoint on the left and right.
# The middle is in between.
left_end = steer.run_until_stalled(-200, then=Stop.HOLD)
right_end = steer.run_until_stalled(200, then=Stop.HOLD)
# We are now at the right. Reset this angle to be half the difference.
# That puts zero in the middle.
steer.reset_angle((right_end - left_end) / 2)
steer.run_target(speed=200, target_angle=0, wait=False)
# Now we can start driving!
while True:
# Check which buttons are pressed.
pressed = remote.buttons.pressed()
# Choose the steer angle based on the left controls.
steer_angle = 0
if Button.LEFT_PLUS in pressed:
steer_angle -= 75
if Button.LEFT_MINUS in pressed:
steer_angle += 75
# Steer to the selected angle.
steer.run_target(500, steer_angle, wait=False)
# Choose the drive speed based on the right controls.
drive_speed = 0
if Button.RIGHT_PLUS in pressed:
drive_speed += 1000
if Button.RIGHT_MINUS in pressed:
drive_speed -= 1000
# Apply the selected speed.
front.run(drive_speed)
rear.run(drive_speed)
# Wait.
wait(10)
Troubleshooting
If you’re having issues getting the code to work, you can try both editors:
You can also use this code to verify that a connection is being successfully made by your controller with the hub:
from pybricks.hubs import TechnicHub
from pybricks.pupdevices import Remote
from pybricks.parameters import Button, Color
from pybricks.tools import wait
hub = TechnicHub()
# Make the light red while we connect.
hub.light.on(Color.RED)
# Connect to the remote.
my_remote = Remote()
# Make the light green when we are connected.
hub.light.on(Color.GREEN)
while True:
# For any button press, make the light magenta.
# Otherwise make it yellow.
if my_remote.buttons.pressed():
hub.light.on(Color.MAGENTA)
else:
hub.light.on(Color.YELLOW)
wait(10)
Lego 42124 Trike Variant
from pybricks.pupdevices import Motor, Remote
from pybricks.parameters import Port, Direction, Stop, Button
from pybricks.hubs import TechnicHub
from pybricks.tools import wait
# Initialize the motors.
steer = Motor(Port.B)
front = Motor(Port.A, Direction.COUNTERCLOCKWISE)
# Connect to the remote.
remote = Remote()
# Initialize the hub.
hub = TechnicHub()
# Read the current settings
old_kp, old_ki, old_kd, _, _ = steer.control.pid()
# Set new values
steer.control.pid(kp=old_kp*0.5, kd=old_kd*1)
# Find the steering endpoint on the left and right.
# The middle is in between.
left_end = steer.run_until_stalled(-200, then=Stop.HOLD)
right_end = steer.run_until_stalled(200, then=Stop.HOLD)
print('left end:',left_end)
print('right end:', right_end)
# We are now at the right. Reset this angle to be half the difference.
# That puts zero in the middle.
steer.reset_angle((right_end - left_end)/2)
steer.run_target(speed=100, target_angle=0, wait=False)
# Set steering angle for the buggy
steer_angle = (((right_end - left_end)/2)-20)
print('steer angle:',steer_angle)
# Now we can start driving!
while True:
# Check which buttons are pressed.
pressed = remote.buttons.pressed()
# Choose the steer angle based on the right controls.
if Button.LEFT_PLUS in pressed:
steer.run_target(400, -steer_angle, Stop.HOLD, False)
elif Button.LEFT_MINUS in pressed:
steer.run_target(400, steer_angle, Stop.HOLD, False)
else:
current_angle = steer.angle()
deadband = 3 # Define a deadband range of +/- 3 degrees
if current_angle > deadband:
steer.track_target(0)
elif current_angle < -deadband:
steer.track_target(0)
else:
steer.stop()
# Top speed controls
top_speed = 50
if Button.LEFT in pressed:
top_speed = 100
# Choose the drive speed based on the left controls.
drive_speed = 0
if Button.RIGHT_PLUS in pressed:
drive_speed += top_speed
if Button.RIGHT_MINUS in pressed:
drive_speed -= top_speed
if Button.RIGHT in pressed:
print('Battery voltage:',(hub.battery.voltage())/1000,"V")
wait(100)
# Apply the selected speed.
front.dc(drive_speed)
# Wait.
wait(10)
Further Reading
- Brickset – reviews of Lego sets
- Rebrickable – alternate builds
- MOCHub – “my own creation”
- RacingBrick on Youtube – fantastic Lego builds channel
Leave a Reply