Program lifecycle
User programs are plain Python scripts executed by the on-device runner
in ports/b2op/fs/runner.py. Write a normal script — no special
decorators are needed.
Running a program
The runner execs the file and waits for it to finish. A program
that needs to keep running (sensor polling, animation) uses its own
while True loop and time.sleep_ms / time.sleep:
import hub, time
hub.led.setColorIdx(lpf2.color.GREEN)
while True:
print(hub.imu.pitch)
time.sleep(0.1)
Button callbacks (hub.buttons.on()) fire automatically from the
C firmware loop (Hub::loop) — no poll() call needed anywhere
in user code.
Exiting a program
Call
hub.exit()— raisesSystemExit; the runner shows the “Done” screen. Safe from anywhere (button callbacks, loops, top-level code).Hold the centre button >= 2 s — the C firmware schedules a
KeyboardInterrupt; the runner catches it and shows “Done”.The runner also catches any unhandled
Exceptionand shows the “Error” screen with itsrepr.
Powering off
Hold the centre button >= 5 s — hardware power-off from C.
Call
hub.powerOff()— immediate power-off from Python. Does not return.
If the hardware power latch does not hold (e.g. because USB power is present), the firmware performs an orderly shutdown:
Disables LPF2 ports and zeroes all motor PWM outputs.
Turns off the LCD backlight.
Deinitialises BLE and WiFi.
Signals the MicroPython task to exit gracefully — the garbage collector runs before the task deletes itself.
Throttles the CPU to 80 MHz.
Enters a low-activity loop that keeps the battery status LED updated every 2 s.
Pressing the right button restarts the firmware.
Background poll hook
hub.on() registers Python callables that the C firmware
schedules on the MicroPython task every ~20 ms via
mp_sched_schedule. This runs even while a user script is blocking
inside exec. Currently only the "poll" event is defined.
@hub.on("poll")
def my_poll():
# called ~every 20 ms on mp_task
...
The on-device protocol layer registers its stdin reader with
hub.on("poll") so USB/BLE frames are processed even while a user
script is running.
Log level
hub.log (hub._log_module) controls the firmware log
verbosity:
hub.log.setLevel(2) # 0=none, 1=err, 2=warn, 3=info, 4=debug, 5=verbose