Web Flasher¶
You can flash the firmware directly from your browser — no toolchain, no
cargo, nothing to install. It serves both supported firmwares — the classic
ESP32 (e-paper) build and the JC3248W535C touchscreen (ESP32-S3) build — and
automatically picks the right one for the connected chip.
Note
The Web Flasher uses the Web Serial API, which is currently supported by Google Chrome and Microsoft Edge on desktop. It will not work in Firefox or Safari, or on mobile.
Steps¶
Open the Web Flasher: f321x.github.io/offline-bitcoin-atm.
Connect your ESP32 to the computer over USB.
Click Install Firmware and select the serial port for your ESP32.
Wait for the flash to complete.
The flasher always serves the firmware from the latest GitHub release.
After flashing, the ATM starts a WiFi access point for initial configuration — continue with Configuration.
Troubleshooting¶
Stuck on “Preparing installation”¶
That screen covers chip detection. If it never advances and never shows an error, the chip did not enter ROM download mode.
The JC3248W535C (ESP32-S3) has no USB-serial bridge — its USB-C port is wired directly to the chip’s built-in USB-Serial/JTAG controller (GPIO19/GPIO20). That controller needs a different reset pulse train than a CH340/CP210x bridge, and the browser only picks it when it can read the port’s USB IDs. When it guesses wrong the chip keeps running the old firmware, which never drains the controller’s RX FIFO, and the flasher’s write blocks with no timeout.
Force download mode by hand:
Hold BOOT — the inner of the two small buttons on the board’s back edge.
Tap RST next to it.
Keep BOOT held while you click Install; release it once flashing starts.
Power the board from a USB-A port. Its USB-C receptacle has no CC pull-down resistors, so a C-to-C cable into a real USB-C host may never enable VBUS.
Linux notes¶
ModemManagerprobes every new/dev/ttyACM*with AT commands and can hold the port. Exempt Espressif devices:printf 'SUBSYSTEM=="usb", ATTRS{idVendor}=="303a", ENV{ID_MM_DEVICE_IGNORE}="1"\n' \ | sudo tee /etc/udev/rules.d/99-espressif.rules sudo udevadm control --reload && sudo udevadm trigger
A Flatpak or Snap browser cannot read the udev database, so the serial port shows up with no USB vendor/product ID and the flasher silently falls back to the wrong reset sequence. Use a distro-packaged Chrome or Chromium. Check what the browser actually sees at
chrome://device-log, or run(await navigator.serial.getPorts()).map(p => p.getInfo())in the console — it should reportusbVendorId: 12346, usbProductId: 4097.usbguardre-evaluates its policy when the chip re-enumerates during reset. Rules pinned to a device hash orvia-portcan stop matching mid-flash; preferusbguard append-rule 'allow id 303a:1001'.
If none of that helps, flash from a terminal instead — see Manual Build & Flash.