* feat: add qemu emulator * chore: use current dir instead of cloning repo * Add QEMU emulator tooling under tools/qemu tools/qemu/tasmota-qemu.sh builds the current Tasmota checkout for an ESP32 PlatformIO env and boots it under an Espressif QEMU fork on an amd64 host: serial console, and a browsable web UI via the WiFi-capable fork. NET_WIFI=1 tools/qemu/tasmota-qemu.sh # build -> image -> wifi QEMU -> web UI Notes: - Builds the local working tree (no clone); artifacts and QEMU forks live under a gitignored tasmota-qemu/ at the repo root. - ESP32-only helper; no Tasmota core source is changed. See tools/qemu/README.md for commands, env vars, and panic-debugging notes. * chore: cleanup
Running Tasmota (ESP32) under QEMU
tasmota-qemu.sh (in tools/qemu/) builds a Tasmota ESP32 firmware with PlatformIO and
boots it under an Espressif QEMU fork on an amd64 host — serial console always, and a
browsable web UI when the WiFi-capable fork is used.
It builds the current checkout (your local changes), not a fresh clone. Build
artifacts and the QEMU forks live under tasmota-qemu/ at the repo root (gitignored).
Examples below assume you run it from tools/qemu/; from the repo root use
tools/qemu/tasmota-qemu.sh … — either works, paths resolve the same.
Quick start
Networked Tasmota with a reachable web UI, in one command:
NET_WIFI=1 ./tasmota-qemu.sh # build -> image -> wifi QEMU -> boot, joins emulated AP
# then browse:
open http://localhost:8080
First run is slow: it clones Tasmota, pulls the toolchain, compiles the firmware, and clones + builds the QEMU fork. Subsequent runs are cached.
Serial-only (no network, stock QEMU, fastest path):
./tasmota-qemu.sh # stock fork, serial console only
Commands
./tasmota-qemu.sh [command]
build Compile Tasmota firmware (PlatformIO env $TASMOTA_ENV)
make-image Build the QEMU flash image (factory.bin, or merge raw parts)
qemu-setup Clone + build the chosen QEMU fork
run Boot the firmware in QEMU (serial tee'd to qemu.log)
smoketest Boot a minimal Arduino app (same IDF) to sanity-check QEMU itself
decode [log] Symbolize the last panic backtrace from a log (default: qemu.log)
all build -> make-image -> qemu-setup -> run (default)
Key environment variables
| Variable | Default | Meaning |
|---|---|---|
NET_WIFI |
0 |
1 = one-shot networked recipe (see below) |
WIFI_SSID |
Open Wifi |
AP the firmware joins when NET_WIFI=1 |
WIFI_PASS |
(empty) | password for that AP (the emulated one is open) |
NET_WIFI_ENV |
tasmota32solo1 |
env used by NET_WIFI=1 |
TASMOTA_ENV |
tasmota32 |
PlatformIO env (must be an ESP32 one) |
MINIMAL |
0 |
1 = build the stripped tasmota32-safeboot env |
QEMU_VARIANT |
stock |
stock (no WiFi) or wifi (Ebiroll/calib fork) |
FLASH_SIZE |
4MB |
2MB/4MB/8MB/16MB only |
WEB_PORT_HOST |
8080 |
host port forwarded to guest :80 |
USE_SYSTEM_QEMU |
0 |
1 = use qemu-system-xtensa from PATH |
INSTALL_DEPS |
0 |
1 = apt-get the QEMU build deps (needs sudo) |
How NET_WIFI=1 works
Getting a networked Tasmota into QEMU needs three things to line up; NET_WIFI=1
sets all of them:
- Survive early IDF init — the full dual-core
tasmota32build panics inesp_timer_impl_initunder QEMU (null interrupt vector). The unicoretasmota32solo1build does not, soNET_WIFI=1selects it. - Survive WiFi bring-up — Tasmota's WiFi blobs poke RF registers (
0x6003xxxx) that the stock QEMU does not emulate (LoadStorePIFAddrError). Thewififork (Ebiroll/calib) emulates them, soNET_WIFI=1forcesQEMU_VARIANT=wifi. - Get an IP the host can reach — that fork advertises one open AP named
Open Wifi.NET_WIFI=1writes station credentials into a managed block intasmota/user_config_override.h, so the firmware joins it in station mode, gets a slirp DHCP lease (10.0.2.15), and the:8080 -> :80port-forward becomes routable.
The credential block is delimited and idempotent — re-running rewrites only that block
and leaves the rest of user_config_override.h intact.
Debugging a panic
The run step boots with -no-reboot (QEMU exits on the first guest reset instead of
looping) and tees the serial console to tasmota-qemu/qemu.log. To symbolize the last
panic:
./tasmota-qemu.sh decode # decodes tasmota-qemu/qemu.log
./tasmota-qemu.sh decode some-other.log # decodes a specific log
decode strips the 0xffffffff sentinel frame, takes the PC of each pc:sp pair, and
runs addr2line against the matching firmware.elf. Frames in app flash
(0x4018xxxx+) are the useful ones. Addresses that resolve to ?? are inside the
closed-source WiFi/BT blobs (no symbols) — usually a sign the firmware touched radio
hardware the emulator doesn't model.
Is it QEMU or the firmware? Run ./tasmota-qemu.sh smoketest: it builds a minimal Arduino
app with the same IDF/toolchain and boots it. If the minimal app runs, QEMU is fine
and the problem is in the Tasmota build.
Web UI: only / loads, sub-pages give ERR_EMPTY_RESPONSE
This is expected on first use and is not a crash. Tasmota's referer check only
accepts a Referer whose host is its own hostname or local IP (10.0.2.15). Through
the host port-forward your browser sends Referer: http://localhost:8080/, whose host
is localhost — matching neither — so every page past / is dropped with an empty
response.
With NET_WIFI=1 this is handled automatically: a background watcher waits for the web
server to come up and issues SetOption128 1 for you, so the UI is clickable out of the
box. If you ever need to do it by hand (e.g. you ran a plain run on a fresh image):
./tasmota-qemu.sh fixui # sends SetOption128 1 (disable referer check)
The setting persists to flash. A fresh make-image wipes flash, but the next
NET_WIFI=1 run re-applies it automatically.
Caveats
- The
wififork is older than the firmware's IDF; its RF-register model is good enough to associate but is not a full WiFi stack. stock+ a real Tasmota build will boot to a serial console but the web UI will not respond (no WiFi emulation). UseNET_WIFI=1for network access.- Stop a running QEMU with
Ctrl-AthenX, or:for p in $(pgrep -f qemu-system-xtensa); do kill -9 $p; done