In this project, we will show you how to control the LEGO® Technic Cat D11 Bulldozer (42131) with the Powered Up (Train) Remote.

This is a fun way to drive the bulldozer around without having to look at your phone screen all time time. You’ll also use the remote to switch between the functions of the blade, ripper, and ladder.

Driving the LEGO® Technic Cat D11 Bulldozer (42131) with the Powered Up Train Remote.
Driving the LEGO® Technic Cat D11 Bulldozer (42131) with the Powered Up Train Remote.


To follow this project, you will need the following:

Understanding the code

The program consists of two main tasks that run in parallel. The first task handles the driving and steering of the bulldozer, as well as powering the function motor.

The second task controls the position of the motor that selects which function is being operated. The details are included as comments in the program below.

When the program begins, it resets the function selector to the zero position, corresponding to the blade up and down movement. You can select any function by pressing the green button along with one of the gray buttons. This will change the remote light and switch the function selector as follows:

  • Left + (blue): Moves the blade up and down.
  • Left − (red): Tilts the blade back and forth.
  • Right + (yellow): Moves the ladder up and down.
  • Right − (green): Moves the ripper up and down.

Once the function is selected, use the red buttons to control the motor that powers the selected function.

Use the gray buttons to control the tracks. You can change the drive acceleration in the main program to a lower value to make the vehicle start and stop driving more gradually. You can also change the speed values corresponding the left and right motor blocks.

This program lets you drive the LEGO® Technic Cat D11 Bulldozer (42131)
  with the Powered Up Remote.
This program lets you drive the LEGO® Technic Cat D11 Bulldozer (42131) with the Powered Up Remote.

Running the Pybricks program

This project uses Pybricks on your LEGO hub. Pybricks makes your creations come alive and helps you unlock the full potential of your LEGO Technic, City, MINDSTORMS, BOOST, or Spike sets.

If you haven’t already, install Pybricks on your hub as shown below, or check out our getting started guide for more details. You can go back to the LEGO firmware and apps at any time.

Install the Pybricks firmware.

Now import the program you downloaded earlier, as shown below. Click to connect your hub and ▶ to start!

Import a Pybricks Code project.

You can run imported block programs even if you’re not signed up. This is a great way to try out Pybricks and see how it works.

The bulldozer in action

When you’re ready, you can drive the bulldozer as shown below. Make sure to turn on the remote when you start the bulldozer program, so that the hub can find it.

Running it as a Python program

You can also run this project as a Python (MicroPython) program. The following code was generated from the block program above. To run it, create a new empty Python program in Pybricks and copy the code into it.

from pybricks.parameters import Button, Color, Direction, Port, Stop
from pybricks.pupdevices import Motor, Remote
from import multitask, run_task, wait

# Set up all devices.
left = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right = Motor(Port.B, Direction.CLOCKWISE)
function = Motor(Port.C, Direction.CLOCKWISE)
switch = Motor(Port.D, Direction.CLOCKWISE)
remote = Remote(timeout=None)

# Initialize variables.
left_end = 0
right_end = 0

async def switch_function(number):
    await wait(1)
    # This function takes care of moving the motor to positions 0, 1, 2, or 3:

    # 0: Blade up/down.
    # 1: Ripper.
    # 2: Ladder.
    # 3: Blade tilt.
    for count in range(5):
        await wait(1)
        # Go to the target angle. But give up if it takes more
        # than 2 seconds, which means it's stuck for now.
        await multitask(switch.run_target(750, number * 90, Stop.COAST), wait(2000), race=True)
        # Let's check if we're on target.
        if abs(switch.angle() - number * 90) <= 10:
            # We are close to the target angle.
            # so we can exit this repeating loop.
        # Otherwise, we're not done yet, so we must be stuck.
        # Let's wiggle the motor around to try to get it unstuck.
        await wait(1000)
        await wait(1000)

async def main1():
    # This main task will handle driving and the motor
    # that powers (not switches) the function gearbox.
    while True:
        await wait(1)
        if Button.CENTER in remote.buttons.pressed():
            # If the center button is pressed to change the switch motor,
            # we stop driving and powering the function motor to be safe.
            # Otherwise drive the motors based on the buttons that are pressed.
            # Use the left/right red buttons for the function motor.
            if Button.LEFT in remote.buttons.pressed():
            elif Button.RIGHT in remote.buttons.pressed():
            # Use the left +/- for the left track.
            if Button.LEFT_PLUS in remote.buttons.pressed():
            elif Button.LEFT_MINUS in remote.buttons.pressed():
            # Use the right +/- for the left track.
            if Button.RIGHT_PLUS in remote.buttons.pressed():
            elif Button.RIGHT_MINUS in remote.buttons.pressed():

async def main2():
    global right_end, left_end
    # This task will handle the motor that switches the function gearbox.
    # First it resets the gearbox by finding the start and end stops.
    await switch.run_until_stalled(500, Stop.COAST, 50)
    right_end = switch.angle()
    await switch.run_until_stalled(-500, Stop.COAST, 50)
    left_end = switch.angle()
    switch.reset_angle((left_end + 270 - right_end) / 2)
    await switch_function(0)
    await remote.light.on(Color.BLUE)
    # Now we run the main loop of operating the switch.
    while True:
        await wait(1)
        # Wait for the center switch to be pressed.
        while not Button.CENTER in remote.buttons.pressed():
            await wait(1)
        # Then select the function based on which other button is pressed.
        if Button.LEFT_PLUS in remote.buttons.pressed():
            # Blade up/down function.
            await switch_function(0)
            await remote.light.on(Color.BLUE)
            while Button.LEFT_PLUS in remote.buttons.pressed():
                await wait(1)
        elif Button.RIGHT_MINUS in remote.buttons.pressed():
            # Ripper function.
            await switch_function(1)
            await remote.light.on(Color.GREEN)
            while Button.RIGHT_MINUS in remote.buttons.pressed():
                await wait(1)
        elif Button.RIGHT_PLUS in remote.buttons.pressed():
            # Ladder function.
            await switch_function(2)
            await remote.light.on(Color.YELLOW)
            while Button.RIGHT_PLUS in remote.buttons.pressed():
                await wait(1)
        elif Button.LEFT_MINUS in remote.buttons.pressed():
            # Blade tilt function.
            await switch_function(3)
            await remote.light.on(Color.RED)
            while Button.LEFT_MINUS in remote.buttons.pressed():
                await wait(1)
            # No other button pressed, so don't switch.

async def main():
    await multitask(main1(), main2())

Python representation of the block program.