Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cli/src/mpftp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""mpftp — MicroPython and CircuitPython board tools.

Importing this package has no side effects: nothing is spawned, no socket is
opened, and no file is written. Entry points live in :mod:`mpftp.cli`,
:mod:`mpftp.sidecar` and :mod:`mpftp.firmware`.
opened, and no file is written. Entry points live in `mpftp.cli`,
`mpftp.sidecar` and `mpftp.firmware`.
"""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""``python -m mpftp`` — the CLI, or the local PWA with no arguments.

``python -m mpftp <subcommand> ...`` behaves exactly like the ``mpftp``
Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/ble.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Reach a board over Bluetooth Low Energy: ``-d ble://NAME``.

The board runs ``bledev.repl`` (pydevices' ``bledev`` package), which serves
the REPL over the Nordic UART service behind a password, the way WebREPL does
over Wi-Fi. :class:`BleSerial` dresses that link up as a pyserial port and
:func:`open_transport` hands it to mpremote's ``SerialTransport``, so exec,
over Wi-Fi. `BleSerial` dresses that link up as a pyserial port and
`open_transport` hands it to mpremote's ``SerialTransport``, so exec,
raw REPL, interrupt and every ``fs_*`` operation work unchanged.

When the board also serves files (``bledev.filetransfer``, CircuitPython's BLE
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/boards.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Boards mpftp can reach over Wi-Fi, and their WebREPL passwords.

Remembered boards live in ``~/.mpftp/config.json`` under ``wifiBoards``,
Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
mpftp CLI — agent-friendly front-end to the mpftp sidecar / extension RPC.

Expand Down Expand Up @@ -167,7 +171,7 @@ def _die(msg: str, code: int = 1) -> None:
def find_rpc_entry() -> Optional[dict[str, Any]]:
"""Full registry entry for the live extension RPC, if any.

Same preference order as :func:`find_rpc_addr`, but keeps whatever
Same preference order as `find_rpc_addr`, but keeps whatever
diagnostic fields (``editor``, ``pid``, ``updatedAt``) came with it —
`find_rpc_addr` throws those away, which is fine for connecting but not
for telling a caller *whose* session they resolved to (mpftp#21).
Expand Down Expand Up @@ -273,7 +277,7 @@ def stream_debug_tee(
(re)appear, and ``duration`` counts from the last time it opened
(each ``debug_tee_open`` notify; ``hard-reset --monitor``, mpftp#60).

Unlike :meth:`call` + ``debug_tee_start`` (which stops the moment the
Unlike `call` + ``debug_tee_start`` (which stops the moment the
CLI returns and closes the private sidecar, so the tee died with it),
this keeps the session alive so the sidecar's tee loop keeps
writing ``log_path`` and emitting ``debug_tee_data`` the whole time.
Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""``~/.mpftp/config.json`` — one settings file for every mpftp frontend.

JSON rather than TOML so the file looks like every other tool config a coding
Expand All @@ -13,7 +17,7 @@
5. The built-in default

Layers 1 and 2 belong to the caller: pass what you were given to
:func:`resolve` as ``override``, and this module supplies the rest.
`resolve` as ``override``, and this module supplies the rest.
"""

from __future__ import annotations
Expand Down Expand Up @@ -190,7 +194,7 @@ def update(patch: dict[str, Any]) -> dict[str, Any]:
"""Merge ``patch`` into the file and return the stored result.

Only the file layer is touched; environment and defaults are not persisted,
so writing back what :func:`load` returned would not bake them in.
so writing back what `load` returned would not bake them in.
"""
stored = validate(read_file())
for name, value in patch.items():
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/conpty.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A Windows program inside a pseudo console (ConPTY), for ``mpftp hold``.

Standard library only (ctypes). Windows-only: imported by the hold pump when
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/desktop_host.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
mpftp desktop host: the PWA's relay without the web server.

Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/emu.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
The desktop app's emulator as a board: ``emu://``.

Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/espusb.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""No-UAC recovery for an ESP32 wedged on its own USB, or a CH343 port that
answers "Incorrect function" (mpftp#31).

Expand All @@ -18,9 +22,9 @@
``Get-PnpDevice | Restart-PnpDevice``, and there is no ``Restart-PnpDevice`` in
Windows PowerShell -- so it failed on every device, reported LastTaskResult 1,
and was indistinguishable from a board that refused to come back. That cost a
night's unattended flashing. :func:`task_state` exists so a caller can tell
night's unattended flashing. `task_state` exists so a caller can tell
"the recovery is not installed" from "the board did not make it", and
:func:`recovery_available` is the one-line question to ask first.
`recovery_available` is the one-line question to ask first.
"""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/firmware.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
mpftp firmware engine — build MicroPython (or CircuitPython-compatible)
firmware with micropython-pydevices' ``build_mp.py``, and flash it.
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/firmware_build.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Firmware builds through micropython-pydevices' ``build_mp.py``.

``mpftp firmware build`` doesn't run ``make`` itself. It finds a
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/firmware_download.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
Official firmware: a catalog of boards, the images each one has, and the
download.
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/hold.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""``mpftp hold``: keep one board's friendly REPL open between commands.

Every other mpftp command opens a connection, enters the raw REPL with a
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/mdns.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Find a board by its ``<hostname>.local`` name: a minimal mDNS client.

MicroPython's esp32 port answers mDNS A queries for ``network.hostname()``
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/package_search.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Find a package without knowing its exact name.

One search across the three places a board's packages come from:
Expand Down
6 changes: 5 additions & 1 deletion cli/src/mpftp/panel.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""The File Transfer panel's host for the browser interface (``python -m mpftp``).

The browser page runs the VS Code extension's own panel script
(``extension/media/ftp.js``). That script talks to its host with
``postMessage({type: ...})`` and redraws from the ``state`` and ``status``
messages it gets back. In VS Code the host is ``FtpViewProvider.ts``; here it
is :class:`PanelHost`, a port of that class's message handling, so the two
is `PanelHost`, a port of that class's message handling, so the two
hosts behave the same way:

* board operations go to the shared sidecar (``SidecarRelay.request``);
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/parentwatch.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""End this process when the one that started it ends, however it ends.

The desktop app starts ``mpftp.desktop_host``, which starts
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/pwa.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
mpftp local PWA — a loopback-only web UI (file transfer + REPL) for a
MicroPython/CircuitPython board, served entirely with the standard library.
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/rawpaste.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Raw-paste over serial, one flow-control window in flight.

Raw-paste mode (``Ctrl-E A Ctrl-A``) lets the host run ahead of the board: the
Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/sidecar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
mpftp sidecar — long-lived mpremote session for VS Code / Cursor.

Expand Down Expand Up @@ -372,7 +376,7 @@ def exec_follow(

Streams the board's bytes through mpremote's ``data_consumer`` hook rather
than letting ``follow`` accumulate them in a local its timeout discards; a
timeout then raises :class:`FollowTimeoutError` carrying the partial text.
timeout then raises `FollowTimeoutError` carrying the partial text.

``timeout`` is how long the script may print nothing before the follow
gives up; ``None`` waits until it ends. mpremote's ``exec`` has no way to
Expand Down Expand Up @@ -4626,7 +4630,7 @@ def debug_tee_start(
catches the boot banner.

``dtr`` raises DTR (RTS stays low) for a native TinyUSB CDC port,
which sends nothing without it; see :func:`console_wants_dtr`.
which sends nothing without it; see `console_wants_dtr`.
"""
import time

Expand Down
6 changes: 5 additions & 1 deletion cli/src/mpftp/uf2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
UF2 support for the mpftp firmware engine: file validation, bootloader-volume
discovery, and the copy-and-verify flash itself.
Expand Down Expand Up @@ -152,7 +156,7 @@ def parse_uf2(path: Path, max_blocks: int = 0) -> dict:
Returns ``{"blocks", "families", "family_names", "payload_bytes",
"target_start", "target_end", "warnings"}``.

Raises :class:`Uf2Error` for anything structurally wrong -- a truncated
Raises `Uf2Error` for anything structurally wrong -- a truncated
file, bad magic, or a block count that disagrees with the header. Checking
this up front matters because the bootloader's response to a bad image is
to ignore it in silence, which is indistinguishable from a failed copy.
Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/webflash.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""
Flashing a board from the browser interface (``python -m mpftp``).

Expand Down
8 changes: 6 additions & 2 deletions cli/src/mpftp/webrepl.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Reach a board over Wi-Fi through MicroPython's WebREPL.

A WebREPL session is a WebSocket whose text frames carry the REPL's byte
stream (``extmod/modwebrepl.c`` hands them to ``os.dupterm``). That is the
same stream a USB serial port carries, so :class:`WebSocketSerial` dresses the
socket up as a pyserial port and :func:`open_transport` hands it to mpremote's
same stream a USB serial port carries, so `WebSocketSerial` dresses the
socket up as a pyserial port and `open_transport` hands it to mpremote's
``SerialTransport``. Raw REPL, raw-paste, exec and every ``fs_*`` operation
then work unchanged. The design is described in ``docs/how-it-works/wifi-and-bluetooth.md``.

Expand Down
4 changes: 4 additions & 0 deletions cli/src/mpftp/wifiboard.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""What mpftp says to a board about Wi-Fi: identity, boot.py, power save.

Pure functions and board-side snippets, so the sidecar stays a caller and the
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/fake_circuitpython.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A serial port that answers the way a CircuitPython 11 board does.

The detail that matters for connect time is how CircuitPython changes REPL
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_app_image_offset.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""An application image written at the bootloader offset boot-loops the board.

mpftp#67: ``micropython.bin`` flashed with ``--artifact`` on the P4 went to
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_binary_roundtrip.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""get, put, cp and hash keep every byte of a binary file (null bytes included).

A model file (.espdl) is full of zeros and every other byte value. The board
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_ble.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""ble:// devices: addresses, passwords, the bledev.repl login and file transfer.

No radio and no bleak: the "board" answers what BleSerial writes, the way
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_circuitpy_drive_match.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Which CIRCUITPY drive belongs to the connected board (mpftp#169)."""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_circuitpython.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Unit tests for CircuitPython serial helpers (no board required)."""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_clean.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""mpftp clean --dry-run: board debris cleanup (mpftp#17). No board required."""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Unit tests for mpftp.config: ~/.mpftp/config.json (no board needed)."""

from __future__ import annotations
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_connect_deadline.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A board that floods output forever must not hold connect indefinitely (mpftp#193).

mpremote's timeouts run between bytes, so continuous output defeats them.
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_cp_connect_time.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Connecting to CircuitPython does not wait out timeouts (mpftp#155).

CircuitPython rebuilds its VM on every switch between the friendly and the raw
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/test_detect.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Unit tests for the firmware Detect parsers / matcher (no hardware).

Sample esptool text is taken from the fixture campaign (ESP32-P4, ESP32-S3
Expand Down
Loading
Loading