Version: 3.3.1
License: MIT
Author: Jason2866 (Johann Obermeier)
Repository: github.com/Jason2866/ESP_Flasher
- Overview
- Supported Chips
- Features
- Installation
- Usage
- Architecture
- Supported Flash Configurations
- Building Standalone Binaries
- CI/CD
- Troubleshooting
- License
ESP-Flasher (Tasmota-ESP-Flasher) is a cross-platform flashing tool designed specifically for flashing Tasmota firmware onto ESP8266 and ESP32-family microcontrollers. It provides both a graphical user interface (PyQt5) and a command line interface.
The tool wraps esptool functionality in a custom module (own_esptool.py) and automates the entire flashing workflow, including bootloader selection, partition table handling, safeboot image management, and ELF-to-binary conversion.
| Chip | ROM Loader Class | Image Chip ID |
|---|---|---|
| ESP8266 | ESP8266ROM |
0 |
| ESP32 | ESP32ROM |
0 |
| ESP32-S2 | ESP32S2ROM |
2 |
| ESP32-S3 | ESP32S3ROM |
9 |
| ESP32-C2 | ESP32C2ROM |
12 |
| ESP32-C3 | ESP32C3ROM |
5 |
| ESP32-C5 | ESP32C5ROM |
— |
| ESP32-C6 | ESP32C6ROM |
13 |
| ESP32-C61 | ESP32C61ROM |
— |
| ESP32-H2 | ESP32H2ROM |
16 |
| ESP32-P4 | ESP32P4ROM |
— |
- Auto-detection of connected ESP chip type and revision
- Automatic bootloader selection based on chip model, flash mode, and flash frequency
- Factory image support — detects and flashes Tasmota factory images directly
- Safeboot partition scheme — automatically downloads and includes safeboot firmware for ESP32-family chips
- ELF-to-binary conversion of bootloader files
- Configurable baud rate (default: 1,500,000 for ESP32, falls back to 115,200 if unsupported)
- Flash erase before writing (can be disabled with
--no-erase) - Serial log viewer after flashing or standalone via
--show-logs - Dark-themed GUI built with PyQt5 using the Fusion style
- Cross-platform — Windows, macOS (Intel + ARM), and Linux (X11 + Wayland)
Download the latest release for your operating system from the Releases page. Simply download, extract (if necessary), and run.
| Platform | Binary |
|---|---|
| Windows | ESP-Flasher.exe |
| macOS (Intel) | ESP-Flasher-macOS.app (tar archive) |
| macOS (ARM/Apple Silicon) | ESP-Flasher-macOSarm.app (tar archive) |
| Linux (Ubuntu) | ESP-Flasher (tar archive) |
pip install esp-flasherLaunch the GUI:
esp_flashergit clone https://github.com/Jason2866/ESP_Flasher.git
cd ESP_Flasher
pip install -e .Launch the GUI:
esp_flasherOr use the CLI:
esp_flasher -hRequirements (Python ≥ 3.8, < 4.0):
pyserial >= 3.5requests >= 2.24.0, < 3PyQt5 >= 5.15.10distro >= 1.9.0
On Linux, add your user to the dialout group to access serial ports:
sudo usermod -a -G dialout $(whoami)Log out and log back in for the change to take effect.
Run esp_flasher without any arguments to launch the graphical interface:
esp_flasherThe GUI provides:
- Serial Port — a dropdown to select the target port (with a Reload button)
- Firmware — a file browser to select a
.binfirmware file - Flash ESP — starts the flashing process
- View Logs — opens a serial monitor at 115200 baud
- Console — displays real-time output from the flashing process
esp_flasher [OPTIONS] BINARYExample:
esp_flasher --port /dev/ttyUSB0 tasmota.bin| Argument | Description | Default |
|---|---|---|
BINARY |
Path to the firmware binary file (required) | — |
-p, --port |
Serial port to use | Auto-detect |
--esp8266 |
Force ESP8266 chip type | Auto-detect |
--esp32 |
Force ESP32 chip type | Auto-detect |
--esp32s2 |
Force ESP32-S2 chip type | Auto-detect |
--esp32s3 |
Force ESP32-S3 chip type | Auto-detect |
--esp32c2 |
Force ESP32-C2 chip type | Auto-detect |
--esp32c3 |
Force ESP32-C3 chip type | Auto-detect |
--esp32c5 |
Force ESP32-C5 chip type | Auto-detect |
--esp32c6 |
Force ESP32-C6 chip type | Auto-detect |
--esp32c61 |
Force ESP32-C61 chip type | Auto-detect |
--esp32p4 |
Force ESP32-P4 chip type | Auto-detect |
--upload-baud-rate |
Baud rate for uploading | 1500000 |
--bootloader |
Custom bootloader ELF path/URL (ESP32x only) | Auto-download |
--safeboot |
Custom safeboot factory image path/URL (ESP32x only) | Auto-download |
--partitions |
Custom partition table path/URL (ESP32x only) | Auto-download |
--otadata |
Custom OTA data file path/URL (ESP32x only) | Auto-download |
--input |
Custom bootloader ELF file to flash (ESP32x only) | — |
--no-erase |
Do not erase flash before flashing | false |
--show-logs |
Only show serial logs (no flashing) | false |
ESP_Flasher/
├── esp_flasher/ # Main Python package
│ ├── __init__.py
│ ├── __main__.py # Entry point (CLI + GUI launcher)
│ ├── common.py # Chip info classes, flash configuration logic
│ ├── const.py # Constants, version, URLs
│ ├── gui.py # PyQt5 GUI implementation
│ ├── helpers.py # Utility functions
│ └── own_esptool.py # Custom esptool (ESP ROM loaders, flash operations)
├── bootloader/ # Pre-built bootloader ELF files per chip
│ ├── esp32/
│ ├── esp32c2/
│ ├── esp32c3/
│ ├── esp32c5/
│ ├── esp32c6/
│ ├── esp32c61/
│ ├── esp32h2/
│ ├── esp32p4/
│ ├── esp32p4rev3/
│ ├── esp32s2/
│ └── esp32s3/
├── partitions/ # Partition table binaries per chip
│ ├── boot_app0.bin
│ ├── partitions.esp32.bin
│ ├── partitions.esp32c2.bin
│ ├── partitions.esp32c3.bin
│ ├── partitions.esp32c5.bin
│ ├── partitions.esp32c6.bin
│ ├── partitions.esp32c61.bin
│ ├── partitions.esp32p4.bin
│ ├── partitions.esp32p4rev3.bin
│ ├── partitions.esp32s2.bin
│ └── partitions.esp32s3.bin
├── .github/workflows/ # CI/CD workflows
│ ├── build.yml # Build binaries for all platforms
│ └── build_pypi.yml # Publish to PyPI
├── setup.py # Package setup
├── requirements.txt # Runtime dependencies
├── requirements_build.txt # Build dependencies (PyInstaller)
├── ESP-Flasher.spec # PyInstaller spec file
├── icon.icns # macOS app icon
├── icon.ico # Windows app icon
└── README.md
The application entry point. If no command line arguments are provided, it launches the PyQt5 GUI. Otherwise, it runs the CLI flashing workflow:
- Parse arguments
- Select / auto-detect serial port
- Detect chip type and read chip info
- Run the stub loader
- Negotiate baud rate
- Detect flash size
- Configure flash arguments (bootloader, partitions, safeboot, firmware)
- Convert ELF bootloader to binary (if needed)
- Erase flash (unless
--no-erase) - Write flash
- Hard reset and show serial logs
Contains the core flashing logic:
ChipInfo/ESP32ChipInfo/ESP8266ChipInfo— Data classes holding chip metadata (model, MAC, features)read_chip_info()— Reads chip description and features using MCU-specific ROM methodsdetect_chip()— Connects to a chip on a serial port (auto-detect or forced type)configure_write_flash_args()— Builds the complete flash argument set:- Determines chip model and selects matching bootloader, partitions, and safeboot image
- Downloads remote files as needed (bootloaders, partitions, safeboot images)
- Caches bootloader ELF files locally in
~/ESP_Flasher_<version>/
read_firmware_info()— Parses firmware binary headers to extract flash mode, frequency, and detect factory imagesopen_downloadable_binary()— Opens firmware from local path, HTTP URL, or file-like object
A self-contained, customized version of Espressif's esptool (based on v3.6.0). It contains:
ESPLoader— Base class for all ESP ROM/stub communication- Chip-specific ROM loader classes for each supported chip family
- Stub loader classes for each chip
- Flash read/write/erase operations
- ELF-to-binary image conversion (
elf2image()) - Serial port detection (
get_port_list())
PyQt5-based GUI with:
- Dark Fusion theme
- Serial port selection with reload
- Firmware file browser (
.binfilter) - Flash and View Logs buttons
- Real-time console output via
RedirectText(redirectssys.stdout) FlashingThread— runs flashing in a background daemon thread- Automatic Qt platform detection (Cocoa/XCB/Wayland/Windows)
Application constants:
__version__— Current version string- Remote URLs for bootloader ELFs, partition tables, OTA data, and safeboot images
HTTP_REGEX— Pattern for validating HTTP(S) URLs
Utility function prevent_print() — temporarily suppresses stdout during esptool calls to hide internal debug output.
The following describes the complete flashing workflow for an ESP32-family chip with a non-factory firmware image:
- Chip Detection — The chip is detected via its magic value (
CHIP_DETECT_MAGIC_VALUE) or forced via CLI flag - Connection — The tool connects using
no_resetmode to support USB-CDC flashing (ESP32-S2) - Chip Info — Chip description, features, and MAC address are read via efuse registers
- Stub Loading — The ROM stub loader is uploaded and executed on the chip
- Baud Rate — If upload baud rate ≠ 115200, it attempts the higher rate and falls back if unsupported
- Flash Size Detection — Flash ID is read and mapped to a human-readable size
- Firmware Analysis — The firmware binary header is parsed to determine flash mode and frequency; if a safeboot image is found at offset
0x10000, it is flagged as a factory image - Resource Assembly (non-factory ESP32x only):
- Bootloader ELF is downloaded (or loaded from cache) and converted to binary via
elf2image() - Partition table is downloaded for the specific chip model
- OTA data (
boot_app0.bin) is downloaded - Safeboot image is downloaded from
ota.tasmota.com
- Bootloader ELF is downloaded (or loaded from cache) and converted to binary via
- Flash Layout (non-factory ESP32x):
0x0000/0x1000/0x2000— Bootloader (offset varies by chip)0x8000— Partition table0xE000— OTA data0x10000— Safeboot firmware0xE0000— User firmware
- Erase — Full flash erase (unless
--no-erase) - Write — All binary segments are written with compressed transfer
- Reset — Hard reset via RTS pin (or RTC WDT for USB-connected S2/S3 chips)
- Logs — Serial output is displayed at 115200 baud
The firmware type is detected by reading the first 4 bytes at two offsets:
| Offset | Magic Byte (0xE9) Found |
Result |
|---|---|---|
0x10000 |
Yes | Factory image — flashed as-is at offset 0x0 |
0x0 |
Yes | Standard firmware — full resource assembly required |
| Neither | — | Error: invalid firmware binary |
Bootloader ELF files are fetched from GitHub and cached locally:
~/ESP_Flasher_<version>/bootloader/<model>/bin/bootloader_<flash_mode>_<flash_freq>.elf
The ELF is converted to a raw binary using the built-in elf2image() function. The resulting .bin is then written to flash at the chip-specific bootloader offset.
Partition tables and safeboot images are downloaded on every flash operation from:
- Partitions:
https://raw.githubusercontent.com/Jason2866/ESP_Flasher/factory/partitions/partitions.<model>.bin - Safeboot:
https://ota.tasmota.com/tasmota32/tasmota32<variant>-safeboot.bin - OTA Data:
https://raw.githubusercontent.com/Jason2866/ESP_Flasher/factory/partitions/boot_app0.bin
| ID | Size | ID | Size |
|---|---|---|---|
0x12 |
256KB | 0x19 |
32MB |
0x13 |
512KB | 0x1A |
64MB |
0x14 |
1MB | 0x1B |
128MB |
0x15 |
2MB | 0x1C |
256MB |
0x16 |
4MB | 0x20 |
64MB |
0x17 |
8MB | 0x21 |
128MB |
0x18 |
16MB | 0x22 |
256MB |
| Mode | Value |
|---|---|
| QIO | 0x00 |
| QOUT | 0x01 |
| DIO | 0x02 |
| DOUT | 0x03 |
| Frequency | Value (ESP32) | Value (ESP8266) |
|---|---|---|
| 40MHz | 0x0 |
0x0 |
| 26MHz | 0x1 |
0x1 |
| 20MHz | 0x2 |
0x2 |
| 80MHz | 0xF |
0xF |
| Chip Model | Bootloader Offset | Notes |
|---|---|---|
| ESP8266 | 0x0000 |
— |
| ESP32 | 0x1000 |
— |
| ESP32-S2 | 0x1000 |
— |
| ESP32-S3 | 0x0000 |
— |
| ESP32-C2 | 0x0000 |
Flash freq forced to 60MHz |
| ESP32-C3 | 0x0000 |
— |
| ESP32-C5 | 0x2000 |
— |
| ESP32-C6 | 0x0000 |
Flash freq forced to 80MHz |
| ESP32-C61 | 0x0000 |
Flash freq forced to 80MHz |
| ESP32-P4 | 0x2000 |
Rev ≥ 3.0 uses esp32p4rev3 model |
Each chip class defines a MEMORY_MAP with regions such as DROM, IROM, DRAM, IRAM, RTC_DATA, etc. These are used internally for ELF segment placement during elf2image() conversion.
Build dependencies:
pip install -r requirements.txt -r requirements_build.txt
pip install -e .pyinstaller -F -w -n ESP-Flasher -i icon.icns esp_flasher/__main__.pyOr using a virtual environment:
/<path>/ESP_Flasher/.venv/bin/pyinstaller -F -w -n ESP-Flasher -i icon.icns esp_flasher/__main__.pyThe output is located at dist/ESP-Flasher.app.
Same command as macOS Intel — PyInstaller builds for the native architecture.
python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.ico esp_flasher\__main__.pyThe output is located at dist\ESP-Flasher.exe.
sudo apt install libnotify-dev libsdl2-dev
python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.ico esp_flasher/__main__.pyThe output is located at dist/ESP-Flasher.
The GitHub Actions workflow (.github/workflows/build.yml) is triggered on:
- Push of version tags (
v*.*.*) - Push to the
factorybranch - Pull requests to
factory - Manual workflow dispatch
It builds binaries for four platforms in parallel:
| Job | Runner | Python | Output |
|---|---|---|---|
build-windows |
windows-latest |
3.13 | ESP-Flasher.exe |
build-ubuntu |
ubuntu-latest |
3.13 | ESP-Flasher (tar) |
build-macos |
macos-15-intel |
3.13 | ESP-Flasher-macOS.app (tar) |
build-macos-arm |
macos-15 |
3.13 | ESP-Flasher-macOSarm.app (tar) |
On tagged releases, a release job uploads all artifacts to the GitHub Release.
The workflow .github/workflows/build_pypi.yml builds an sdist and wheel, then publishes to PyPI using the PYPI_API_TOKEN secret. Triggered on version tags or manual dispatch.
| Issue | Solution |
|---|---|
| No serial port found | Check cable, install drivers, and on Linux add user to dialout group |
| Antivirus flags the binary | This is a known false positive with PyInstaller |
| ESP not in flash boot mode | Hold the BOOT/FLASH button on the board while connecting |
| Baud rate not supported | The tool automatically falls back to 115200 baud |
| macOS driver issues | Install the Silicon Labs VCP Driver |
| Wrong boot mode detected | Re-plug the device while holding the boot pin pressed |
| No bootloader for flash frequency | Frequencies 15MHz, 20MHz, 26MHz, and 30MHz are not supported for Tasmota bootloaders |
| Invalid firmware binary | Ensure the file starts with magic byte 0xE9 (ESP image format) |
MIT License © Otto Winter, Michael Kandziora, Johann Obermeier
The embedded own_esptool.py is based on esptool by Espressif Systems, licensed under GPL-2.0-or-later.