Skip to content

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/.

ChipArchitectureQEMU MachineNotes
esp32Xtensaesp32Full support, most mature emulation
esp32s3Xtensaesp32s3Shares esp32c3 eFuse device in QEMU
esp32c3RISC-Vesp32c3Full support
ModeBehavior
downloadGPIO 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.
normalBoots from flash and runs firmware. Use this to observe application output after flashing.

Start a virtual ESP device. Returns a socket:// URI that works with all esptool-based tools.

NameTypeDefaultDescription
chip_typestr"esp32"Target chip: esp32, esp32s3, or esp32c3.
flash_imagestr | NoneNonePath to an existing flash image file. Creates a blank (all 0xFF) flash if omitted.
flash_size_mbint4Flash size in MB when creating blank images.
tcp_portint | NoneNoneTCP port for virtual serial. Auto-assigned from the port pool (starting at 5555) if omitted.
boot_modestr"download""download" for esptool interaction, "normal" to boot from flash.
extra_argslist[str] | NoneNoneAdditional QEMU command-line arguments.
# 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 mode
result = await client.call_tool("esp_qemu_start", {
"chip_type": "esp32c3",
"flash_image": "/images/my_firmware.bin",
"boot_mode": "normal"
})
{
"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"
}
  1. Start in download mode: esp_qemu_start — get the socket URI
  2. Flash firmware: esp_flash_firmware with the socket URI as port
  3. Stop the instance: esp_qemu_stop
  4. Restart in normal mode: esp_qemu_start with the same flash_image and boot_mode: "normal"

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).


Stop a running QEMU instance. The flash image is preserved on disk so the instance can be restarted later.

NameTypeDefaultDescription
instance_idstr | NoneNoneInstance ID to stop (e.g., "qemu-1"). Stops all running instances if omitted.
# Stop a specific instance
result = await client.call_tool("esp_qemu_stop", {
"instance_id": "qemu-1"
})
# Stop all instances
result = await client.call_tool("esp_qemu_stop", {})
{
"success": true,
"stopped": ["qemu-1"],
"remaining": 0
}

List all QEMU instances with their status, chip type, port, and uptime.

This tool takes no parameters.

result = await client.call_tool("esp_qemu_list", {})
{
"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
}

Get detailed status of a specific QEMU instance including uptime, PID, boot mode, and flash/efuse image paths.

NameTypeDefaultDescription
instance_idstr | NoneNoneInstance to inspect. Returns the first running instance if omitted.
result = await client.call_tool("esp_qemu_status", {
"instance_id": "qemu-1"
})
{
"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": []
}

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.

NameTypeDefaultDescription
instance_idstr(required)Target QEMU instance. Must be stopped.
firmware_pathstr(required)Path to the firmware binary to write.
addressstr"0x0"Flash address offset as hex string.
# Stop instance, write firmware, restart in normal mode
await 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"
})
{
"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"
}

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.