QEMU Manager
The QEMU Manager component provides 5 tools for creating and managing virtual ESP devices using Espressif’s QEMU fork. Virtual devices expose a TCP serial port that accepts socket:// URIs, making them fully transparent to all other mcesptool operations.
QEMU emulation is only available when the Espressif QEMU fork binaries are installed. The server auto-detects binaries in ~/.espressif/tools/qemu-xtensa/ and ~/.espressif/tools/qemu-riscv32/.
Supported Chips
Section titled “Supported Chips”| Chip | Architecture | QEMU Machine | Notes |
|---|---|---|---|
esp32 | Xtensa | esp32 | Full support, most mature emulation |
esp32s3 | Xtensa | esp32s3 | Shares esp32c3 eFuse device in QEMU |
esp32c3 | RISC-V | esp32c3 | Full support |
Boot Modes
Section titled “Boot Modes”| Mode | Behavior |
|---|---|
download | GPIO strap forces ROM into serial bootloader. esptool can connect, flash, read, and identify the chip — same as holding BOOT on real hardware. This is the default. |
normal | Boots from flash and runs firmware. Use this to observe application output after flashing. |
esp_qemu_start
Section titled “esp_qemu_start”Start a virtual ESP device. Returns a socket:// URI that works with all esptool-based tools.
Parameters
Section titled “Parameters”| Name | Type | Default | Description |
|---|---|---|---|
chip_type | str | "esp32" | Target chip: esp32, esp32s3, or esp32c3. |
flash_image | str | None | None | Path to an existing flash image file. Creates a blank (all 0xFF) flash if omitted. |
flash_size_mb | int | 4 | Flash size in MB when creating blank images. |
tcp_port | int | None | None | TCP port for virtual serial. Auto-assigned from the port pool (starting at 5555) if omitted. |
boot_mode | str | "download" | "download" for esptool interaction, "normal" to boot from flash. |
extra_args | list[str] | None | None | Additional QEMU command-line arguments. |
Example
Section titled “Example”# Start a virtual ESP32 in download mode (default)result = await client.call_tool("esp_qemu_start", { "chip_type": "esp32"})
# Start ESP32-C3 with an existing flash image in normal boot moderesult = await client.call_tool("esp_qemu_start", { "chip_type": "esp32c3", "flash_image": "/images/my_firmware.bin", "boot_mode": "normal"})Return Value
Section titled “Return Value”{ "success": true, "instance_id": "qemu-1", "chip_type": "esp32", "tcp_port": 5555, "socket_uri": "socket://localhost:5555", "flash_image": "/home/user/.../flash_esp32_5555.bin", "boot_mode": "download", "pid": 12345, "hint": "Use port='socket://localhost:5555' with other esp_ tools to interact with this virtual device"}Typical Workflow
Section titled “Typical Workflow”- Start in download mode:
esp_qemu_start— get the socket URI - Flash firmware:
esp_flash_firmwarewith the socket URI asport - Stop the instance:
esp_qemu_stop - Restart in normal mode:
esp_qemu_startwith the sameflash_imageandboot_mode: "normal"
Instance Limits
Section titled “Instance Limits”The maximum number of concurrent QEMU instances is controlled by the QEMU_MAX_INSTANCES configuration (default: 4). Each instance occupies one TCP port from the pool starting at QEMU_BASE_PORT (default: 5555).
esp_qemu_stop
Section titled “esp_qemu_stop”Stop a running QEMU instance. The flash image is preserved on disk so the instance can be restarted later.
Parameters
Section titled “Parameters”| Name | Type | Default | Description |
|---|---|---|---|
instance_id | str | None | None | Instance ID to stop (e.g., "qemu-1"). Stops all running instances if omitted. |
Example
Section titled “Example”# Stop a specific instanceresult = await client.call_tool("esp_qemu_stop", { "instance_id": "qemu-1"})
# Stop all instancesresult = await client.call_tool("esp_qemu_stop", {})Return Value
Section titled “Return Value”{ "success": true, "stopped": ["qemu-1"], "remaining": 0}esp_qemu_list
Section titled “esp_qemu_list”List all QEMU instances with their status, chip type, port, and uptime.
Parameters
Section titled “Parameters”This tool takes no parameters.
Example
Section titled “Example”result = await client.call_tool("esp_qemu_list", {})Return Value
Section titled “Return Value”{ "success": true, "instances": [ { "instance_id": "qemu-1", "chip_type": "esp32", "tcp_port": 5555, "socket_uri": "socket://localhost:5555", "running": true, "pid": 12345, "uptime_seconds": 342.5 }, { "instance_id": "qemu-2", "chip_type": "esp32c3", "tcp_port": 5556, "socket_uri": "socket://localhost:5556", "running": false, "pid": 12350, "uptime_seconds": 0 } ], "total": 2, "running": 1, "max_instances": 4}esp_qemu_status
Section titled “esp_qemu_status”Get detailed status of a specific QEMU instance including uptime, PID, boot mode, and flash/efuse image paths.
Parameters
Section titled “Parameters”| Name | Type | Default | Description |
|---|---|---|---|
instance_id | str | None | None | Instance to inspect. Returns the first running instance if omitted. |
Example
Section titled “Example”result = await client.call_tool("esp_qemu_status", { "instance_id": "qemu-1"})Return Value
Section titled “Return Value”{ "success": true, "instance_id": "qemu-1", "chip_type": "esp32", "machine": "esp32", "tcp_port": 5555, "socket_uri": "socket://localhost:5555", "flash_image": "/home/user/.../flash_esp32_5555.bin", "flash_size_mb": 4, "boot_mode": "download", "running": true, "pid": 12345, "started_at": 1708732800.0, "uptime_seconds": 342.5, "extra_args": []}esp_qemu_flash
Section titled “esp_qemu_flash”Write a firmware binary directly into a stopped QEMU instance’s flash image file. This is an offline operation that patches the raw binary image at the specified offset.
For most workflows, prefer using esp_flash_firmware with the instance’s socket URI while it is running in download mode. That approach uses esptool’s full flash protocol including verification. Use this tool when you need direct image manipulation — for example, pre-loading a merged binary without starting the emulator.
Parameters
Section titled “Parameters”| Name | Type | Default | Description |
|---|---|---|---|
instance_id | str | (required) | Target QEMU instance. Must be stopped. |
firmware_path | str | (required) | Path to the firmware binary to write. |
address | str | "0x0" | Flash address offset as hex string. |
Example
Section titled “Example”# Stop instance, write firmware, restart in normal modeawait client.call_tool("esp_qemu_stop", {"instance_id": "qemu-1"})
result = await client.call_tool("esp_qemu_flash", { "instance_id": "qemu-1", "firmware_path": "/build/merged_firmware.bin", "address": "0x0"})
await client.call_tool("esp_qemu_start", { "chip_type": "esp32", "flash_image": result["flash_image"], "boot_mode": "normal"})Return Value
Section titled “Return Value”{ "success": true, "instance_id": "qemu-1", "firmware_path": "/build/merged_firmware.bin", "address": "0x00000000", "bytes_written": 524288, "flash_image": "/home/user/.../flash_esp32_5555.bin", "hint": "Use esp_qemu_start to restart the instance with the new firmware"}Scan Integration
Section titled “Scan Integration”Running QEMU instances automatically appear in esp_scan_ports results with "source": "qemu". The server cross-wires the QEMU Manager with the Chip Control component so that port scanning discovers both physical and virtual devices.