Pybricks is excellent. Through a web-based IDE, that supports both Python and block-based programming (costs extra, compiling the blocks down into Python, uses Google's Blockly), you can program your Lego hubs and motors.
The household winner was controlling the Lego remote-controlled cars (e.g., Off-Road Buggy) with an Xbox controller.
Some notes:
- Safari doesn't work but Chrome does.
- The IDE first prompts you to flash the firmware of the controllers over Bluetooth, and then you connect to the hub and run a program on it.
- The MicroPython seems to be running on the hub itself. I've not figured out the exact memory limitations, but a naive "macro record-replay" program ran out of memory if the macro was too long.
- There's a
car
abstraction that takes as input the steering and drive motors, and lets you implement a car more simply.
Here's the Xbox-controlled car with a record-replay capability that runs out of memory to give you a sense:
from pybricks.iodevices import XboxController
from pybricks.parameters import Direction, Port, Button
from pybricks.pupdevices import Motor
from pybricks.robotics import Car
from pybricks.tools import wait
from micropython import mem_info
# Set up all devices.
read = Motor(Port.D, Direction.CLOCKWISE)
steer = Motor(Port.B, Direction.CLOCKWISE)
car = Car(steer, read)
controller = XboxController()
memory = []
is_recording = False
# Drive with controller triggers, and steer with left joystick.
# Start recording with A, stop with B, replay with X.
while True:
if not is_recording and Button.A in controller.buttons.pressed():
is_recording = True
memory = []
if is_recording and Button.B in controller.buttons.pressed():
is_recording = False
if Button.X in controller.buttons.pressed():
for (power, steer, duration) in memory:
car.drive_power(power)
car.steer(steer)
wait(duration)
power = controller.triggers()[1] - controller.triggers()[0]
steer = controller.joystick_left()[0]
car.drive_power(power)
car.steer(steer)
if is_recording:
if len(memory) > 0:
# Extend previous recording rather than recording something every
# 10ms, as a memory optimization. Perhaps better would be to pre-allocate
# a fixed bufffer.
(previous_power, previous_steer, previous_time) = memory[-1]
if power == previous_power and steer == previous_steer:
memory[-1] = (power, steer, 10 + previous_time)
else:
memory.append( (power, steer, 10) )
else:
memory.append( (power, steer, 10) )
wait(10)