├── .github └── workflows │ ├── build.yml │ └── build_pypi.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── bootloader ├── esp32 │ └── bin │ │ ├── bootloader_dio_40m.elf │ │ ├── bootloader_dio_80m.elf │ │ ├── bootloader_qio_40m.elf │ │ └── bootloader_qio_80m.elf ├── esp32c2 │ └── bin │ │ ├── bootloader_dio_60m.elf │ │ └── bootloader_qio_60m.elf ├── esp32c3 │ └── bin │ │ ├── bootloader_dio_40m.elf │ │ ├── bootloader_dio_80m.elf │ │ ├── bootloader_qio_40m.elf │ │ └── bootloader_qio_80m.elf ├── esp32c5 │ └── bin │ │ ├── bootloader_dio_80m.elf │ │ └── bootloader_qio_80m.elf ├── esp32c6 │ └── bin │ │ ├── bootloader_dio_80m.elf │ │ └── bootloader_qio_80m.elf ├── esp32h2 │ └── bin │ │ ├── bootloader_dio_64m.elf │ │ └── bootloader_qio_64m.elf ├── esp32s2 │ └── bin │ │ ├── bootloader_dio_40m.elf │ │ ├── bootloader_dio_80m.elf │ │ ├── bootloader_qio_40m.elf │ │ └── bootloader_qio_80m.elf └── esp32s3 │ └── bin │ ├── bootloader_dio_120m.elf │ ├── bootloader_dio_80m.elf │ ├── bootloader_dout_80m.elf │ ├── bootloader_opi_80m.elf │ ├── bootloader_qio_120m.elf │ └── bootloader_qio_80m.elf ├── build-instructions.md ├── esp_flasher ├── __init__.py ├── __main__.py ├── common.py ├── const.py ├── gui.py ├── helpers.py └── own_esptool.py ├── icon.icns ├── icon.ico ├── partitions ├── boot_app0.bin ├── partitions.bin ├── partitions.esp32.bin ├── partitions.esp32c2.bin ├── partitions.esp32c3.bin ├── partitions.esp32c5.bin ├── partitions.esp32c6.bin ├── partitions.esp32s2.bin └── partitions.esp32s3.bin ├── requirements.txt ├── requirements_build.txt └── setup.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Tasmota Esp Flasher 2 | 3 | on: 4 | workflow_dispatch: # Manually start a workflow 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | branches: 9 | - factory 10 | paths-ignore: 11 | - '.github/**' # Ignore changes towards the .github directory 12 | - '*.md' 13 | pull_request: 14 | branches: 15 | - factory 16 | 17 | jobs: 18 | build-windows: 19 | runs-on: windows-2019 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Install Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: '3.9' 27 | architecture: 'x64' 28 | - name: Install requirements 29 | run: | 30 | pip install -r requirements.txt -r requirements_build.txt 31 | pip install -e . 32 | - name: Check if the installed versions can run 33 | run: | 34 | esp_flasher -h 35 | - name: Run PyInstaller 36 | run: | 37 | python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.ico esp_flasher\__main__.py 38 | - name: Test binary 39 | shell: bash 40 | run: | 41 | dist/ESP-Flasher.exe -h 42 | - uses: actions/upload-artifact@v4 43 | with: 44 | name: Windows 45 | path: dist/ESP-Flasher.exe 46 | 47 | build-ubuntu: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | - name: Install Python 53 | uses: actions/setup-python@v5 54 | with: 55 | python-version: '3.9' 56 | - name: Install dependencies 57 | run: | 58 | sudo apt update 59 | sudo apt install libnotify-dev libsdl2-dev 60 | - name: Install requirements 61 | run: | 62 | pip install -r requirements.txt -r requirements_build.txt 63 | pip install -e . 64 | - name: Check if the installed versions can run 65 | run: | 66 | esp_flasher -h 67 | - name: Run PyInstaller 68 | run: | 69 | python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.ico esp_flasher/__main__.py 70 | - name: Test binary 71 | shell: bash 72 | run: | 73 | dist/ESP-Flasher -h 74 | - name: Move app 75 | run: | 76 | mv dist/ESP-Flasher ESP-Flasher 77 | - name: 'Tar files' 78 | run: tar -cvf Ubuntu.tar ESP-Flasher 79 | - uses: actions/upload-artifact@v4 80 | with: 81 | name: Ubuntu 82 | path: Ubuntu.tar 83 | 84 | build-macos: 85 | runs-on: macos-13 86 | steps: 87 | - name: Checkout 88 | uses: actions/checkout@v4 89 | - name: Install Python 90 | uses: actions/setup-python@v5 91 | with: 92 | python-version: '3.9' 93 | - name: Install requirements 94 | run: | 95 | pip install -r requirements.txt -r requirements_build.txt 96 | pip install -e . 97 | - name: Check if the installed versions can run 98 | run: | 99 | esp_flasher -h 100 | - name: Run PyInstaller 101 | run: | 102 | python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.icns esp_flasher/__main__.py 103 | - name: Test binary 104 | shell: bash 105 | run: | 106 | dist/ESP-Flasher -h 107 | - name: Move app 108 | run: | 109 | mv dist/ESP-Flasher.app ESP-Flasher-macOS.app 110 | - name: 'Tar files' 111 | run: tar -cvf macOS.tar ESP-Flasher-macOS.app 112 | - name: 'Upload Artifact' 113 | uses: actions/upload-artifact@v4 114 | with: 115 | name: macOS 116 | path: macOS.tar 117 | 118 | build-macos-arm: 119 | runs-on: macos-14 120 | steps: 121 | - name: Checkout 122 | uses: actions/checkout@v4 123 | - name: Install Python 124 | uses: actions/setup-python@v5 125 | with: 126 | python-version: '3.11' 127 | - name: Install requirements 128 | run: | 129 | pip install -r requirements.txt -r requirements_build.txt 130 | pip install -e . 131 | - name: Check if the installed versions can run 132 | run: | 133 | esp_flasher -h 134 | - name: Run PyInstaller 135 | run: | 136 | python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.icns esp_flasher/__main__.py 137 | - name: Test binary 138 | shell: bash 139 | run: | 140 | dist/ESP-Flasher -h 141 | - name: Move app 142 | run: | 143 | mv dist/ESP-Flasher.app ESP-Flasher-macOSarm.app 144 | - name: 'Tar files' 145 | run: tar -cvf macOSarm.tar ESP-Flasher-macOSarm.app 146 | - name: 'Upload Artifact' 147 | uses: actions/upload-artifact@v4 148 | with: 149 | name: macOSarm 150 | path: macOSarm.tar 151 | 152 | release: 153 | name: Upload binaries to release section 154 | needs: [build-windows, build-ubuntu, build-macos, build-macos-arm] 155 | if: startsWith(github.ref, 'refs/tags/') 156 | runs-on: ubuntu-latest 157 | steps: 158 | - name: Checkout repository 159 | uses: actions/checkout@v4 160 | with: 161 | ref: factory 162 | - name: Download built binaries artifacts 163 | uses: actions/download-artifact@v4 164 | with: 165 | path: binary 166 | merge-multiple: true 167 | - name: Display downloaded artifact files 168 | run: | 169 | ls -R ./ 170 | - name: Release 171 | uses: jason2866/action-gh-release@v1.3 172 | with: 173 | prerelease: false 174 | files: | 175 | binary/* 176 | env: 177 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 178 | -------------------------------------------------------------------------------- /.github/workflows/build_pypi.yml: -------------------------------------------------------------------------------- 1 | name: PyPi Publish 2 | 3 | on: 4 | workflow_dispatch: # Manually start Publishing 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | 9 | jobs: 10 | 11 | build-pypi: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - name: Install Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: '3.9' 20 | - name: Install dependencies 21 | run: | 22 | sudo apt-get update 23 | sudo apt install libnotify-dev libsdl2-dev 24 | - name: Install requirements 25 | run: | 26 | pip install -r requirements.txt -r requirements_build.txt 27 | pip install -e . 28 | - name: Run sdist 29 | run: python setup.py sdist bdist_wheel 30 | - name: See dist directory 31 | run: ls dist 32 | - uses: actions/upload-artifact@v4 33 | with: 34 | name: sdist 35 | path: dist/esp_flasher-*.tar.gz 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: bdist_wheel 39 | path: dist/esp_flasher-*.whl 40 | - name: Publish a Python distribution to PyPI 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | with: 43 | user: __token__ 44 | password: ${{ secrets.PYPI_API_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | config/ 107 | 108 | .DS_Store 109 | /.idea/ 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Johann Obermeier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include requirements.txt 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build_special_firmware](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) 3 | 4 | # Tasmota-ESP-Flasher for Tasmota v13 and later (Safeboot partition scheme) 5 | 6 | [![GitHub Releases](https://img.shields.io/github/downloads/Jason2866/ESP_Flasher/total?label=downloads&color=%231FA3EC&style=for-the-badge)](https://github.com/Jason2866/ESP_Flasher/releases/latest) 7 | 8 | Tasmota-ESP-Flasher is an app for ESP8266 / ESP32 designed to make flashing Tasmota on ESPs as simple as possible by: 9 | 10 | * Pre-built binaries for most used operating systems 11 | * Support for Tasmota factory images 12 | * Hiding all non-essential options for flashing 13 | * All necessary options (bootloader, flash mode, safeboot) are set automatically 14 | * Flashing is lightning fast 15 | 16 | The flashing process is done using [esptool](https://github.com/espressif/esptool) from espressif. 17 | 18 | ## Installation 19 | 20 | - Check the [releases section](https://github.com/Jason2866/ESP_Flasher/releases) for your OS. 21 | - Download and double-click and it'll start. 22 | 23 | - The native Python version can be installed from PyPI: **`pip install esp-flasher`**. 24 | Start the GUI by `esp_flasher`. Alternatively, you can use the command line interface ( type `esp_flasher -h` for info) 25 | 26 | In the odd case of your antivirus going haywire over that application, it's a [false positive.](https://github.com/pyinstaller/pyinstaller/issues/3802) 27 | 28 | ## Build it yourself 29 | 30 | If you want to build this application yourself you need to: 31 | 32 | - Install Python 3.x 33 | - Download this project and run `pip3 install -e .` in the project's root. 34 | - Start the GUI using `esp_flasher`. Alternatively, you can use the command line interface ( 35 | type `esp_flasher -h` for info) 36 | 37 | ### Mac OSX (compiled binary only for 11 and newer) 38 | 39 | Driver maybe needed for Mac OSx. 40 | 41 | Info: https://www.silabs.com/community/interface/forum.topic.html/vcp_driver_for_macosbigsur110x-krlP 42 | 43 | Driver: https://www.silabs.com/documents/public/software/Mac_OSX_VCP_Driver.zip 44 | 45 | ## License 46 | 47 | [MIT](http://opensource.org/licenses/MIT) © Otto Winter, Michael Kandziora, Johann Obermeier 48 | 49 | ### Powered by 50 | [![CLion logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport) 51 | -------------------------------------------------------------------------------- /bootloader/esp32/bin/bootloader_dio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32/bin/bootloader_dio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32/bin/bootloader_qio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32/bin/bootloader_qio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c2/bin/bootloader_dio_60m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c2/bin/bootloader_dio_60m.elf -------------------------------------------------------------------------------- /bootloader/esp32c2/bin/bootloader_qio_60m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c2/bin/bootloader_qio_60m.elf -------------------------------------------------------------------------------- /bootloader/esp32c3/bin/bootloader_dio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c3/bin/bootloader_dio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32c3/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c3/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c3/bin/bootloader_qio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c3/bin/bootloader_qio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32c3/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c3/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c5/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c5/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c5/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c5/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c6/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c6/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32c6/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32c6/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32h2/bin/bootloader_dio_64m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32h2/bin/bootloader_dio_64m.elf -------------------------------------------------------------------------------- /bootloader/esp32h2/bin/bootloader_qio_64m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32h2/bin/bootloader_qio_64m.elf -------------------------------------------------------------------------------- /bootloader/esp32s2/bin/bootloader_dio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s2/bin/bootloader_dio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32s2/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s2/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32s2/bin/bootloader_qio_40m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s2/bin/bootloader_qio_40m.elf -------------------------------------------------------------------------------- /bootloader/esp32s2/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s2/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_dio_120m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_dio_120m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_dio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_dio_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_dout_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_dout_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_opi_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_opi_80m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_qio_120m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_qio_120m.elf -------------------------------------------------------------------------------- /bootloader/esp32s3/bin/bootloader_qio_80m.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/bootloader/esp32s3/bin/bootloader_qio_80m.elf -------------------------------------------------------------------------------- /build-instructions.md: -------------------------------------------------------------------------------- 1 | # macOS 2 | 3 | `pyinstaller -F -w -n ESP-Flasher -i icon.icns esp_flasher/__main__.py` 4 | 5 | # Windows 6 | 7 | 1. Start up VM 8 | 2. Install Python (3) from App Store 9 | 3. Download esp-flasher from GitHub 10 | 4. `pip install -e.` and `pip install pyinstaller` 11 | 5. Check with `python -m esp_flasher.__main__` 12 | 6. `python -m PyInstaller.__main__ -F -w -n ESP-Flasher -i icon.ico esp_flasher\__main__.py` 13 | 7. Go to `dist` folder, check ESP-Flasher.exe works. 14 | -------------------------------------------------------------------------------- /esp_flasher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/esp_flasher/__init__.py -------------------------------------------------------------------------------- /esp_flasher/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import argparse 4 | from datetime import datetime 5 | import sys 6 | import time 7 | 8 | import esp_flasher.own_esptool as esptool 9 | from esp_flasher.own_esptool import get_port_list 10 | 11 | import serial 12 | 13 | from esp_flasher import const 14 | from esp_flasher.common import ( 15 | ESP32ChipInfo, 16 | Esp_flasherError, 17 | chip_run_stub, 18 | configure_write_flash_args, 19 | detect_chip, 20 | detect_flash_size, 21 | read_chip_info, 22 | ) 23 | from esp_flasher.const import ( 24 | ESP32_DEFAULT_BOOTLOADER_FORMAT, 25 | ESP32_DEFAULT_OTA_DATA, 26 | ESP32_SAFEBOOT_SERVER 27 | ) 28 | 29 | def parse_args(argv): 30 | parser = argparse.ArgumentParser(prog=f"esp_flasher {const.__version__}") 31 | parser.add_argument("-p", "--port", help="Select the USB/COM port for uploading.") 32 | group = parser.add_mutually_exclusive_group(required=False) 33 | group.add_argument("--esp8266", action="store_true") 34 | group.add_argument("--esp32", action="store_true") 35 | group.add_argument("--esp32s2", action="store_true") 36 | group.add_argument("--esp32s3", action="store_true") 37 | group.add_argument("--esp32c2", action="store_true") 38 | group.add_argument("--esp32c3", action="store_true") 39 | group.add_argument("--esp32c5", action="store_true") 40 | group.add_argument("--esp32c6", action="store_true") 41 | group.add_argument("--esp32p4", action="store_true") 42 | group.add_argument( 43 | "--upload-baud-rate", 44 | type=int, 45 | default=1500000, 46 | help="Baud rate to upload (not for logging)", 47 | ) 48 | parser.add_argument( 49 | "--bootloader", 50 | help="(ESP32x-only) The bootloader to flash.", 51 | default=ESP32_DEFAULT_BOOTLOADER_FORMAT, 52 | ) 53 | parser.add_argument( 54 | "--safeboot", 55 | help="(ESP32x-only) The safeboot factory image to flash.", 56 | ) 57 | parser.add_argument( 58 | "--input", 59 | help="(ESP32x-only) The bootloader elf file to flash.", 60 | ) 61 | parser.add_argument( 62 | "--partitions", 63 | help="(ESP32x-only) The partitions to flash.", 64 | ) 65 | parser.add_argument( 66 | "--otadata", 67 | help="(ESP32x-only) The otadata file to flash.", 68 | default=ESP32_DEFAULT_OTA_DATA, 69 | ) 70 | parser.add_argument( 71 | "--no-erase", help="Do not erase flash before flashing", action="store_true" 72 | ) 73 | parser.add_argument("--show-logs", help="Only show logs", action="store_true") 74 | parser.add_argument("binary", help="The binary image to flash.") 75 | 76 | return parser.parse_args(argv[1:]) 77 | 78 | 79 | def select_port(args): 80 | if args.port is not None: 81 | print(f"Using '{args.port}' as serial port.") 82 | return args.port 83 | ports = get_port_list() 84 | if not ports: 85 | raise Esp_flasherError("No serial port found!") 86 | if len(ports) != 1: 87 | print("Found more than one serial port:") 88 | for port in ports: 89 | print(f" * {port}") 90 | print("Please choose one with the --port argument.") 91 | raise Esp_flasherError 92 | print(f"Auto-detected serial port: {ports}") 93 | return ports 94 | 95 | 96 | def show_logs(serial_port): 97 | print("Showing logs:") 98 | with serial_port: 99 | while True: 100 | try: 101 | raw = serial_port.readline() 102 | except serial.SerialException: 103 | print("Serial port closed!") 104 | return 105 | text = raw.decode(errors="ignore") 106 | line = text.replace("\r", "").replace("\n", "") 107 | time_ = datetime.now().time().strftime("[%H:%M:%S]") 108 | message = time_ + line 109 | try: 110 | print(message) 111 | except UnicodeEncodeError: 112 | print(message.encode("ascii", "backslashreplace")) 113 | 114 | 115 | def run_esp_flasher(argv): 116 | args = parse_args(argv) 117 | port = select_port(args) 118 | 119 | if args.show_logs: 120 | serial_port = serial.Serial(port, baudrate=115200) 121 | show_logs(serial_port) 122 | return 123 | 124 | try: 125 | # pylint: disable=consider-using-with 126 | firmware = open(args.binary, "rb") 127 | except IOError as err: 128 | raise Esp_flasherError(f"Error opening binary: {err}") from err 129 | chip = detect_chip(port, args.esp8266, args.esp32) 130 | info = read_chip_info(chip) 131 | 132 | print() 133 | print("Chip Info:") 134 | print(f" - Chip Family: {info.family}") 135 | print(f" - Chip Model: {info.model}") 136 | if isinstance(info, ESP32ChipInfo): 137 | print(f" - Number of Cores: {info.num_cores}") 138 | print(f" - Max CPU Frequency: {info.cpu_frequency}") 139 | print(f" - Has Bluetooth: {'YES' if info.has_bluetooth else 'NO'}") 140 | print(f" - Has Embedded Flash: {'YES' if info.has_embedded_flash else 'NO'}") 141 | print( 142 | f" - Has Factory-Calibrated ADC: {'YES' if info.has_factory_calibrated_adc else 'NO'}" 143 | ) 144 | else: 145 | print(f" - Chip ID: {info.chip_id:08X}") 146 | 147 | print(f" - MAC Address: {info.mac}") 148 | 149 | stub_chip = chip_run_stub(chip) 150 | flash_size = None 151 | 152 | if (args.upload_baud_rate != 115200) and ("ESP32" in info.family): 153 | try: 154 | stub_chip.change_baud(args.upload_baud_rate) 155 | except esptool.FatalError as err: 156 | raise Esp_flasherError( 157 | f"Error changing ESP upload baud rate: {err}" 158 | ) from err 159 | 160 | # Check if the higher baud rate works 161 | try: 162 | flash_size = detect_flash_size(stub_chip) 163 | except Esp_flasherError: 164 | # Go back to old baud rate by recreating chip instance 165 | print( 166 | f"Chip does not support baud rate {args.upload_baud_rate}, changing to 115200" 167 | ) 168 | # pylint: disable=protected-access 169 | stub_chip._port.close() 170 | chip = detect_chip(port, args.esp8266, args.esp32) 171 | stub_chip = chip_run_stub(chip) 172 | 173 | if flash_size is None: 174 | flash_size = detect_flash_size(stub_chip) 175 | 176 | print(f" - Flash Size: {flash_size}") 177 | 178 | flag_factory = False 179 | min_rev = 0 180 | min_rev_full = 0 181 | max_rev_full = 65535 182 | secure_pad = "False" 183 | secure_pad_v2 = "False" 184 | elf_sha256_offset = "" 185 | use_segments = "" 186 | flash_mmu_page_size = "" 187 | pad_to_size = "" 188 | spi_connection = "" 189 | output = "" 190 | 191 | mock_args = configure_write_flash_args( 192 | info, chip, flag_factory, args.safeboot, firmware, flash_size, args.bootloader, args.partitions, args.otadata, 193 | args.input, secure_pad, secure_pad_v2, min_rev, min_rev_full, max_rev_full, elf_sha256_offset, 194 | use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output 195 | ) 196 | if (not "ESP8266" in info.family) and (not mock_args.flag_factory): 197 | try: 198 | esptool.elf2image(mock_args) 199 | except esptool.FatalError as err: 200 | raise Esp_flasherError(f"Error while converting elf to bin: {err}") from err 201 | 202 | mock_args = configure_write_flash_args( 203 | info, chip, flag_factory, args.safeboot, firmware, flash_size, args.bootloader, args.partitions, args.otadata, 204 | args.input, secure_pad, secure_pad_v2, min_rev, min_rev_full, max_rev_full, elf_sha256_offset, 205 | use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output 206 | ) 207 | 208 | #print(f" - Flash Mode: {mock_args.flash_mode}") 209 | #print(f" - Flash Frequency: {mock_args.flash_freq.upper()}Hz") 210 | 211 | try: 212 | stub_chip.flash_set_parameters(esptool.flash_size_bytes(flash_size)) 213 | except esptool.FatalError as err: 214 | raise Esp_flasherError(f"Error setting flash parameters: {err}") from err 215 | 216 | if not args.no_erase: 217 | try: 218 | esptool.erase_flash(stub_chip, mock_args) 219 | except esptool.FatalError as err: 220 | raise Esp_flasherError(f"Error while erasing flash: {err}") from err 221 | 222 | try: 223 | esptool.write_flash(stub_chip, mock_args) 224 | except esptool.FatalError as err: 225 | raise Esp_flasherError(f"Error while writing flash: {err}") from err 226 | 227 | print("Hard Resetting...") 228 | stub_chip.hard_reset() 229 | 230 | print("Done! Flashing is complete!") 231 | print() 232 | 233 | if args.upload_baud_rate != 115200: 234 | # pylint: disable=protected-access 235 | stub_chip._port.baudrate = 115200 236 | time.sleep(0.05) # get rid of crap sent during baud rate change 237 | # pylint: disable=protected-access 238 | stub_chip._port.flushInput() 239 | 240 | # pylint: disable=protected-access 241 | show_logs(stub_chip._port) 242 | 243 | 244 | def main(): 245 | try: 246 | if len(sys.argv) <= 1: 247 | from esp_flasher import gui 248 | 249 | return gui.main() or 0 250 | return run_esp_flasher(sys.argv) or 0 251 | except Esp_flasherError as err: 252 | msg = str(err) 253 | if msg: 254 | print(msg) 255 | return 1 256 | except KeyboardInterrupt: 257 | return 1 258 | 259 | 260 | if __name__ == "__main__": 261 | sys.exit(main()) 262 | -------------------------------------------------------------------------------- /esp_flasher/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | import io 3 | import struct 4 | from os.path import join 5 | from io import BytesIO 6 | 7 | import esp_flasher.own_esptool as esptool 8 | 9 | from esp_flasher.const import ( 10 | ESP32_DEFAULT_PARTITIONS, 11 | ESP32_SAFEBOOT_SERVER, 12 | HTTP_REGEX, 13 | __version__, 14 | ) 15 | from esp_flasher.helpers import prevent_print 16 | 17 | 18 | class Esp_flasherError(Exception): 19 | pass 20 | 21 | 22 | class MockEsptoolArgs: 23 | def __init__(self, chip, flag_factory, flash_size, addr_filename, flash_mode, flash_freq, input, secure_pad, secure_pad_v2, 24 | min_rev, min_rev_full, max_rev_full, elf_sha256_offset, use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output): 25 | self.compress = True 26 | self.chip = chip 27 | self.flag_factory = flag_factory 28 | self.no_compress = False 29 | self.flash_size = flash_size 30 | self.addr_filename = addr_filename 31 | self.flash_mode = flash_mode 32 | self.flash_freq = flash_freq 33 | self.no_stub = False 34 | self.verify = False 35 | self.erase_all = False 36 | self.encrypt = False 37 | self.encrypt_files = None 38 | self.input = input 39 | self.secure_pad = False 40 | self.secure_pad_v2 = False 41 | self.min_rev = 0 42 | self.min_rev_full = 0 43 | self.max_rev_full = 65535 44 | self.elf_sha256_offset = "" 45 | self.use_segments = "False" 46 | self.flash_mmu_page_size = "" 47 | self.pad_to_size = "" 48 | self.spi_connection = "" 49 | self.output = output 50 | 51 | class ChipInfo: 52 | def __init__(self, family, model, mac): 53 | self.family = family 54 | self.model = model 55 | self.mac = mac 56 | self.is_esp32 = None 57 | 58 | def as_dict(self): 59 | return { 60 | "family": self.family, 61 | "model": self.model, 62 | "mac": self.mac, 63 | "is_esp32": self.is_esp32, 64 | } 65 | 66 | 67 | class ESP32ChipInfo(ChipInfo): 68 | def __init__( 69 | self, 70 | model, 71 | mac, 72 | num_cores, 73 | cpu_frequency, 74 | has_bluetooth, 75 | has_embedded_flash, 76 | has_factory_calibrated_adc, 77 | ): 78 | super().__init__("ESP32", model, mac) 79 | self.num_cores = num_cores 80 | self.cpu_frequency = cpu_frequency 81 | self.has_bluetooth = has_bluetooth 82 | self.has_embedded_flash = has_embedded_flash 83 | self.has_factory_calibrated_adc = has_factory_calibrated_adc 84 | 85 | def as_dict(self): 86 | data = ChipInfo.as_dict(self) 87 | data.update( 88 | { 89 | "num_cores": self.num_cores, 90 | "cpu_frequency": self.cpu_frequency, 91 | "has_bluetooth": self.has_bluetooth, 92 | "has_embedded_flash": self.has_embedded_flash, 93 | "has_factory_calibrated_adc": self.has_factory_calibrated_adc, 94 | } 95 | ) 96 | return data 97 | 98 | 99 | class ESP8266ChipInfo(ChipInfo): 100 | def __init__(self, model, mac, chip_id): 101 | super().__init__("ESP8266", model, mac) 102 | self.chip_id = chip_id 103 | 104 | def as_dict(self): 105 | data = ChipInfo.as_dict(self) 106 | data.update( 107 | { 108 | "chip_id": self.chip_id, 109 | } 110 | ) 111 | return data 112 | 113 | 114 | def read_chip_property(func, *args, **kwargs): 115 | try: 116 | return prevent_print(func, *args, **kwargs) 117 | except esptool.FatalError as err: 118 | raise Esp_flasherError(f"Reading chip details failed: {err}") from err 119 | 120 | 121 | def read_chip_info(chip): 122 | mac = ":".join(f"{x:02X}" for x in read_chip_property(chip.read_mac)) 123 | if isinstance(chip, esptool.ESP32ROM): 124 | model = read_chip_property(chip.get_chip_description) 125 | features = read_chip_property(chip.get_chip_features) 126 | num_cores = 2 if "Dual Core" in features else 1 127 | frequency = next((x for x in ("160MHz", "240MHz") if x in features), "80MHz") 128 | has_bluetooth = "BLE" in features or "BT" in features or "BT 5" in features 129 | has_embedded_flash = "Embedded Flash" in features 130 | has_factory_calibrated_adc = "VRef calibration in efuse" in features 131 | return ESP32ChipInfo( 132 | model, 133 | mac, 134 | num_cores, 135 | frequency, 136 | has_bluetooth, 137 | has_embedded_flash, 138 | has_factory_calibrated_adc, 139 | ) 140 | if isinstance(chip, esptool.ESP8266ROM): 141 | model = read_chip_property(chip.get_chip_description) 142 | chip_id = read_chip_property(chip.chip_id) 143 | return ESP8266ChipInfo(model, mac, chip_id) 144 | raise Esp_flasherError(f"Unknown chip type {type(chip)}") 145 | 146 | 147 | def chip_run_stub(chip): 148 | try: 149 | return chip.run_stub() 150 | except esptool.FatalError as err: 151 | raise Esp_flasherError( 152 | f"Error putting ESP in stub flash mode: {err}" 153 | ) from err 154 | 155 | 156 | def detect_flash_size(stub_chip): 157 | flash_id = read_chip_property(stub_chip.flash_id) 158 | return esptool.DETECTED_FLASH_SIZES.get(flash_id >> 16, "4MB") 159 | 160 | 161 | def read_firmware_info(firmware): 162 | firmware.seek(0x10000) # Check for safeboot image 163 | header = firmware.read(4) 164 | magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header) 165 | if magic == esptool.ESPLoader.ESP_IMAGE_MAGIC: 166 | flash_freq_raw = flash_size_freq & 0x0F 167 | flash_mode = {0: "qio", 1: "qout", 2: "dio", 3: "dout"}.get(flash_mode_raw) 168 | flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw) 169 | flag_factory = True 170 | return flash_mode, flash_freq, flag_factory 171 | 172 | firmware.seek(0) # Check for firmware image 173 | header = firmware.read(4) 174 | magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header) 175 | if magic == esptool.ESPLoader.ESP_IMAGE_MAGIC: 176 | flash_freq_raw = flash_size_freq & 0x0F 177 | flash_mode = {0: "qio", 1: "qout", 2: "dio", 3: "dout"}.get(flash_mode_raw) 178 | flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw) 179 | flag_factory = False 180 | return flash_mode, flash_freq, flag_factory 181 | 182 | if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC: 183 | raise Esp_flasherError( 184 | f"The firmware binary is invalid (magic byte={magic:02X}, should be {esptool.ESPLoader.ESP_IMAGE_MAGIC:02X})" 185 | ) 186 | 187 | 188 | 189 | def open_downloadable_binary(path): 190 | if hasattr(path, "seek"): 191 | path.seek(0) 192 | return path 193 | 194 | if HTTP_REGEX.match(path) is not None: 195 | import requests 196 | 197 | try: 198 | response = requests.get(path) 199 | response.raise_for_status() 200 | except requests.exceptions.Timeout as err: 201 | raise Esp_flasherError( 202 | f"Timeout while retrieving firmware file '{path}': {err}" 203 | ) from err 204 | except requests.exceptions.RequestException as err: 205 | raise Esp_flasherError( 206 | f"Error while retrieving firmware file '{path}': {err}" 207 | ) from err 208 | 209 | binary = io.BytesIO() 210 | binary.write(response.content) 211 | binary.seek(0) 212 | return binary 213 | 214 | try: 215 | return open(path, "rb") 216 | except IOError as err: 217 | raise Esp_flasherError(f"Error opening binary '{path}': {err}") from err 218 | 219 | def format_bootloader_path(path, model, flash_mode, flash_freq): 220 | return path.replace("$MODEL$", model).replace("$FLASH_MODE$", flash_mode).replace("$FLASH_FREQ$", flash_freq) 221 | 222 | 223 | def format_partitions_path(path, model): 224 | return path.replace("$MODEL$", model) 225 | 226 | 227 | def configure_write_flash_args( 228 | info, chip, flag_factory, factory_firm_path, firmware_path, flash_size, bootloader_path, partitions_path, otadata_path, input, secure_pad, secure_pad_v2, 229 | min_rev, min_rev_full, max_rev_full, elf_sha256_offset, use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output 230 | ): 231 | addr_filename = [] 232 | firmware = open_downloadable_binary(firmware_path) 233 | flash_mode, flash_freq, flag_factory = read_firmware_info(firmware) 234 | if flag_factory: 235 | print("Detected factory firmware Image, flashing without changes") 236 | if (isinstance(info, ESP32ChipInfo)): # No esp8266 image, fetching and processing needed files 237 | ofs_partitions = 0x8000 238 | ofs_otadata = 0xe000 239 | ofs_factory_firm = 0x10000 240 | ofs_firmware = 0xe0000 241 | 242 | if "ESP32-C2" in info.model: 243 | model = "esp32c2" 244 | safeboot = "tasmota32c2-safeboot.bin" 245 | ofs_bootloader = 0x0 246 | flash_freq = "60m" # For Tasmota we use only fastest 247 | elif "ESP32-C3" in info.model: 248 | model = "esp32c3" 249 | safeboot = "tasmota32c3-safeboot.bin" 250 | ofs_bootloader = 0x0 251 | elif "ESP32-C5" in info.model: 252 | model = "esp32c5" 253 | safeboot = "tasmota32c5-safeboot.bin" 254 | ofs_bootloader = 0x2000 255 | elif "ESP32-C6" in info.model: 256 | model = "esp32c6" 257 | safeboot = "tasmota32c6-safeboot.bin" 258 | ofs_bootloader = 0x0 259 | flash_freq = "80m" # For Tasmota we use only fastest 260 | elif "ESP32-P4" in info.model: 261 | model = "esp32p4" 262 | safeboot = "tasmota32p4-safeboot.bin" 263 | ofs_bootloader = 0x2000 264 | elif "ESP32-S3" in info.model: 265 | model = "esp32s3" 266 | safeboot = "tasmota32s3-safeboot.bin" 267 | ofs_bootloader = 0x0 268 | elif "ESP32-S2" in info.model: 269 | model = "esp32s2" 270 | safeboot = "tasmota32s2-safeboot.bin" 271 | ofs_bootloader = 0x1000 272 | else: 273 | model = "esp32" 274 | safeboot = "tasmota32solo1-safeboot.bin" 275 | ofs_bootloader = 0x1000 276 | 277 | if flash_freq in ("15", "20m", "26m", "30"): 278 | raise Esp_flasherError( 279 | f"No bootloader available for flash frequency {flash_freq}" 280 | ) 281 | chip = model 282 | min_rev = 0 283 | min_rev_full = 0 284 | max_rev_full = 65535 285 | secure_pad = "False" 286 | secure_pad_v2 = "False" 287 | elf_sha256_offset = "" 288 | use_segments = "False" 289 | flash_mmu_page_size = "" 290 | pad_to_size = "" 291 | spi_connection = "" 292 | 293 | if (isinstance(info, ESP32ChipInfo)) and not flag_factory: # esp32 and no factory image 294 | uwd = os.path.expanduser("~") 295 | esp_flasher_ver = "ESP_Flasher_" + __version__ 296 | bootloaderstring = "bootloader_" + flash_mode + "_" + flash_freq + ".elf" 297 | boot_loader_path = join(uwd, esp_flasher_ver, "bootloader", model, "bin") 298 | boot_loader_file = join(boot_loader_path , bootloaderstring) 299 | if not os.path.exists(boot_loader_path): 300 | os.makedirs(boot_loader_path) 301 | output = boot_loader_file.replace(".elf", ".bin") 302 | 303 | try: 304 | open(boot_loader_file, "rb") # check for local elf bootloader file 305 | except IOError as err: # download elf bootloader file 306 | boot_elf_path = open_downloadable_binary( 307 | format_bootloader_path(bootloader_path, model, flash_mode, flash_freq) 308 | ) 309 | with open(boot_loader_file, "wb") as f: 310 | f.write(boot_elf_path.getbuffer()) # save elf bootloader file local 311 | 312 | try: 313 | with open(output, "rb") as fh: 314 | bootloader = BytesIO(fh.read()) 315 | except IOError as err: 316 | bootloader="" # Will be there in second call! 317 | 318 | input = boot_loader_file # local downloaded elf bootloader file 319 | if not partitions_path: 320 | partitions_path = format_partitions_path(ESP32_DEFAULT_PARTITIONS, model) 321 | if not factory_firm_path: 322 | factory_firm_path = ESP32_SAFEBOOT_SERVER + safeboot 323 | 324 | partitions = open_downloadable_binary(partitions_path) 325 | factory_firm = open_downloadable_binary(factory_firm_path) 326 | otadata = open_downloadable_binary(otadata_path) 327 | # add all needed files since only firmware is provided 328 | addr_filename.append((ofs_bootloader, bootloader)) 329 | addr_filename.append((ofs_partitions, partitions)) 330 | addr_filename.append((ofs_otadata, otadata)) 331 | addr_filename.append((ofs_factory_firm, factory_firm)) 332 | addr_filename.append((ofs_firmware, firmware)) 333 | else: 334 | addr_filename.append((0x0, firmware)) # esp32 factory image 335 | else: 336 | addr_filename.append((0x0, firmware)) # esp8266 image 337 | return MockEsptoolArgs(chip, flag_factory, flash_size, addr_filename, flash_mode, flash_freq, input, secure_pad, secure_pad_v2, 338 | min_rev, min_rev_full, max_rev_full, elf_sha256_offset, use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output) 339 | 340 | 341 | def detect_chip(port, force_esp8266=False, force_esp32=False, force_esp32s2=False, force_esp32s3=False, force_esp32c2=False, force_esp32c3=False, force_esp32c5=False, force_esp32c6=False, force_esp32p4=False): 342 | if force_esp8266 or force_esp32 or force_esp32s2 or force_esp32s3 or force_esp32c2 or force_esp32c3 or force_esp32c5 or force_esp32c6 or force_esp32p4: 343 | if force_esp8266: 344 | klass = esptool.ESP8266ROM 345 | elif force_esp32: 346 | klass = esptool.ESP32ROM 347 | elif force_esp32s2: 348 | klass = esptool.ESP32S2ROM 349 | elif force_esp32s3: 350 | klass = esptool.ESP32S3ROM 351 | elif force_esp32c2: 352 | klass = esptool.ESP32C2ROM 353 | elif force_esp32c3: 354 | klass = esptool.ESP32C3ROM 355 | elif force_esp32c5: 356 | klass = esptool.ESP32C5ROM 357 | elif force_esp32c6: 358 | klass = esptool.ESP32C6ROM 359 | elif force_esp32p4: 360 | klass = esptool.ESP32P4ROM 361 | chip = klass(port) 362 | else: 363 | try: 364 | chip = esptool.ESPLoader.detect_chip(port) 365 | except esptool.FatalError as err: 366 | if "Wrong boot mode detected" in str(err): 367 | msg = "ESP is not in flash boot mode. If your board has a flashing pin, try again while keeping it pressed." 368 | else: 369 | msg = f"ESP Chip Auto-Detection failed: {err}" 370 | raise Esp_flasherError(msg) from err 371 | 372 | try: 373 | # connect(mode='default_reset', attempts=DEFAULT_CONNECT_ATTEMPTS (7), detecting=False, warnings=True) 374 | # no_reset is used to stay in bootloader -> fix for S2 CDC flashing 375 | chip.connect('no_reset',7,False,False) 376 | except esptool.FatalError as err: 377 | raise Esp_flasherError(f"Error connecting to ESP: {err}") from err 378 | 379 | return chip 380 | -------------------------------------------------------------------------------- /esp_flasher/const.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | __version__ = "3.1.0" 4 | 5 | ESP32_DEFAULT_OTA_DATA = ( 6 | "https://raw.githubusercontent.com/Jason2866/ESP_Flasher/factory/" 7 | "partitions/boot_app0.bin" 8 | ) 9 | ESP32_DEFAULT_BOOTLOADER_FORMAT = ( 10 | "https://raw.githubusercontent.com/Jason2866/ESP_Flasher/factory/" 11 | "bootloader/$MODEL$/bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.elf" 12 | ) 13 | ESP32_DEFAULT_PARTITIONS = ( 14 | "https://raw.githubusercontent.com/Jason2866/ESP_Flasher/factory/" 15 | "partitions/partitions.$MODEL$.bin" 16 | ) 17 | ESP32_SAFEBOOT_SERVER = ( 18 | "https://ota.tasmota.com/tasmota32/" 19 | ) 20 | 21 | # https://stackoverflow.com/a/3809435/8924614 22 | HTTP_REGEX = re.compile( 23 | r"https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)" 24 | ) 25 | -------------------------------------------------------------------------------- /esp_flasher/gui.py: -------------------------------------------------------------------------------- 1 | # Big thx to Michael Kandziora for this GUI port to PyQt5 2 | import re 3 | import sys 4 | import threading 5 | import os 6 | import platform 7 | import distro 8 | 9 | from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 10 | QHBoxLayout, QPushButton, QLabel, QComboBox, 11 | QFileDialog, QTextEdit, QGroupBox, QGridLayout) 12 | from PyQt5.QtGui import QColor, QTextCursor, QPalette, QColor 13 | from PyQt5.QtCore import pyqtSignal, QObject 14 | 15 | from esp_flasher.own_esptool import get_port_list 16 | from esp_flasher.const import __version__ 17 | 18 | COLOR_RE = re.compile(r'(?:\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))') 19 | COLORS = { 20 | 'black': QColor('black'), 21 | 'red': QColor('red'), 22 | 'green': QColor('green'), 23 | 'yellow': QColor('yellow'), 24 | 'blue': QColor('blue'), 25 | 'magenta': QColor('magenta'), 26 | 'cyan': QColor('cyan'), 27 | 'white': QColor('white'), 28 | } 29 | FORE_COLORS = {**COLORS, None: QColor('white')} 30 | BACK_COLORS = {**COLORS, None: QColor('black')} 31 | 32 | class RedirectText(QObject): 33 | text_written = pyqtSignal(str) 34 | 35 | def __init__(self, text_edit): 36 | super().__init__() 37 | self._out = text_edit 38 | self._line = '' 39 | self._bold = False 40 | self._italic = False 41 | self._underline = False 42 | self._foreground = None 43 | self._background = None 44 | self._secret = False 45 | self.text_written.connect(self._append_text) 46 | 47 | def write(self, string): 48 | self.text_written.emit(string) 49 | 50 | def flush(self): 51 | pass 52 | 53 | def _append_text(self, text): 54 | cursor = self._out.textCursor() 55 | self._out.moveCursor(QTextCursor.End) 56 | self._out.insertPlainText(text) 57 | self._out.setTextCursor(cursor) 58 | 59 | class FlashingThread(threading.Thread): 60 | def __init__(self, firmware, port, show_logs=False): 61 | threading.Thread.__init__(self) 62 | self.daemon = True 63 | self._firmware = firmware 64 | self._port = port 65 | self._show_logs = show_logs 66 | 67 | def run(self): 68 | try: 69 | from esp_flasher.__main__ import run_esp_flasher 70 | 71 | argv = ['esp_flasher', '--port', self._port, self._firmware] 72 | if self._show_logs: 73 | argv.append('--show-logs') 74 | run_esp_flasher(argv) 75 | except Exception as e: 76 | print("Unexpected error: {}".format(e)) 77 | raise 78 | 79 | class MainWindow(QMainWindow): 80 | def __init__(self): 81 | super().__init__() 82 | 83 | self._firmware = None 84 | self._port = None 85 | 86 | self.init_ui() 87 | sys.stdout = RedirectText(self.console) # Redirect stdout to console 88 | 89 | def init_ui(self): 90 | self.setWindowTitle(f"Tasmota-Esp-Flasher {__version__}") 91 | self.setGeometry(100, 100, 800, 600) 92 | 93 | central_widget = QWidget() 94 | self.setCentralWidget(central_widget) 95 | 96 | vbox = QVBoxLayout() 97 | 98 | port_group_box = QGroupBox("Serial Port") 99 | port_layout = QGridLayout() 100 | port_label = QLabel("Select Port:") 101 | self.port_combobox = QComboBox() 102 | self.reload_ports() 103 | self.port_combobox.currentIndexChanged.connect(self.select_port) 104 | reload_button = QPushButton("Reload") 105 | reload_button.clicked.connect(self.reload_ports) 106 | port_layout.addWidget(port_label, 0, 0) 107 | port_layout.addWidget(self.port_combobox, 0, 1) 108 | port_layout.addWidget(reload_button, 0, 2) 109 | port_group_box.setLayout(port_layout) 110 | 111 | firmware_group_box = QGroupBox("Firmware") 112 | firmware_layout = QGridLayout() 113 | firmware_label = QLabel("Select Firmware:") 114 | self.firmware_button = QPushButton("Browse") 115 | self.firmware_button.clicked.connect(self.pick_file) 116 | firmware_layout.addWidget(firmware_label, 0, 0) 117 | firmware_layout.addWidget(self.firmware_button, 0, 1) 118 | firmware_group_box.setLayout(firmware_layout) 119 | 120 | actions_group_box = QGroupBox("Actions") 121 | actions_layout = QHBoxLayout() 122 | self.flash_button = QPushButton("Flash ESP") 123 | self.flash_button.clicked.connect(self.flash_esp) 124 | self.logs_button = QPushButton("View Logs") 125 | self.logs_button.clicked.connect(self.view_logs) 126 | actions_layout.addWidget(self.flash_button) 127 | actions_layout.addWidget(self.logs_button) 128 | actions_group_box.setLayout(actions_layout) 129 | 130 | console_group_box = QGroupBox("Console") 131 | console_layout = QVBoxLayout() 132 | self.console = QTextEdit() 133 | self.console.setReadOnly(True) 134 | console_layout.addWidget(self.console) 135 | console_group_box.setLayout(console_layout) 136 | 137 | vbox.addWidget(port_group_box) 138 | vbox.addWidget(firmware_group_box) 139 | vbox.addWidget(actions_group_box) 140 | vbox.addWidget(console_group_box) 141 | 142 | central_widget.setLayout(vbox) 143 | 144 | def reload_ports(self): 145 | self.port_combobox.clear() 146 | ports = get_port_list() 147 | if ports: 148 | self.port_combobox.addItems(ports) 149 | self._port = ports[0] 150 | else: 151 | self.port_combobox.addItem("") 152 | 153 | def select_port(self, index): 154 | self._port = self.port_combobox.itemText(index) 155 | 156 | def pick_file(self): 157 | options = QFileDialog.Options() 158 | file_name, _ = QFileDialog.getOpenFileName(self, "Select Firmware File", "", "Binary Files (*.bin);;All Files (*)", options=options) 159 | if file_name: 160 | self._firmware = file_name 161 | self.firmware_button.setText(file_name) 162 | 163 | def flash_esp(self): 164 | self.console.clear() 165 | if self._firmware and self._port: 166 | worker = FlashingThread(self._firmware, self._port) 167 | worker.start() 168 | 169 | def view_logs(self): 170 | self.console.clear() 171 | if self._port: 172 | worker = FlashingThread('dummy', self._port, show_logs=True) 173 | worker.start() 174 | 175 | def main(): 176 | 177 | os_name = platform.system() 178 | if os_name == 'Darwin': 179 | os.environ['QT_QPA_PLATFORM'] = 'cocoa' 180 | elif os_name == 'Linux': 181 | distro_name = distro.id().lower() 182 | if 'ubuntu' in distro_name or 'debian' in distro_name: 183 | os.environ['QT_QPA_PLATFORM'] = 'wayland' 184 | else: 185 | os.environ['QT_QPA_PLATFORM'] = 'xcb' 186 | elif os_name == 'Windows': 187 | os.environ['QT_QPA_PLATFORM'] = 'windows' 188 | else: 189 | os.environ['QT_QPA_PLATFORM'] = 'offscreen' 190 | 191 | app = QApplication(sys.argv) 192 | 193 | app.setStyle("Fusion") 194 | palette = QPalette() 195 | palette.setColor(QPalette.Window, QColor(53, 53, 53)) 196 | palette.setColor(QPalette.WindowText, QColor(255, 255, 255)) 197 | palette.setColor(QPalette.Base, QColor(35, 35, 35)) 198 | palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) 199 | palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) 200 | palette.setColor(QPalette.ToolTipText, QColor(255, 255, 255)) 201 | palette.setColor(QPalette.Text, QColor(255, 255, 255)) 202 | palette.setColor(QPalette.Button, QColor(53, 53, 53)) 203 | palette.setColor(QPalette.ButtonText, QColor(255, 255, 255)) 204 | palette.setColor(QPalette.BrightText, QColor(255, 0, 0)) 205 | palette.setColor(QPalette.Link, QColor(42, 130, 218)) 206 | palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) 207 | palette.setColor(QPalette.HighlightedText, QColor(0, 0, 0)) 208 | app.setPalette(palette) 209 | 210 | main_window = MainWindow() 211 | main_window.show() 212 | sys.exit(app.exec_()) 213 | 214 | if __name__ == "__main__": 215 | main() 216 | -------------------------------------------------------------------------------- /esp_flasher/helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | 6 | import serial 7 | 8 | DEVNULL = open(os.devnull, 'w') 9 | 10 | 11 | def prevent_print(func, *args, **kwargs): 12 | orig_sys_stdout = sys.stdout 13 | sys.stdout = DEVNULL 14 | try: 15 | return func(*args, **kwargs) 16 | except serial.SerialException as err: 17 | from esp_flasher.common import EspflasherError 18 | 19 | raise EspflasherError("Serial port closed: {}".format(err)) 20 | finally: 21 | sys.stdout = orig_sys_stdout 22 | sys.stdout.isatty = lambda: False 23 | pass 24 | -------------------------------------------------------------------------------- /icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/icon.icns -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/icon.ico -------------------------------------------------------------------------------- /partitions/boot_app0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/boot_app0.bin -------------------------------------------------------------------------------- /partitions/partitions.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32c2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32c2.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32c3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32c3.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32c5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32c5.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32c6.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32c6.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32s2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32s2.bin -------------------------------------------------------------------------------- /partitions/partitions.esp32s3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jason2866/ESP_Flasher/f2d699c417431dad89b16d1227922431c4fdcbeb/partitions/partitions.esp32s3.bin -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial>=3.5 2 | requests>=2.24.0,<3 3 | PyQT5>=5.15.10 4 | distro>=1.9.0 5 | -------------------------------------------------------------------------------- /requirements_build.txt: -------------------------------------------------------------------------------- 1 | pyinstaller>=4.8,<7 2 | wheel 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """esp_flasher setup script.""" 3 | import os 4 | 5 | from setuptools import setup, find_packages 6 | 7 | from esp_flasher import const 8 | 9 | PROJECT_NAME = 'ESP_Flasher' 10 | PROJECT_PACKAGE_NAME = 'esp_flasher' 11 | PROJECT_LICENSE = 'MIT' 12 | PROJECT_AUTHOR = 'Jason2866' 13 | PROJECT_COPYRIGHT = '2023, Jason2866' 14 | PROJECT_URL = 'https://github.com/Jason2866/ESP_Flasher' 15 | PROJECT_EMAIL = 'obermeier.johann@googlemail.com' 16 | 17 | PROJECT_GITHUB_USERNAME = 'Jason2866' 18 | PROJECT_GITHUB_REPOSITORY = 'ESP_Flasher' 19 | 20 | PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME) 21 | GITHUB_PATH = '{}/{}'.format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) 22 | GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH) 23 | 24 | here = os.path.abspath(os.path.dirname(__file__)) 25 | 26 | with open(os.path.join(here, 'requirements.txt')) as requirements_txt: 27 | REQUIRES = requirements_txt.read().splitlines() 28 | 29 | with open(os.path.join(here, 'README.md')) as readme: 30 | LONG_DESCRIPTION = readme.read() 31 | 32 | 33 | setup( 34 | name=PROJECT_PACKAGE_NAME, 35 | version=const.__version__, 36 | license=PROJECT_LICENSE, 37 | url=GITHUB_URL, 38 | author=PROJECT_AUTHOR, 39 | author_email=PROJECT_EMAIL, 40 | description="ESP8266/ESP32 Tasmota firmware flasher for ESP", 41 | include_package_data=True, 42 | zip_safe=False, 43 | platforms='any', 44 | test_suite='tests', 45 | python_requires='>=3.8,<4.0', 46 | install_requires=REQUIRES, 47 | long_description=LONG_DESCRIPTION, 48 | long_description_content_type='text/markdown', 49 | keywords=['home', 'automation'], 50 | entry_points={ 51 | 'console_scripts': [ 52 | 'esp_flasher = esp_flasher.__main__:main' 53 | ] 54 | }, 55 | packages=find_packages(include="esprelease.*") 56 | ) 57 | --------------------------------------------------------------------------------