Remote control Gelo

Python code and building instructions for the LEGO MINDSTORMS Robot Inventor Main Models (51515).

Remote control Gelo
Image credit: LEGO


Instructions

This program requires the LEGO Powered Up remote control. The left + and - buttons control the speed and the right + and - buttons control the steering. Either red button will make Gelo stop.

image of LEGO remote

This script makes use of the gelo.py module, so make sure to save that program in Pybricks Code first.

Then save the script below as gelo_remote.py and run it.

When the program starts, the light will turn yellow. This means Gelo is waiting to connect to the remote control. Turn on the remote and it will connect automatically and the light on Gelo will turn green. Then you can press the buttons on the remote to control Gelo.

Program

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

from gelo import Gelo, copy_sign


#######################################################
# State management
#######################################################

_SPEED_MAP = {
    0: 0,
    1: 100,
    2: 300,
    3: 500,
    4: 800,
}

_STEER_MAP = {
    0: 0,
    1: 23,
    2: 45,
    3: 68,
    4: 90,
}


class State:
    def __init__(self):
        self.speed_index = 0
        self.steer_index = 0
        self.timer = StopWatch()

    def speed(self):
        return copy_sign(_SPEED_MAP[abs(self.speed_index)], self.speed_index)

    def steer(self):
        return copy_sign(_STEER_MAP[abs(self.steer_index)], self.steer_index)

    def inc_speed(self):
        if self.speed_index < 4:
            self.speed_index += 1

    def dec_speed(self):
        if self.speed_index > -4:
            self.speed_index -= 1

    def inc_steer(self):
        if self.steer_index < 4:
            self.steer_index += 1

    def dec_steer(self):
        if self.steer_index > -4:
            self.steer_index -= 1


#######################################################
# Helper functions
#######################################################

def pressed_oneshot_gen(remote: Remote):
    """
    Yields a list of buttons that were pressed
    since the last iteration.
    """
    previous = ()

    while True:
        pressed = remote.buttons.pressed()
        oneshot = []

        for b in pressed:
            if b not in previous:
                oneshot.append(b)

        yield oneshot

        previous = pressed


def handle_pressed(buttons: list[Button], state: State) -> bool:
    """
    Updates the state based on any new button presses.

    Returns: True if the state changed, otherwise false.
    """
    # Prefer stopping if multiple buttons are pressed
    # at the same time.
    if Button.LEFT in buttons or Button.RIGHT in buttons:
        state.speed_index = 0
        state.steer_index = 0
        return True

    if Button.LEFT_PLUS in buttons:
        state.inc_speed()
        return True

    if Button.LEFT_MINUS in buttons:
        state.dec_speed()
        return True

    if Button.RIGHT_PLUS in buttons:
        state.inc_steer()
        return True

    if Button.RIGHT_MINUS in buttons:
        state.dec_steer()
        return True

    return False


def update_display(hub: InventorHub, state: State):
    """
    Updates the display on the hub based on the current state.
    """
    i = state.speed_index
    j = state.steer_index

    if i == 0 and j == 0:
        pulse = state.timer.time() // 10 % 200

        if pulse > 100:
            pulse = 200 - pulse

        half_pulse = pulse // 2

        hub.display.pixel(0, 2, 0)
        hub.display.pixel(1, 2, half_pulse)
        hub.display.pixel(3, 2, half_pulse)
        hub.display.pixel(4, 2, 0)

        hub.display.pixel(2, 2, pulse)

        hub.display.pixel(2, 0, 0)
        hub.display.pixel(2, 1, half_pulse)
        hub.display.pixel(2, 3, half_pulse)
        hub.display.pixel(2, 4, 0)
    else:
        # speed indication
        hub.display.pixel(0, 2, 100 if i > 3 else (50 if i > 2 else 0))
        hub.display.pixel(1, 2, 100 if i > 1 else (50 if i > 0 else 0))
        hub.display.pixel(3, 2, 100 if i < -1 else (50 if i < 0 else 0))
        hub.display.pixel(4, 2, 100 if i < -3 else (50 if i < -2 else 0))

        # center pixel
        hub.display.pixel(2, 2, 100)

        # steering indication
        hub.display.pixel(2, 0, 100 if j > 3 else (50 if j > 2 else 0))
        hub.display.pixel(2, 1, 100 if j > 1 else (50 if j > 0 else 0))
        hub.display.pixel(2, 3, 100 if j < -1 else (50 if j < 0 else 0))
        hub.display.pixel(2, 4, 100 if j < -3 else (50 if j < -2 else 0))


def idle_gen():
    """
    Yields forever (doesn't do anything).
    """
    while True:
        yield


def get_action(gelo: Gelo, state: State):
    """
    Gets an action based on the current state.
    """
    if state.speed_index == 0:
        return idle_gen()

    return gelo.walk_gen(state.speed(), state.steer())


#######################################################
# Main program
#######################################################


with Gelo() as gelo:
    # yellow indicates we are waiting for remote
    gelo.hub.light.on(Color.YELLOW)

    remote = Remote()

    # green indicates that remote is connected
    gelo.hub.light.on(Color.GREEN)

    # inital state
    state = State()
    action_iter = idle_gen()
    pressed_iter = pressed_oneshot_gen(remote)

    while True:
        # when a button is pressed, select a new action
        if handle_pressed(next(pressed_iter), state):
            action_iter = get_action(gelo, state)

        # update the outputs based on the current state
        update_display(gelo.hub, state)
        next(action_iter)

        wait(10)


This project was submitted by The Pybricks Team.