Skip to content

Conversation

@dlech
Copy link
Member

@dlech dlech commented Jul 14, 2025

Well, I might have got a bit carried away with this. 😀

In addition to fixing the issue with USB stdout blocking forever when an app was not connected to the hub, I added a dynamic sys/std{in,out,err} to MicroPython.

Initially, we were considering following the MicroPython "dupterm" mechanism of connecting multiple streams in parallel for stdin/stdout. This would be fine as long as we only ever had one connection type at a time. But I don't think that is a reasonable expectation. And I think it would be very confusing to users that wanted to use multiple connections at the same time.

So they way this works is that Bluetooth and USB stdin/stdout are treated as two completely separate streams. When MicroPython starts, it will use a sensible default (what this means exactly is up for discussion). But both streams are always present at hub.ble.io and hub.usb.io.

So for people that want to use both connections, they now have full control and can read and write data from either stream and know where it is coming/from going to. For example, you could use USB for stdio (print messages) and Bluetooth for remote control. I.e. you could add sys.stdout = hub.usb.io early in the program to always print to USB.

This requires pbricksdev from the master branch for testing.

I used this program to check that things didn't lock up waiting for print over USB when running without USB connection or disconnecting while running.

from pybricks.hubs import PrimeHub
from pybricks.parameters import Color
from pybricks.tools import wait

hub = PrimeHub()

while True:
    hub.light.on(Color.RED)
    print("Red")
    wait(1000)

    hub.light.on(Color.GREEN)
    print("Green")
    wait(1000)

dlech added 12 commits July 13, 2025 20:59
The pbsys_host layer was intended to combine all stdio through a single
interface. However, this does not allow users to specify where stdout
goes or which input source data came from. For example, they may want
to print debug messages over Bluetooth while communicating with another
device over USB. Instead, we will create separate stdio interfaces for
each connection type so that users can choose which to use if they want
to.
It is possible for print functions to raise an exception, so we need to
catch this when printing a final exception since the nlr stack is empty
at that point and would otherwise cause a crash.
Add a new function to convert PBIO error codes to MicroPython errnos.
This works similar to pb_assert(), but does not raise an exception.
There are some cases where we need to convert PBIO errors to errnos
without raising an exception, such as when implementing the stream
protocol.
Add a new stream object that uses the Pybricks Profile stdin/stdout over
Bluetooth. This is available a hub.bluetooth.io and can be used to
send and receive data over Bluetooth even when something else is used
for sys.stdin/stdout (e.g. USB).
Pull in changes to allow mutable sys.stdin, sys.stdout, and sys.stderr.
Change the sys.stdin, sys.stdout, and sys.stderr objects to be mutable
so that they can be replaced with USB stdio when available on hubs that
support it.
Add transport parameter to pbsys_command() so that it can distinguish
between commands received over USB and Bluetooth.
Copy the ring buffer logic from bluetooth to usb_stm32.c so that we
can get stdin commands over USB working.
Add a new USB stream type that can be used for stdin, stdout, and stderr
over USB. For now, this just lives at hub.usb.io.
Implement the Pybricks Profile event subscription flag.

This saves us from trying to send messages to the host when there is no
app running to receive them. Previously, trying to write to stdout
would block forever because of this.

The 5 millisecond timeout is arbitrary. Wireshark shows that the OUT
endpoint is being serviced in less than 1 ms, so this should be plenty
of time even if some hosts are an order of magnitude slower. We don't
want to make it too big because it will delay user programs for this
long and we don't want to mess up someone's control loop too badly.
Pass the transport type that requested the user program to start to
the MicroPython launcher to allow it to select the appropriate default
stdio stream (USB or Bluetooth).
Now that USB is mostly working, we can enable it by default on SPIKE
Essential and Prime hubs.
@coveralls
Copy link

Coverage Status

coverage: 57.428% (+0.3%) from 57.162%
when pulling 2c21a28 on dlech:usb-stdio
into 625e80b on pybricks:master.

@laurensvalk
Copy link
Member

Sounds very exciting! Let's discuss the API changes over some 🍵.

@dlech
Copy link
Member Author

dlech commented Jul 19, 2025

Closing in favor of #349.

If we ever need a stream class, e.g. for Nordic UART service, 58d9bde could still be useful.

@dlech dlech closed this Jul 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants