A lightweight safety and diagnostics layer for PUPRemote communication between LEGO Pybricks hubs and external MicroPython devices.
Developed by Astrogenius Team from Brazil
Maintained by Luan Veras
Instagram: @astrogenius.team
AstroPUP is a small helper layer built on top of PUPRemote.
It is designed to make communication between a LEGO hub running Pybricks and an external MicroPython device safer, easier to debug, and more reliable during robotics projects and competitions.
AstroPUP does not replace PUPRemote or LPF2.
It adds a higher-level layer for:
- safe calls
- safe processing
- startup diagnostics
- remote mode validation
- call statistics
- last good response tracking
- optional heartbeat / stale-data detection
AstroPUP is intended for this kind of setup:
External MicroPython Device <--> LEGO Hub running Pybricks
Example external devices:
- LMS-ESP32
- ESP32
- OpenMV
- RP2040
- other MicroPython boards capable of LPF2 / Powered Up communication
AstroPUP is:
- a helper layer for PUPRemote
- a safer way to call remote commands
- a diagnostics tool for LPF2 / Powered Up communication
- a generic bridge layer for custom sensors and external processors
- useful for robotics education, competitions, and experiments
AstroPUP is not:
- a new protocol
- a replacement for PUPRemote
- a replacement for Pybricks
- a replacement for LPF2 / Powered Up
- a robot framework
- a mission strategy library
- a sensor-specific library
AstroPUP does not know anything about your robot, your sensors, your camera, your line follower, or your competition rules.
Your project-specific logic should stay in your own files, such as:
main.py
robot.py
sensors.py
profiles.py
Copy this file to your Pybricks project:
src/astropup_hub.py
Then import it:
from astropup_hub import AstroPUPHubCopy this file to your external device:
src/astropup_sensor.py
Then import it:
from astropup_sensor import AstroPUPSensorlpf2.py is the low-level LPF2 / Powered Up implementation derived from PUPRemote.
Some devices already include the required LPF2 / PUPRemote support. Other MicroPython devices may need lpf2.py copied manually.
| Device / Environment | Files usually needed |
|---|---|
| LEGO Hub with Pybricks | astropup_hub.py |
| LMS-ESP32 with PUPRemote firmware | astropup_sensor.py |
| Generic ESP32 MicroPython | astropup_sensor.py + lpf2.py |
| OpenMV | astropup_sensor.py + lpf2.py |
| RP2040 MicroPython | astropup_sensor.py + lpf2.py |
| Other MicroPython boards | astropup_sensor.py + lpf2.py |
If your device already provides lpf2.py internally, you do not need to copy it again.
Run this on the external MicroPython device.
from astropup_sensor import AstroPUPSensor
sensor = AstroPUPSensor(profile="competition", debug=False)
def reset():
return (1,)
def state():
value_1 = 123
value_2 = -45
return (value_1, value_2)
sensor.add_command("reset", "B", callback=reset)
sensor.add_command("state", "hh", callback=state)
while True:
sensor.safe_process()Run this on the LEGO hub with Pybricks.
from pybricks.parameters import Port
from pybricks.tools import wait
from astropup_hub import AstroPUPHub
link = AstroPUPHub(Port.C, profile="debug", debug=True)
link.add_command("reset", "B")
link.add_command("state", "hh")
print(link.startup_report())
while True:
data = link.safe_call("state", default=None)
print(data)
wait(100)The hub side and the external device side must register commands in the same:
- order
- names
- formats
For example:
sensor.add_command("reset", "B", callback=reset)
sensor.add_command("state", "hh", callback=state)link.add_command("reset", "B")
link.add_command("state", "hh")If the order, name, or format does not match, communication may fail or return unexpected data.
While debugging, use:
print(link.startup_report())
print(link.validate_remote_modes())AstroPUP can optionally track whether the hub is receiving fresh data or repeated data.
This is useful when stale sensor values could cause incorrect robot behavior.
def state():
frame_id = sensor.next_frame_id()
value_1 = 123
value_2 = -45
return (frame_id, value_1, value_2)
sensor.add_command("state", "hhh", callback=state)link.add_command("state", "hhh")
data = link.safe_call("state", default=None)
if data is not None:
frame_id, value_1, value_2 = data
fresh = link.track_frame(frame_id)
print("fresh:", fresh)
print("stale:", link.is_stale())
print("stale_count:", link.stale_count())For more details, see:
docs/HEARTBEAT_GUIDE.md
For Pybricks programs using multitask, use safe_call_multitask().
from pybricks.parameters import Port
from pybricks.tools import wait, multitask, run_task
from astropup_hub import AstroPUPHub
link = AstroPUPHub(Port.C, profile="competition", debug=False)
link.add_command("reset", "B")
link.add_command("state", "hh")
async def pup_loop():
while True:
await link.process_async()
await wait(0)
async def read_loop():
while True:
data = await link.safe_call_multitask("state", default=None)
if data is not None:
value_1, value_2 = data
print(value_1, value_2)
await wait(0)
async def main():
await multitask(pup_loop(), read_loop())
run_task(main())Start with the simplest examples before using advanced sensors, cameras, or robot-specific packets.
| Example | Purpose |
|---|---|
examples/esp32_hello_world |
Minimal ESP32 communication test |
examples/openmv_hello_camera |
Minimal OpenMV camera + communication test |
examples/basic_sensor |
Generic minimal hub/sensor pair |
examples/startup_diagnostics |
Startup report and mode validation |
examples/pybricks_multitask_hub |
Pybricks multitask usage |
examples/heartbeat_stale_demo |
Frame tracking and stale-data detection |
examples/astrogenius_style_bridge |
More realistic bridge-style packet example |
See:
examples/README.md
- Start with
examples/esp32_hello_worldorexamples/openmv_hello_cameraif you are using real hardware. - Use
examples/basic_sensoras the generic minimal hub/sensor pair. - Add
startup_report()andvalidate_remote_modes()while debugging. - Add heartbeat tracking only after basic communication is stable.
- Move robot-specific logic to your own project files.
AstroPUP includes automated tests for internal logic using pytest and GitHub Actions.
These tests validate:
- module imports
- heartbeat tracking
- command order helpers
- sensor-side frame ID helpers
Automated tests do not replace real LPF2 / Powered Up hardware validation.
For real hardware validation, see:
docs/HARDWARE_TEST_CHECKLIST.md
Run tests locally with:
pytest -qAstroPUP is based on and derived from PUPRemote by Anton's Mindstorms.
Original project:
https://github.com/AntonsMindstorms/PUPRemote
PUPRemote provides the LPF2 / Powered Up emulation foundation and the original hub/sensor communication approach.
AstroPUP adds a higher-level helper layer focused on safer usage, diagnostics, and competition-friendly reliability tools.
See:
CREDITS.md
AstroPUP includes code derived from PUPRemote.
PUPRemote is distributed under the GPL-3.0 license. AstroPUP is released under GPL-3.0-compatible terms and keeps attribution to the original PUPRemote project.
See:
LICENSE
CREDITS.md
AstroPUP is an independent open-source project.
It is not affiliated with, endorsed by, or sponsored by:
- LEGO Group
- Pybricks
- Anton's Mindstorms
- PUPRemote
LEGO, SPIKE, MINDSTORMS, Powered Up, and related names are trademarks of their respective owners.
Current development version:
v0.3.2
AstroPUP is under active development and should be tested carefully before use in competition runs.
