Examples

The examples/ folder in the repository contains runnable MicroPython scripts. Highlights below — each is a full .py file you can copy to the hub’s fs/ directory.

Distance sensor

examples/distanceSensor.py
import hub, lpf2, time

print("Distance Sensor Example")
print("Connect a distance sensor to port B")

while True:
    port = hub.ports.B
    if (port.isDeviceConnected()
            and port.getDeviceType() == lpf2.device_type.TECHNIC_DISTANCE_SENSOR):
        print(port.getValueStr(0))
    time.sleep(0.1)

Dumb-car (two motors + remote)

examples/dumbCar.py
import lpf2, hub, time

print("Dumb Car Example")
print("Connect a 'dumb' motor to port A and a distance sensor to port D")

while True:
    motor = hub.ports.A
    sensor = hub.ports.D
    if not (motor.isDeviceConnected()
            and motor.getDeviceType() == lpf2.device_type.TRAIN_MOTOR):
        time.sleep_ms(50)
        continue
    if not (sensor.isDeviceConnected()
            and sensor.getDeviceType() == lpf2.device_type.TECHNIC_DISTANCE_SENSOR):
        time.sleep_ms(50)
        continue

    distance = sensor.getValue(0, 0)
    if distance <= -0.1:
        distance = float('inf')

    if distance < 40:
        motor.startPower(-10)
    else:
        motor.startPower(50)

    time.sleep(0.01)

Port expander

examples/expander.py
import lpf2, hub, time

hub.ports.LED.setRgbColorIdx(lpf2.color.BLUE)
portA = hub.ports.A
print("Waiting for expander on port A ...")

while True:
    dev = portA.device()
    if isinstance(dev, lpf2.devices.port_expander):
        exp1 = dev
        print("Expander found.")
        break
    time.sleep_ms(100)

portAB = exp1.getPort(lpf2.port_expander.port_num.B)
print("Waiting for distance sensor on port AB ...")

while True:
    dev = portAB.device()
    if isinstance(dev, lpf2.devices.distance_sensor):
        print("distance sensor found.")
        dev.setLight(100, 0, 0, 100)
        print("Distance: ", dev.getDistance(), " cm")
        break
    time.sleep_ms(100)

portAC = exp1.getPort(lpf2.port_expander.port_num.C)
print("Waiting for encoder motor on port AC ...")

while True:
    dev = portAC.device()
    if isinstance(dev, lpf2.devices.encoder_motor):
        print("motor found.")
        dev.startSpeedForDegrees(720, 100, 100, 0)
        break
    time.sleep_ms(100)

portAA = exp1.getPort(lpf2.port_expander.port_num.A)
print("Waiting for expander on port AA ...")

while True:
    dev = portAA.device()
    if isinstance(dev, lpf2.devices.port_expander):
        print("Expander found.")
        portAAA = dev.getPort(lpf2.port_expander.port_num.A)
        break
    time.sleep_ms(100)

print("Waiting for led on port AAA ...")

while True:
    if portAAA.getDeviceType() == lpf2.device_type.HUB_LED:
        print("led found.")
        portAAA.setRgbColorIdx(lpf2.color.PURPLE)
        time.sleep(5)
        portAAA.setRgbColorIdx(lpf2.color.BLACK)
        print("Test finished")
        break
    time.sleep_ms(100)

IMU

examples/imu.py
import hub, lvgl as lv, time

scr = lv.screen_active()
scr.clean()
scr.set_style_bg_color(lv.color_black(), 0)

title = lv.label(scr)
title.set_text("IMU")
title.set_style_text_color(lv.color_white(), 0)
title.align(lv.ALIGN.TOP_MID, 0, 2)

attitude = lv.label(scr)
attitude.set_style_text_color(lv.color_white(), 0)
attitude.align(lv.ALIGN.TOP_LEFT, 4, 20)

accel = lv.label(scr)
accel.set_style_text_color(lv.color_white(), 0)
accel.align(lv.ALIGN.TOP_LEFT, 4, 74)

while True:
    p = hub.imu.pitch
    y = hub.imu.yaw
    r = hub.imu.roll
    a = hub.imu.acceleration
    attitude.set_text(
        "pitch {:>6.1f}\nyaw   {:>6.1f}\nroll  {:>6.1f}".format(p, y, r)
    )
    accel.set_text(
        "ax {:>6.0f} mG\nay {:>6.0f} mG\naz {:>6.0f} mG".format(a.x, a.y, a.z)
    )
    lv.timer_handler()
    time.sleep(0.1)

LCD (LVGL)

examples/lcd.py
import hub, lvgl as lv, time

hub.lcd.init()
hub.lcd.on()
scr = lv.screen_active()
btn = lv.button(scr)
btn.center()
lv.label(btn).set_text("hi")

# Idle loop — keeps the script alive so the LCD stays visible.
# Hold centre 2 s to stop.
while True:
    lv.timer_handler()
    time.sleep_ms(100)

BLE remote hub

examples/remote.py
"""Remote hub scanner: mirror status + color readings to the LCD instead of serial."""

import lpf2, hub, lvgl as lv, time

_COLOR_NAMES = {
    lpf2.color.BLACK: "black",
    lpf2.color.PINK: "pink",
    lpf2.color.PURPLE: "purple",
    lpf2.color.BLUE: "blue",
    lpf2.color.LIGHTBLUE: "lightblue",
    lpf2.color.CYAN: "cyan",
    lpf2.color.GREEN: "green",
    lpf2.color.YELLOW: "yellow",
    lpf2.color.ORANGE: "orange",
    lpf2.color.RED: "red",
    lpf2.color.WHITE: "white",
    lpf2.color.NONE: "none",
}

_hub = lpf2.hub()
_port = None
_dev = None
_done = False

scr = lv.screen_active()
scr.set_style_bg_color(lv.color_hex(0x000000), 0)
scr.clean()

status = lv.label(scr)
status.set_style_text_color(lv.color_hex(0xFFFFFF), 0)
status.align(lv.ALIGN.TOP_MID, 0, 4)

detail = lv.label(scr)
detail.set_style_text_color(lv.color_hex(0xFFFF00), 0)
detail.align(lv.ALIGN.CENTER, 0, 8)


def _show(msg, val=""):
    status.set_text(msg)
    detail.set_text(val)


_show("Waiting for HUB")

while not _done:
    lv.timer_handler()
    if not _hub.isConnected():
        if _hub.isConnecting():
            _show("Connecting...")
            if not _hub.connectHub():
                _show("Connect failed")
                time.sleep_ms(500)
        elif not _hub.isScanning():
            _show("Scanning...")
            _hub.init()
            time.sleep(1)
        else:
            _show("Scanning...")
            time.sleep_ms(100)
        continue

    if _port is None:
        _show("Connected")
        time.sleep_ms(100)
        port = _hub.getPort(lpf2.port_num.controlplus.LED)
        if isinstance(port, lpf2.port):
            port.setRgbColorIdx(lpf2.color.GREEN)
        _port = port
        continue

    A = _hub.getPort(lpf2.port_num.controlplus.A)
    if _dev is None:
        if A is None:
            _show("Port A missing")
            raise ValueError("Port A not found.")
        if A.isDeviceConnected():
            _dev = A.device()
            _show("Wait CYAN")
        else:
            time.sleep_ms(100)
        continue

    if isinstance(_dev, lpf2.devices.color_sensor):
        idx = _dev.getColorIdx()
        if idx == lpf2.color.CYAN:
            _show("Done.")
            _done = True
        else:
            _show("Color", _COLOR_NAMES.get(idx, str(idx)))
            time.sleep_ms(100)
    else:
        _done = True

Grove I2C (MPU-6050)

examples/grove_i2c_mpu6050.py
"""MPU-6050 accelerometer over the Grove I2C connector.

Reads WHO_AM_I, wakes the chip, then streams 6-byte accel bursts.
Uses the shared internal I2C bus via ``hub.i2c`` — coexists with the
PCA9685, SC16IS750, BNO085 and everything else already on Wire1.
"""

import hub, struct, time

ADDR = 0x68           # MPU-6050 / MPU-6500 default (AD0 low)
WHO_AM_I = 0x75
PWR_MGMT_1 = 0x6B
ACCEL_XOUT_H = 0x3B

print("scanning I2C bus...")
devs = hub.i2c.scan()
print("addresses:", [hex(a) for a in devs])
if ADDR not in devs:
    print(f"MPU-6050 not found at {hex(ADDR)}")
else:
    who = hub.i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0]
    print(f"WHO_AM_I: 0x{who:02X}")
    hub.i2c.writeto_mem(ADDR, PWR_MGMT_1, b"\x00")

    _buf = bytearray(6)
    _last_print = 0

    while True:
        now = time.ticks_ms()
        if time.ticks_diff(now, _last_print) >= 200:
            _last_print = now
            hub.i2c.readfrom_mem_into(ADDR, ACCEL_XOUT_H, _buf)
            ax, ay, az = struct.unpack(">hhh", _buf)
            print(f"accel  x={ax:+6}  y={ay:+6}  z={az:+6}")
        time.sleep_ms(10)

NeoPixel on a repurposed LPF2 port

Drives a WS2812 strip off port D’s ID1 line by first disabling the port so LPF2 releases the pin, then handing it to the standard machine.Pin + neopixel.NeoPixel driver.

examples/neopixel.py
import hub, machine, time
from neopixel import NeoPixel

_hue = 0.0


def hsv_to_rgb(h, s, v):
    """
    h: hue, 0.0-1.0 (wraps around, so any float works via mod)
    s: saturation, 0.0-1.0
    v: value/brightness, 0.0-1.0
    returns: (r, g, b) each 0-255
    """
    h = h % 1.0
    i = int(h * 6.0)  # always 0-5 since h is 0.0-1.0
    f = (h * 6.0) - i
    p = v * (1.0 - s)
    q = v * (1.0 - s * f)
    t = v * (1.0 - s * (1.0 - f))

    if i == 0:
        r, g, b = v, t, p
    elif i == 1:
        r, g, b = q, v, p
    elif i == 2:
        r, g, b = p, v, t
    elif i == 3:
        r, g, b = p, q, v
    elif i == 4:
        r, g, b = t, p, v
    else:
        r, g, b = v, p, q

    return (int(r * 255), int(g * 255), int(b * 255))


def draw_rainbow(strip, start_hue=0.0, hue_increment=0.125, saturation=1.0, value=1.0):
    """
    Fills `strip` (a NeoPixel-like object) with a rainbow gradient.

    start_hue: hue of pixel 0, 0.0-1.0
    hue_increment: how much hue advances per pixel (wraps automatically)
    saturation, value: constant across the strip, 0.0-1.0
    """
    n = strip.n if hasattr(strip, "n") else len(strip)
    for i in range(n):
        hue = start_hue + i * hue_increment
        r, g, b = hsv_to_rgb(hue, saturation, value)
        strip[i] = (r, g, b)
    strip.write()


hub.ports.D.disable()
leds = NeoPixel(machine.Pin(hub.board.PORT_D_ID_1), 8)


@hub.buttons.on("center")
def on_exit():
    hub.ports.D.disable(False)
    hub.exit()


while True:
    draw_rainbow(leds, start_hue=_hue, hue_increment=0.01, saturation=1.0, value=0.2)
    _hue += 0.01  # animate — remove this line for a static rainbow

Why hub.ports.D.disable() first

An LPF2 port owns its four pins (ID1, ID2, PWM1, PWM2). While the port is live the C++ side is doing three things on them:

  • driving the H-bridge PWM channels (PORT_D_PWM_1 / PORT_D_PWM_2),

  • toggling the ID lines between analog-ID probing and UART TX/RX,

  • polling the LPF2 UART on every update() call.

If a MicroPython driver grabs one of those pins while the port is still active, the two drivers fight for the pad — PWM keeps flipping it, the UART peripheral keeps re-configuring it, and any output the foreign driver writes gets stomped. WS2812 timing tolerates none of that, so the strip either shows garbage or stays dark.

disable() (implemented in lib/Lpf2/src/Lpf2/Local/PortProcess.cpp) is the release step:

  • turns the PCA9685 PWM channels off,

  • calls uartPinsOff() on the LPF2 serial, releasing ID1/ID2 from the UART peripheral,

  • stops polling — subsequent update() calls are no-ops until disable(False) re-enables the port.

Once disabled, the pin is a plain GPIO and any driver (machine.Pin, neopixel, bit-banged I2C, RMT, …) can take it over. The example uses PORT_D_ID_1 (see hub.board), but any of the four port pins works — pick whichever your wiring exposes.

Re-enabling the port

To hand the pin back to LPF2 later:

hub.ports.D.disable(False)

Note that resetDevice() runs on re-enable, so the port re-negotiates analog-ID and re-attaches whatever device is plugged in. Anything still holding a machine.Pin on those pads will now be fighting the port again — drop the foreign driver first.