Jean Galea

AI, Investing, Health, and Building Businesses

  • Start Here
  • Guides
    • Beginner’s Guide to Investing
    • Cryptocurrencies
    • Stocks
    • P2P Lending
    • Real Estate
  • Blog
  • My Story
  • Projects
  • Community
  • AI Consultancy
  • Search

Adding a Hardware Remote Control to Lego PoweredUP Control+ Sets

Last updated: April 15, 2026Leave a Comment

lego buggy

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.

Youtube video

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 Go Kart (42109 variant)

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.GREEN)

# Print the current name of the remote.
print(remote.name())

# Choose a new name.
remote.name('kart')

# Initialize the hub.
hub = TechnicHub()
hub.light.on(Color.GREEN)

# 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)

Lego 42099 Trike (MOC-76580)

This is an alternate build of the 42099 using the MOC-76580 Trike instructions by poljvd. It’s a three-wheeled trike with an articulated steering mechanism — Motor C pivots the rear chassis relative to the front fork to steer, rather than turning the front wheel directly. The front wheel is unpowered and free-spinning.

Since the articulated joint has a high gear ratio, steering uses power-based control (the motor spins while the button is held) rather than position-based targeting.

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

# Motor A+B = rear drive (both wheels)
# Motor C   = steering (articulated rear mechanism)
steer = Motor(Port.C)
drive_a = Motor(Port.A, Direction.CLOCKWISE)
drive_b = Motor(Port.B, Direction.CLOCKWISE)

# Connect to any available remote.
remote = Remote(timeout=None)
remote.light.on(Color.YELLOW)
remote.name('trike')
print('Remote connected:', remote.name())

# Initialize the hub.
hub = TechnicHub()
hub.light.on(Color.YELLOW)

# Steering power (0-100). Adjust if too fast/slow.
steer_power = 80

# Main control loop.
while True:
    pressed = remote.buttons.pressed()

    # STEERING with LEFT +/- buttons.
    if Button.LEFT_PLUS in pressed:
        steer.dc(-steer_power)
    elif Button.LEFT_MINUS in pressed:
        steer.dc(steer_power)
    else:
        steer.stop()

    # DRIVE with RIGHT +/- buttons.
    drive_speed = 0
    if Button.RIGHT_PLUS in pressed:
        drive_speed += 100
    if Button.RIGHT_MINUS in pressed:
        drive_speed -= 100

    # BATTERY VOLTAGE on RIGHT center button.
    if Button.RIGHT in pressed:
        print('Battery voltage:', hub.battery.voltage() / 1000, 'V')
        wait(100)

    # Apply drive speed to both rear motors.
    drive_a.dc(drive_speed)
    drive_b.dc(drive_speed)

    wait(10)

Troubleshooting

If you’re having issues getting the code to work, you can try both editors:

  • beta.pybricks.com
  • code.pybricks.com

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)

Lego 42160 Audi e-tron


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

# --- Motor setup for Audi RS Q e-tron (42160) ---
# Adjust ports if your wiring is different.
steer = Motor(Port.D)  # steering motor

# Both drive motors. If the car drives backwards, flip the sign of drive_speed
# below or change one/both to Direction.COUNTERCLOCKWISE.
front = Motor(Port.A, Direction.CLOCKWISE)
rear  = Motor(Port.B, Direction.CLOCKWISE)

# Hub + remote
hub = TechnicHub()
remote = Remote(timeout=None)

# Set both lights to a supported color
hub.light.on(Color.MAGENTA)
remote.light.on(Color.MAGENTA)

# --- Steering PID tweak (same idea as your buggy) ---
old_kp, old_ki, old_kd, _, _ = steer.control.pid()
steer.control.pid(kp=old_kp * 4, kd=old_kd * 0.4)

# --- Auto-center steering using the end stops ---

# Find the steering endpoint on the left and right.
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 end.
# The middle is half the difference between right and left.
center_offset = (right_end - left_end) / 2

# Define "0" as the middle by setting the current angle to center_offset.
steer.reset_angle(center_offset)

# Now command the motor to go to target 0 (which is the middle).
steer.run_target(speed=200, target_angle=0, wait=False)

# How far we can safely steer from center (small margin from hard stops).
steer_angle = center_offset - 5
if steer_angle < 0:
    steer_angle = 0  # safety clamp

print("left_end:", left_end)
print("right_end:", right_end)
print("center_offset:", center_offset)
print("steer_angle used:", steer_angle)

# --- Main control loop ---
while True:
    pressed = remote.buttons.pressed()

    # Steering with LEFT +/- buttons.
    if Button.LEFT_PLUS in pressed:
        steer.run_target(1400, -steer_angle, Stop.HOLD, wait=False)
    elif Button.LEFT_MINUS in pressed:
        steer.run_target(1400, steer_angle, Stop.HOLD, wait=False)
    else:
        # Hold center.
        steer.track_target(0)

    # Top speed controls
    top_speed = 100
    if Button.LEFT in pressed:
        top_speed = 60  # slow mode

    # Drive speed with RIGHT +/- buttons.
    drive_speed = 0
    if Button.RIGHT_PLUS in pressed:
        drive_speed += top_speed
    if Button.RIGHT_MINUS in pressed:
        drive_speed -= top_speed

    # Battery voltage on RIGHT center button.
    if Button.RIGHT in pressed:
        print("Battery voltage:", hub.battery.voltage() / 1000, "V")
        wait(100)

    # Apply the selected speed to BOTH drive motors.
    front.dc(drive_speed)
    rear.dc(drive_speed)

    wait(10)

Lego 51515 Robot Inventor

You can also use the same principles to connect to the Lego 51515 Robot Inventor hub. There are many things you can build with that kit, but below you can find the general framework for connecting the Lego remote to the 51515 hub:

from pybricks.hubs import InventorHub
from pybricks.pupdevices import Remote
from pybricks.parameters import Button
from pybricks.tools import wait

# Initialize the hub and the remote
hub = InventorHub()
remote = Remote()

while True:
    # Check if a button on the remote is pressed
    pressed = remote.buttons.pressed()

    if Button.LEFT in pressed:
        # Code for when the left button is pressed
        pass
    elif Button.RIGHT in pressed:
        # Code for when the right button is pressed
        pass
    
    # Delay to avoid rapid polling
    wait(10)

Using a PS4 Controller via BrickController2

BrickController2 is an app that lets you control LEGO creations with game controllers. To use a PS4 controller with BrickController2, follow these steps:

  1. Pair the PS4 Controller with Your Device:a. Put the PS4 controller in pairing mode by holding down the “Share” and “PS” buttons at the same time until the light bar starts flashing.b. On your device (whether it’s an Android or iOS), open the Bluetooth settings.c. Look for the PS4 controller in the list of available devices. It might show up as “Wireless Controller” or something similar.

    d. Tap on the PS4 controller’s name in the list to pair it with your device. You might be prompted to confirm the pairing.

  2. Open BrickController2:a. Make sure you’ve set up your LEGO devices in the app.b. In the “Controllers” section, you should see the PS4 controller listed if it’s connected. If not, try disconnecting and reconnecting the PS4 controller via Bluetooth.
  3. Assign Controls:a. Once the PS4 controller is recognized in BrickController2, you can start assigning its buttons to control specific functions of your LEGO creation. This can include movement directions, rotations, or other actions.
  4. Play and Control:Once you’ve set up the controls, you can use your PS4 controller to play with and control your LEGO models. Ensure the LEGO devices and the controller are both connected and in range for optimal performance.

Further Reading

  • Brickset – reviews of Lego sets
  • Rebrickable – alternate builds
    • GoPro mod
  • MOCHub – “my own creation”
  • RacingBrick on Youtube – fantastic Lego builds channel

Filed under: Hobbies, Tech

About Jean Galea

I build things on the internet and write about AI, investing, health, and how to live well. Founder of AgentVania and the Good Life Collective.

Leave a Reply Cancel reply

Thanks for choosing to leave a comment. Please keep in mind that all comments are moderated according to our comment policy, and your email address will NOT be published. Please Do NOT use keywords or links in the name field.

Latest Padel Match

Jean Galea

Investor | Dad | Global Citizen | Athlete

Follow @jeangalea

  • My Padel Journey
  • Affiliate Disclaimer
  • Cookies
  • Contact

Copyright © 2006 - 2026