`mpremote mip install` hangs when uploading files
Port, board and/or hardware
STM32 port on a NUCLEO-144--F767ZI
MicroPython version
MicroPython v1.27.0-preview.440.ga6864109db on 2025-11-23; NUCLEO-F767ZI with STM32F767
Reproduction
- (Optional) Perform a mass erase on all flash blocks.
- Build main firmware and mboot, then upload image to the board with
deploy-stlink - Reboot the board
- Run
mpremote mip install unittest - Wait until it hangs
Expected behaviour
mpremote mip should install the required package
Observed behaviour
The upload process hangs at random intervals, and when stopping it via CTRL+C the traceback is as follows (the process was stopped after a few minutes of being stuck at 13%):
Traceback (most recent call last):
File "/home/agatti/src/micropython/ports/stm32/../../tools/mpremote/mpremote.py", line 6, in <module>
sys.exit(main.main())
~~~~~~~~~^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/main.py", line 614, in main
handler_func(state, args)
~~~~~~~~~~~~^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 206, in do_mip
_install_package(
~~~~~~~~~~~~~~~~^
state.transport,
^^^^^^^^^^^^^^^^
...<4 lines>...
args.mpy,
^^^^^^^^^
)
^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 169, in _install_package
_install_json(transport, package, index, target, version, mpy)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 131, in _install_json
_download_file(transport, file_url, fs_target_path)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 99, in _download_file
transport.fs_writefile(dest, data, progress_callback=show_progress_bar)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport.py", line 163, in fs_writefile
self.exec("w(" + repr(chunk) + ")")
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 309, in exec
ret, ret_err = self.exec_raw(command, data_consumer=data_consumer)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 295, in exec_raw
self.exec_raw_no_follow(command)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 273, in exec_raw_no_follow
return self.raw_paste_write(command_bytes)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 228, in raw_paste_write
data = self.serial.read(1)
File "/usr/lib/python3.13/site-packages/serial/serialposix.py", line 565, in read
ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
The firmware is still waiting for data to arrive on the serial port when mpremote seems to be stuck:
^C
Program received signal SIGINT, Interrupt.
mp_hal_stdin_rx_chr () at mphalport.c:53
53 for (;;) {
08:14:08 mp_hal_stdin_rx_chr > bt
#0 mp_hal_stdin_rx_chr () at mphalport.c:53
#1 0x08068aec in mp_reader_stdin_readbyte (data=0x2007ff74)
at ../../shared/runtime/pyexec.c:255
#2 0x08023164 in next_char (lex=lex@entry=0x20037b50) at ../../py/lexer.c:174
#3 0x08023456 in parse_string_literal (lex=0x20037b50, is_raw=false,
is_fstring=false) at ../../py/lexer.c:413
#4 mp_lexer_to_next (lex=lex@entry=0x20037b50) at ../../py/lexer.c:696
#5 0x0802443c in mp_parse (lex=lex@entry=0x20037b50,
input_kind=input_kind@entry=MP_PARSE_FILE_INPUT) at ../../py/parse.c:1167
#6 0x08068be8 in parse_compile_execute (source=source@entry=0x2007ff7c,
input_kind=input_kind@entry=MP_PARSE_FILE_INPUT,
exec_flags=exec_flags@entry=65) at ../../shared/runtime/pyexec.c:111
#7 0x08068d90 in do_reader_stdin (c=65) at ../../shared/runtime/pyexec.c:324
#8 pyexec_raw_repl () at ../../shared/runtime/pyexec.c:567
#9 0x080730d4 in stm32_main (reset_mode=<optimized out>) at main.c:718
#10 <signal handler called>
in fact, reconnecting to the serial port will find the interpreter still waiting for data to arrive.
Additional Information
No, I've provided everything above.
Code of Conduct
Yes, I agree
tools/mpremote: Add mip download and mpy version handling.
<!-- Thanks for submitting a Pull Request! We appreciate you spending the
time to improve MicroPython. Please provide enough information so that
others can review your Pull Request.
Before submitting, please read:
https://github.com/micropython/micropython/blob/master/CODEOFCONDUCT.md
https://github.com/micropython/micropython/wiki/ContributorGuidelines
Please check any CI failures that appear after your Pull Request is opened.
-->
Summary
Adds a new sub-command for mpremote mip: mpremote mip download, inspired by pip download, which accept same packages and uses same logic as mip install, but downloads files to local filesystem instead of remote micropython board.
Fixes #18711.
A use case: run mip download for all the dependencies, and add ./lib to python.analysis.extraPaths or whatever your ide/setup uses -- and now you have a proper code completion and go to definition working with same code obtained by same command as the code running on the board.
An issue on Raspberry Pi forums by someone else on this with more details: https://forums.raspberrypi.com/viewtopic.php?t=377948
Testing
- tested that mip install maintains old behaviour with no changes.
- tested that mip download works without a connected device, but also works with connected devices when
--mpyis set to download mpy files.
Trade-offs and Alternatives
An alternative approach is to only keep mip install command and support both local and remote paths in the --target argument.
I have considered this alternative to adding mip download command and keeping mip install, but I have decided against it due to following reasons:
Backwards incompatibility in path handling
Many mpremote commands already support working with both local and remote filesystems, and : is used to reference remote filesystem.
The mip install doesn't follow that convention: it can only work with remote paths, so mip install's --target argument treats all paths as remote, and doesn't support the : notation for remote paths.
Making mip install follow this convention would be a backwards incompatible change which I wanted to avoid.
Defaults for mpy argument
The mip install prefers mpy files instead of py files as those are a better default for files installed to the micropython environment.
However, preferring mpy files to py files for locally downloaded files is not a sane default: the mpy files won't help for most use-cases where local downloads of mip packages are needed.
Having multiple different commands makes the mpy defaulting easier: it makes more sense to have different defaults for different commands rather than to have the default value for a flag change depending what was provided in the other flag.
pip download command
There's a pip download command the mip download command matches, while the pip install arguably doesn't provide options to just download the packages to a given directory. This makes the mip download command be recognizable for people already familiar with pip commands.