From cb4822a0a4bdb37ae3b02af489fe89d8a00ba5d5 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 28 Jan 2026 16:48:39 -0700 Subject: [PATCH] Document QEMU emulation in README and improve tool descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add QEMU section to README with install instructions and tool reference. Enrich MCP tool docstrings with workflow context, cross-references to related tools, and guidance on when to use each tool — these descriptions are read by the calling LLM to decide tool selection. --- README.md | 24 +++++++ .../components/qemu_manager.py | 66 +++++++++++++------ 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c165116..f8d76af 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ FastMCP server providing AI-powered ESP32/ESP8266 development workflows through - **Production Tools**: Factory programming and batch operations - **Middleware System**: Universal CLI tool integration with bidirectional MCP communication - **ESP-IDF Integration**: Host application support for hardware-free development +- **QEMU Emulation**: Virtual ESP32 devices for testing without physical hardware ## Quick Start @@ -64,6 +65,29 @@ The server implements a component-based architecture with middleware for CLI too - `OTAManager`: Over-the-air update workflows - `ProductionTools`: Factory programming and quality control - `Diagnostics`: Memory dumps and performance profiling +- `QemuManager`: QEMU-based ESP32 emulation with download mode, efuse, and flash support + +## QEMU Emulation + +Run virtual ESP32 devices without physical hardware. Requires [Espressif's QEMU fork](https://github.com/espressif/qemu): + +```bash +# Install via ESP-IDF tools +source /path/to/esp-idf/export.sh +python3 $IDF_PATH/tools/idf_tools.py install qemu-xtensa qemu-riscv32 +``` + +The server auto-detects QEMU binaries from `~/.espressif/tools/`. Once available, five tools are exposed: + +| Tool | Description | +|------|-------------| +| `esp_qemu_start` | Launch a virtual ESP device (supports esp32, esp32s2, esp32s3, esp32c3) | +| `esp_qemu_stop` | Stop a running instance | +| `esp_qemu_list` | List all running instances | +| `esp_qemu_status` | Detailed instance info | +| `esp_qemu_flash` | Write firmware to a virtual device's flash | + +Virtual devices appear in `esp_scan_ports` alongside physical hardware, connected via `socket://localhost:`. ## Configuration diff --git a/src/mcp_esptool_server/components/qemu_manager.py b/src/mcp_esptool_server/components/qemu_manager.py index 418af1e..79785b7 100644 --- a/src/mcp_esptool_server/components/qemu_manager.py +++ b/src/mcp_esptool_server/components/qemu_manager.py @@ -149,14 +149,30 @@ class QemuManager: boot_mode: str = "download", extra_args: list[str] | None = None, ) -> dict[str, Any]: - """ - Start a QEMU ESP32 emulation instance + """Start a virtual ESP device using QEMU emulation. No physical hardware needed. + + Returns a socket URI (socket://localhost:PORT) that works with all + esptool operations: esp_detect_chip, esp_flash_firmware, esp_scan_ports, etc. + Virtual devices also appear automatically in esp_scan_ports results. + + Boot modes: + - "download" (default): Device starts in serial bootloader. Use this for + esptool interactions like flashing, chip identification, and flash reading. + - "normal": Device boots from flash and runs firmware. Use this to observe + application output after flashing. + + Typical workflow: + 1. esp_qemu_start (download mode) -> get socket URI + 2. esp_flash_firmware with socket URI -> flash your firmware + 3. esp_qemu_stop -> stop the instance + 4. esp_qemu_start (normal mode, same flash image) -> boot firmware Args: chip_type: Target chip (esp32, esp32s2, esp32s3, esp32c3) - flash_image: Path to flash image file (creates blank if not specified) - flash_size_mb: Flash size in MB for blank images (default: 4) - tcp_port: TCP port for virtual serial (auto-assigned if not specified) + flash_image: Path to existing flash image file. Creates a blank erased + flash (all 0xFF) if not specified. + flash_size_mb: Flash size in MB when creating blank images (default: 4) + tcp_port: TCP port for virtual serial (auto-assigned from pool if not specified) boot_mode: "download" for esptool interaction (default), "normal" to boot from flash extra_args: Additional QEMU command-line arguments """ @@ -168,28 +184,34 @@ class QemuManager: async def qemu_stop( context: Context, instance_id: str | None = None ) -> dict[str, Any]: - """ - Stop a running QEMU instance + """Stop a running QEMU virtual device. Terminates the QEMU process and frees the TCP port. + + The flash image is preserved on disk, so the instance can be restarted + with esp_qemu_start using the same flash_image path (e.g., to switch + from download mode to normal boot mode after flashing). Args: - instance_id: Instance ID to stop (stops all if not specified) + instance_id: Instance ID to stop (stops all running instances if not specified) """ return await self._stop_impl(context, instance_id) @self.app.tool("esp_qemu_list") async def qemu_list(context: Context) -> dict[str, Any]: - """List all QEMU instances with status""" + """List all QEMU virtual device instances with their status, chip type, port, and boot mode. + + Returns running and stopped instances. Use this to find instance IDs + for esp_qemu_stop or esp_qemu_status, or to get socket URIs for esptool operations.""" return await self._list_impl(context) @self.app.tool("esp_qemu_status") async def qemu_status( context: Context, instance_id: str | None = None ) -> dict[str, Any]: - """ - Get detailed status of a QEMU instance + """Get detailed status of a QEMU virtual device including uptime, PID, socket URI, + boot mode, and flash/efuse image paths. Args: - instance_id: Instance to inspect (first running if not specified) + instance_id: Instance to inspect (returns first running instance if not specified) """ return await self._status_impl(context, instance_id) @@ -200,17 +222,21 @@ class QemuManager: firmware_path: str, address: str = "0x0", ) -> dict[str, Any]: - """ - Flash a firmware binary to a QEMU instance's flash image + """Write a firmware binary directly into a QEMU instance's flash image file. - The instance must be stopped first. This writes the binary at the - given offset into the raw flash image file, then you can restart - the instance. + This is an offline operation — the instance must be stopped first. + It patches the raw flash image at the given offset, then you can restart + with esp_qemu_start in "normal" boot mode to run the firmware. + + For most use cases, prefer using esp_flash_firmware with the instance's + socket URI while it's running in download mode — that uses esptool's + full flash protocol including verification. Use this tool only when you + need direct image manipulation (e.g., pre-loading a merged binary). Args: - instance_id: Target QEMU instance - firmware_path: Path to firmware binary to write - address: Flash address offset (hex string, default: 0x0) + instance_id: Target QEMU instance (must be stopped) + firmware_path: Path to firmware binary to write into the flash image + address: Flash address offset as hex string (default: "0x0") """ return await self._flash_impl(context, instance_id, firmware_path, address)