Compare commits
No commits in common. "v2.2" and "main" have entirely different histories.
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
|
@ -0,0 +1,144 @@
|
||||||
|
name: Build Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
architecture: 'x64'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher\__main__.py
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Windows
|
||||||
|
path: dist/ESPHome-Flasher.exe
|
||||||
|
|
||||||
|
build-windows-x86:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
architecture: 'x86'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher\__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Windows-x86
|
||||||
|
path: dist/ESPHome-Flasher.exe
|
||||||
|
|
||||||
|
build-ubuntu:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libgtk-3-dev libnotify-dev libsdl2-dev
|
||||||
|
pip install -U \
|
||||||
|
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||||
|
wxPython
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher/__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Ubuntu
|
||||||
|
path: dist/ESPHome-Flasher
|
||||||
|
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.icns esphomeflasher/__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- name: Tar files
|
||||||
|
run: |
|
||||||
|
tar -C dist -cvf dist.tar ESPHome-Flasher.app
|
||||||
|
- name: 'Upload Artifact'
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macOS
|
||||||
|
path: dist.tar
|
||||||
|
|
||||||
|
build-pypi:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libgtk-3-dev libnotify-dev libsdl2-dev
|
||||||
|
pip install -U \
|
||||||
|
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||||
|
wxPython
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run sdist
|
||||||
|
run: python setup.py sdist bdist_wheel
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: sdist
|
||||||
|
path: dist/esphomeflasher-*.tar.gz
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: bdist_wheel
|
||||||
|
path: dist/esphomeflasher-*.whl
|
|
@ -0,0 +1,71 @@
|
||||||
|
name: Lint workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- id: flake8
|
||||||
|
name: Lint with flake8
|
||||||
|
- id: pylint
|
||||||
|
name: Lint with pylint
|
||||||
|
- id: black
|
||||||
|
name: Check formatting with black
|
||||||
|
- id: isort
|
||||||
|
name: Check import order with isort
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
id: python
|
||||||
|
with:
|
||||||
|
python-version: '3.7'
|
||||||
|
- name: Install apt dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libgtk-3-dev libnotify-dev libsdl2-dev
|
||||||
|
|
||||||
|
- name: Get pip cache dir
|
||||||
|
id: pip-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=dir::$(pip cache dir)"
|
||||||
|
- name: Restore PIP cache
|
||||||
|
uses: actions/cache@v3.0.1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
|
key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt', 'requirements_test.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
pip-${{ steps.python.outputs.python-version }}-
|
||||||
|
- name: Set up Python environment
|
||||||
|
run: |
|
||||||
|
pip3 install -U \
|
||||||
|
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||||
|
wxPython
|
||||||
|
pip3 install -r requirements.txt -r requirements_test.txt
|
||||||
|
pip3 install -e .
|
||||||
|
|
||||||
|
- name: Register problem matchers
|
||||||
|
run: |
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/flake8.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/pylint.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/isort.json"
|
||||||
|
|
||||||
|
- run: flake8 esphomeflasher
|
||||||
|
if: ${{ matrix.id == 'flake8' }}
|
||||||
|
- run: pylint esphomeflasher
|
||||||
|
if: ${{ matrix.id == 'pylint' }}
|
||||||
|
- run: black --check --diff --color esphomeflasher
|
||||||
|
if: ${{ matrix.id == 'black' }}
|
||||||
|
- run: isort --check --diff esphomeflasher
|
||||||
|
if: ${{ matrix.id == 'isort' }}
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: Lock
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lock
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v3
|
||||||
|
with:
|
||||||
|
pr-inactive-days: "7"
|
||||||
|
pr-lock-reason: ""
|
||||||
|
exclude-any-pr-labels: keep-open
|
||||||
|
|
||||||
|
issue-inactive-days: "7"
|
||||||
|
issue-lock-reason: ""
|
||||||
|
exclude-any-issue-labels: keep-open
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "flake8-error",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(.*):(\\d+):(\\d+):\\s([EF]\\d{3}\\s.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"message": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "flake8-warning",
|
||||||
|
"severity": "warning",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(.*):(\\d+):(\\d+):\\s([CDNW]\\d{3}\\s.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"message": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "isort",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^ERROR:\\s+(.+)\\s+(.+)$",
|
||||||
|
"file": 1,
|
||||||
|
"message": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "pylint-error",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(.+):(\\d+):(\\d+):\\s(([EF]\\d{4}):\\s.+)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"message": 4,
|
||||||
|
"code": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "pylint-warning",
|
||||||
|
"severity": "warning",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(.+):(\\d+):(\\d+):\\s(([CRW]\\d{4}):\\s.+)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"message": 4,
|
||||||
|
"code": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
name: Release Assets Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
type: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
architecture: 'x64'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher\__main__.py
|
||||||
|
- uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: dist/ESPHome-Flasher.exe
|
||||||
|
asset_name: ESPHome-Flasher-$tag-Windows-x64.exe
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
build-windows-x86:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
architecture: 'x86'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher\__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: dist/ESPHome-Flasher.exe
|
||||||
|
asset_name: ESPHome-Flasher-$tag-Windows-x86.exe
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
build-ubuntu:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libgtk-3-dev libnotify-dev libsdl2-dev
|
||||||
|
pip install -U \
|
||||||
|
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||||
|
wxPython
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher/__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: dist/ESPHome-Flasher
|
||||||
|
asset_name: ESPHome-Flasher-$tag-Ubuntu-x64.exec
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v3.1.0
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt -r requirements_build.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Run PyInstaller
|
||||||
|
run: |
|
||||||
|
python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.icns esphomeflasher/__main__.py
|
||||||
|
- name: See dist directory
|
||||||
|
run: ls dist
|
||||||
|
- name: Tar files
|
||||||
|
run: |
|
||||||
|
tar -C dist -cvf dist.tar ESPHome-Flasher.app
|
||||||
|
- uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: dist.tar
|
||||||
|
asset_name: ESPHome-Flasher-$tag-macOS.tar
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: Stale
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lock
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
days-before-pr-stale: 90
|
||||||
|
days-before-pr-close: 7
|
||||||
|
days-before-issue-stale: 90
|
||||||
|
days-before-issue-close: 7
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
exempt-pr-labels: "no-stale"
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
exempt-issue-labels: "no-stale"
|
||||||
|
stale-pr-message: >
|
||||||
|
There hasn't been any activity on this pull request recently. This
|
||||||
|
pull request has been automatically marked as stale because of that
|
||||||
|
and will be closed if no further activity occurs within 7 days.
|
||||||
|
Thank you for your contributions.
|
||||||
|
stale-issue-message: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
|
@ -1,5 +1,3 @@
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
@ -10,7 +8,6 @@ __pycache__/
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
|
@ -22,15 +19,17 @@ lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
#*.spec
|
*.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
|
@ -44,8 +43,9 @@ htmlcov/
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
@ -54,6 +54,7 @@ coverage.xml
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
|
@ -68,7 +69,7 @@ docs/_build/
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# IPython Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
|
@ -77,15 +78,41 @@ target/
|
||||||
# celery beat schedule file
|
# celery beat schedule file
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
|
|
||||||
# dotenv
|
# SageMath parsed files
|
||||||
.env
|
*.sage.py
|
||||||
|
|
||||||
# virtualenv
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
config/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
/.idea/
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
72
About.py
|
@ -1,72 +0,0 @@
|
||||||
|
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
import sys, os, wx
|
|
||||||
import wx.html
|
|
||||||
import wx.lib.wxpTag
|
|
||||||
import webbrowser
|
|
||||||
from Main import __version__
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class AboutDlg(wx.Dialog):
|
|
||||||
text = '''
|
|
||||||
<html>
|
|
||||||
<body bgcolor="#DCDCDC" style="font-family: Arial; background-color: #DCDCDC;">
|
|
||||||
<center>
|
|
||||||
<img src="{0}/images/python-64.png" width="64" height="64" alt="Python">
|
|
||||||
<img src="{0}/images/icon-64.png" width="64" height="64" alt="NodeMCU">
|
|
||||||
<img src="{0}/images/espressif-64.png" width="64" height="64" alt="Espressif, producers of ESP8266 et.al.">
|
|
||||||
<img src="{0}/images/wxpython-64.png" width="64" height="43" alt="wxPython, cross-platform GUI framework">
|
|
||||||
|
|
||||||
<h1>NodeMCU PyFlasher</h1>
|
|
||||||
|
|
||||||
<p>Version {1}</p>
|
|
||||||
|
|
||||||
<p>Fork the <a style="color: #004CE5;" href="https://github.com/marcelstoer/nodemcu-pyflasher">project on
|
|
||||||
GitHub</a> and help improve it for all!</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
As with everything I offer for free, this is donation-ware.
|
|
||||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q"><img src="{0}/images/paypal-256.png" width="256" height="88" alt="Donate with PayPal"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>© 2017 Marcel Stör. Licensed under MIT.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<wxp module="wx" class="Button">
|
|
||||||
<param name="label" value="Close">
|
|
||||||
<param name="id" value="ID_OK">
|
|
||||||
</wxp>
|
|
||||||
</p>
|
|
||||||
</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
wx.Dialog.__init__(self, parent, wx.ID_ANY, "About NodeMCU PyFlasher")
|
|
||||||
html = HtmlWindow(self, wx.ID_ANY, size=(420, -1))
|
|
||||||
if "gtk2" in wx.PlatformInfo or "gtk3" in wx.PlatformInfo:
|
|
||||||
html.SetStandardFonts()
|
|
||||||
txt = self.text.format(self._get_bundle_dir(), __version__)
|
|
||||||
html.SetPage(txt)
|
|
||||||
ir = html.GetInternalRepresentation()
|
|
||||||
html.SetSize((ir.GetWidth() + 25, ir.GetHeight() + 25))
|
|
||||||
self.SetClientSize(html.GetSize())
|
|
||||||
self.CentreOnParent(wx.BOTH)
|
|
||||||
|
|
||||||
def _get_bundle_dir(self):
|
|
||||||
# set by PyInstaller, see http://pyinstaller.readthedocs.io/en/v3.2/runtime-information.html
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
return sys._MEIPASS
|
|
||||||
else:
|
|
||||||
return os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlWindow(wx.html.HtmlWindow):
|
|
||||||
def OnLinkClicked(self, link):
|
|
||||||
webbrowser.open(link.GetHref())
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
|
@ -1,30 +0,0 @@
|
||||||
|
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
import wx
|
|
||||||
import wx.html
|
|
||||||
import webbrowser
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlPopupTransientWindow(wx.PopupTransientWindow):
|
|
||||||
def __init__(self, parent, style, html_body_content, bgcolor, size):
|
|
||||||
wx.PopupTransientWindow.__init__(self, parent, style)
|
|
||||||
panel = wx.Panel(self)
|
|
||||||
panel.SetBackgroundColour(bgcolor)
|
|
||||||
|
|
||||||
html_window = self.HtmlWindow(panel, wx.ID_ANY, size=size)
|
|
||||||
html_window.SetPage('<body bgcolor="' + bgcolor + '">' + html_body_content + '</body>')
|
|
||||||
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
||||||
sizer.Add(html_window, 0, wx.ALL, 5)
|
|
||||||
panel.SetSizer(sizer)
|
|
||||||
|
|
||||||
sizer.Fit(panel)
|
|
||||||
sizer.Fit(self)
|
|
||||||
self.Layout()
|
|
||||||
|
|
||||||
class HtmlWindow(wx.html.HtmlWindow):
|
|
||||||
def OnLinkClicked(self, link):
|
|
||||||
# get a hold of the PopupTransientWindow to close it
|
|
||||||
self.GetParent().GetParent().Dismiss()
|
|
||||||
webbrowser.open(link.GetHref())
|
|
4
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 Marcel Stör
|
Copyright (c) 2016 Marcel Stör, Otto Winter
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
||||||
|
include LICENSE
|
||||||
|
include README.md
|
||||||
|
include requirements.txt
|
440
Main.py
|
@ -1,440 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import wx
|
|
||||||
import wx.lib.inspection
|
|
||||||
import wx.lib.mixins.inspection
|
|
||||||
import sys, os
|
|
||||||
import esptool
|
|
||||||
import threading
|
|
||||||
import json
|
|
||||||
import images as images
|
|
||||||
from serial import SerialException
|
|
||||||
from serial.tools import list_ports
|
|
||||||
from esptool import ESPLoader
|
|
||||||
from esptool import NotImplementedInROMError
|
|
||||||
from argparse import Namespace
|
|
||||||
|
|
||||||
__version__ = "2.2"
|
|
||||||
__flash_help__ = '''
|
|
||||||
<p>This setting is highly dependent on your device!<p>
|
|
||||||
<p>
|
|
||||||
Details at <a style="color: #004CE5;"
|
|
||||||
href="https://www.esp32.com/viewtopic.php?p=5523&sid=08ef44e13610ecf2a2a33bb173b0fd5c#p5523">http://bit.ly/2v5Rd32</a>
|
|
||||||
and in the <a style="color: #004CE5;" href="https://github.com/espressif/esptool/#flash-modes">esptool
|
|
||||||
documentation</a>
|
|
||||||
<ul>
|
|
||||||
<li>Most ESP32 and ESP8266 ESP-12 use DIO.</li>
|
|
||||||
<li>Most ESP8266 ESP-01/07 use QIO.</li>
|
|
||||||
<li>ESP8285 requires DOUT.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
'''
|
|
||||||
__supported_baud_rates__ = [9600, 57600, 74880, 115200, 230400, 460800, 921600]
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# See discussion at http://stackoverflow.com/q/41101897/131929
|
|
||||||
class RedirectText:
|
|
||||||
def __init__(self, text_ctrl):
|
|
||||||
self.__out = text_ctrl
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
if string.startswith("\r"):
|
|
||||||
# carriage return -> remove last line i.e. reset position to start of last line
|
|
||||||
current_value = self.__out.GetValue()
|
|
||||||
last_newline = current_value.rfind("\n")
|
|
||||||
new_value = current_value[:last_newline + 1] # preserve \n
|
|
||||||
new_value += string[1:] # chop off leading \r
|
|
||||||
wx.CallAfter(self.__out.SetValue, new_value)
|
|
||||||
else:
|
|
||||||
wx.CallAfter(self.__out.AppendText, string)
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
None
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
class FlashingThread(threading.Thread):
|
|
||||||
def __init__(self, parent, config):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.daemon = True
|
|
||||||
self._parent = parent
|
|
||||||
self._config = config
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
initial_baud = min(ESPLoader.ESP_ROM_BAUD, self._config.baud)
|
|
||||||
|
|
||||||
esp = ESPLoader.detect_chip(self._config.port, initial_baud)
|
|
||||||
print("Chip is %s" % (esp.get_chip_description()))
|
|
||||||
|
|
||||||
esp = esp.run_stub()
|
|
||||||
|
|
||||||
if self._config.baud > initial_baud:
|
|
||||||
try:
|
|
||||||
esp.change_baud(self._config.baud)
|
|
||||||
except NotImplementedInROMError:
|
|
||||||
print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d." %
|
|
||||||
initial_baud)
|
|
||||||
|
|
||||||
args = Namespace()
|
|
||||||
args.flash_size = "detect"
|
|
||||||
args.flash_mode = self._config.mode
|
|
||||||
args.flash_freq = "40m"
|
|
||||||
args.no_progress = False
|
|
||||||
args.no_stub = False
|
|
||||||
args.verify = False # TRUE is deprecated
|
|
||||||
args.compress = True
|
|
||||||
args.addr_filename = [[int("0x00000", 0), open(self._config.firmware_path, 'rb')]]
|
|
||||||
|
|
||||||
print("Configuring flash size...")
|
|
||||||
esptool.detect_flash_size(esp, args)
|
|
||||||
esp.flash_set_parameters(esptool.flash_size_bytes(args.flash_size))
|
|
||||||
|
|
||||||
if self._config.erase_before_flash:
|
|
||||||
esptool.erase_flash(esp, args)
|
|
||||||
esptool.write_flash(esp, args)
|
|
||||||
# The last line printed by esptool is "Leaving..." -> some indication that the process is done is needed
|
|
||||||
print("\nDone.")
|
|
||||||
except SerialException as e:
|
|
||||||
self._parent.report_error(e.strerror)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# DTO between GUI and flashing thread
|
|
||||||
class FlashConfig:
|
|
||||||
def __init__(self):
|
|
||||||
self.baud = 115200
|
|
||||||
self.erase_before_flash = False
|
|
||||||
self.mode = "dio"
|
|
||||||
self.firmware_path = None
|
|
||||||
self.port = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls, file_path):
|
|
||||||
conf = cls()
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
conf.port = data['port']
|
|
||||||
conf.baud = data['baud']
|
|
||||||
conf.mode = data['mode']
|
|
||||||
conf.erase_before_flash = data['erase']
|
|
||||||
return conf
|
|
||||||
|
|
||||||
def safe(self, file_path):
|
|
||||||
data = {
|
|
||||||
'port': self.port,
|
|
||||||
'baud': self.baud,
|
|
||||||
'mode': self.mode,
|
|
||||||
'erase': self.erase_before_flash,
|
|
||||||
}
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
json.dump(data, f)
|
|
||||||
|
|
||||||
def is_complete(self):
|
|
||||||
return self.firmware_path is not None and self.port is not None
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
class NodeMcuFlasher(wx.Frame):
|
|
||||||
|
|
||||||
def __init__(self, parent, title):
|
|
||||||
wx.Frame.__init__(self, parent, -1, title, size=(700, 650),
|
|
||||||
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
|
|
||||||
self._config = FlashConfig.load(self._get_config_file_path())
|
|
||||||
|
|
||||||
self._build_status_bar()
|
|
||||||
self._set_icons()
|
|
||||||
self._build_menu_bar()
|
|
||||||
self._init_ui()
|
|
||||||
|
|
||||||
sys.stdout = RedirectText(self.console_ctrl)
|
|
||||||
|
|
||||||
self.SetMinSize((640, 480))
|
|
||||||
self.Centre(wx.BOTH)
|
|
||||||
self.Show(True)
|
|
||||||
|
|
||||||
def _init_ui(self):
|
|
||||||
def on_reload(event):
|
|
||||||
self.choice.SetItems(self._get_serial_ports())
|
|
||||||
|
|
||||||
def on_baud_changed(event):
|
|
||||||
radio_button = event.GetEventObject()
|
|
||||||
|
|
||||||
if radio_button.GetValue():
|
|
||||||
self._config.baud = radio_button.rate
|
|
||||||
|
|
||||||
def on_mode_changed(event):
|
|
||||||
radio_button = event.GetEventObject()
|
|
||||||
|
|
||||||
if radio_button.GetValue():
|
|
||||||
self._config.mode = radio_button.mode
|
|
||||||
|
|
||||||
def on_erase_changed(event):
|
|
||||||
radio_button = event.GetEventObject()
|
|
||||||
|
|
||||||
if radio_button.GetValue():
|
|
||||||
self._config.erase_before_flash = radio_button.erase
|
|
||||||
|
|
||||||
def on_clicked(event):
|
|
||||||
self.console_ctrl.SetValue("")
|
|
||||||
worker = FlashingThread(self, self._config)
|
|
||||||
worker.start()
|
|
||||||
|
|
||||||
def on_select_port(event):
|
|
||||||
choice = event.GetEventObject()
|
|
||||||
self._config.port = choice.GetString(choice.GetSelection())
|
|
||||||
|
|
||||||
def on_pick_file(event):
|
|
||||||
self._config.firmware_path = event.GetPath().replace("'", "")
|
|
||||||
|
|
||||||
panel = wx.Panel(self)
|
|
||||||
|
|
||||||
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
|
|
||||||
fgs = wx.FlexGridSizer(7, 2, 10, 10)
|
|
||||||
|
|
||||||
self.choice = wx.Choice(panel, choices=self._get_serial_ports())
|
|
||||||
self.choice.Bind(wx.EVT_CHOICE, on_select_port)
|
|
||||||
self._select_configured_port()
|
|
||||||
bmp = images.Reload.GetBitmap()
|
|
||||||
reload_button = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp,
|
|
||||||
size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7))
|
|
||||||
reload_button.Bind(wx.EVT_BUTTON, on_reload)
|
|
||||||
reload_button.SetToolTipString("Reload serial device list")
|
|
||||||
|
|
||||||
file_picker = wx.FilePickerCtrl(panel, style=wx.FLP_USE_TEXTCTRL)
|
|
||||||
file_picker.Bind(wx.EVT_FILEPICKER_CHANGED, on_pick_file)
|
|
||||||
|
|
||||||
serial_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
serial_boxsizer.Add(self.choice, 1, wx.EXPAND)
|
|
||||||
serial_boxsizer.AddStretchSpacer(0)
|
|
||||||
serial_boxsizer.Add(reload_button, 0, wx.ALIGN_RIGHT, 20)
|
|
||||||
|
|
||||||
baud_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
|
|
||||||
def add_baud_radio_button(sizer, index, baud_rate):
|
|
||||||
style = wx.RB_GROUP if index == 0 else 0
|
|
||||||
radio_button = wx.RadioButton(panel, name="baud-%d" % baud_rate, label="%d" % baud_rate, style=style)
|
|
||||||
radio_button.rate = baud_rate
|
|
||||||
# sets default value
|
|
||||||
radio_button.SetValue(baud_rate == self._config.baud)
|
|
||||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_baud_changed)
|
|
||||||
sizer.Add(radio_button)
|
|
||||||
sizer.AddSpacer(10)
|
|
||||||
|
|
||||||
for idx, rate in enumerate(__supported_baud_rates__):
|
|
||||||
add_baud_radio_button(baud_boxsizer, idx, rate)
|
|
||||||
|
|
||||||
flashmode_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
|
|
||||||
def add_flash_mode_radio_button(sizer, index, mode, label):
|
|
||||||
style = wx.RB_GROUP if index == 0 else 0
|
|
||||||
radio_button = wx.RadioButton(panel, name="mode-%s" % mode, label="%s" % label, style=style)
|
|
||||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_mode_changed)
|
|
||||||
radio_button.mode = mode
|
|
||||||
radio_button.SetValue(mode == self._config.mode)
|
|
||||||
sizer.Add(radio_button)
|
|
||||||
sizer.AddSpacer(10)
|
|
||||||
|
|
||||||
add_flash_mode_radio_button(flashmode_boxsizer, 0, "qio", "Quad I/O (QIO)")
|
|
||||||
add_flash_mode_radio_button(flashmode_boxsizer, 1, "dio", "Dual I/O (DIO)")
|
|
||||||
add_flash_mode_radio_button(flashmode_boxsizer, 2, "dout", "Dual Output (DOUT)")
|
|
||||||
|
|
||||||
erase_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
|
|
||||||
def add_erase_radio_button(sizer, index, erase_before_flash, label, value):
|
|
||||||
style = wx.RB_GROUP if index == 0 else 0
|
|
||||||
radio_button = wx.RadioButton(panel, name="erase-%s" % erase_before_flash, label="%s" % label, style=style)
|
|
||||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_erase_changed)
|
|
||||||
radio_button.erase = erase_before_flash
|
|
||||||
radio_button.SetValue(value)
|
|
||||||
sizer.Add(radio_button)
|
|
||||||
sizer.AddSpacer(10)
|
|
||||||
|
|
||||||
erase = self._config.erase_before_flash
|
|
||||||
add_erase_radio_button(erase_boxsizer, 0, False, "no", erase is False)
|
|
||||||
add_erase_radio_button(erase_boxsizer, 1, True, "yes, wipes all data", erase is True)
|
|
||||||
|
|
||||||
button = wx.Button(panel, -1, "Flash NodeMCU")
|
|
||||||
button.Bind(wx.EVT_BUTTON, on_clicked)
|
|
||||||
|
|
||||||
self.console_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL)
|
|
||||||
self.console_ctrl.SetFont(wx.Font(13, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
|
|
||||||
self.console_ctrl.SetBackgroundColour(wx.BLACK)
|
|
||||||
self.console_ctrl.SetForegroundColour(wx.RED)
|
|
||||||
self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.RED))
|
|
||||||
|
|
||||||
port_label = wx.StaticText(panel, label="Serial port")
|
|
||||||
file_label = wx.StaticText(panel, label="NodeMCU firmware")
|
|
||||||
baud_label = wx.StaticText(panel, label="Baud rate")
|
|
||||||
flashmode_label = wx.StaticText(panel, label="Flash mode")
|
|
||||||
|
|
||||||
def on_info_hover(event):
|
|
||||||
from HtmlPopupTransientWindow import HtmlPopupTransientWindow
|
|
||||||
win = HtmlPopupTransientWindow(self, wx.SIMPLE_BORDER, __flash_help__, "#FFB6C1", (410, 140))
|
|
||||||
|
|
||||||
image = event.GetEventObject()
|
|
||||||
image_position = image.ClientToScreen((0, 0))
|
|
||||||
image_size = image.GetSize()
|
|
||||||
win.Position(image_position, (0, image_size[1]))
|
|
||||||
|
|
||||||
win.Popup()
|
|
||||||
|
|
||||||
icon = wx.StaticBitmap(panel, wx.ID_ANY, images.Info.GetBitmap())
|
|
||||||
icon.Bind(wx.EVT_MOTION, on_info_hover)
|
|
||||||
|
|
||||||
flashmode_label_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
flashmode_label_boxsizer.Add(flashmode_label, 1, wx.EXPAND)
|
|
||||||
flashmode_label_boxsizer.AddStretchSpacer(0)
|
|
||||||
flashmode_label_boxsizer.Add(icon, 0, wx.ALIGN_RIGHT, 20)
|
|
||||||
|
|
||||||
erase_label = wx.StaticText(panel, label="Erase flash")
|
|
||||||
console_label = wx.StaticText(panel, label="Console")
|
|
||||||
|
|
||||||
fgs.AddMany([
|
|
||||||
port_label, (serial_boxsizer, 1, wx.EXPAND),
|
|
||||||
file_label, (file_picker, 1, wx.EXPAND),
|
|
||||||
baud_label, baud_boxsizer,
|
|
||||||
flashmode_label_boxsizer, flashmode_boxsizer,
|
|
||||||
erase_label, erase_boxsizer,
|
|
||||||
(wx.StaticText(panel, label="")), (button, 1, wx.EXPAND),
|
|
||||||
(console_label, 1, wx.EXPAND), (self.console_ctrl, 1, wx.EXPAND)])
|
|
||||||
fgs.AddGrowableRow(6, 1)
|
|
||||||
fgs.AddGrowableCol(1, 1)
|
|
||||||
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
|
|
||||||
panel.SetSizer(hbox)
|
|
||||||
|
|
||||||
def _select_configured_port(self):
|
|
||||||
count = 0
|
|
||||||
for item in self.choice.GetItems():
|
|
||||||
if item == self._config.port:
|
|
||||||
self.choice.Select(count)
|
|
||||||
break
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
def _get_serial_ports(self):
|
|
||||||
ports = [""]
|
|
||||||
for port, desc, hwid in sorted(list_ports.comports()):
|
|
||||||
ports.append(port)
|
|
||||||
return ports
|
|
||||||
|
|
||||||
def _set_icons(self):
|
|
||||||
self.SetIcon(images.Icon.GetIcon())
|
|
||||||
|
|
||||||
def _build_status_bar(self):
|
|
||||||
self.statusBar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
|
|
||||||
self.statusBar.SetStatusWidths([-2, -1])
|
|
||||||
status_text = "Welcome to NodeMCU PyFlasher %s" % __version__
|
|
||||||
self.statusBar.SetStatusText(status_text, 0)
|
|
||||||
|
|
||||||
def _build_menu_bar(self):
|
|
||||||
self.menuBar = wx.MenuBar()
|
|
||||||
|
|
||||||
# File menu
|
|
||||||
file_menu = wx.Menu()
|
|
||||||
wx.App.SetMacExitMenuItemId(wx.ID_EXIT)
|
|
||||||
exit_item = file_menu.Append(wx.ID_EXIT, "E&xit\tCtrl-Q", "Exit NodeMCU PyFlasher")
|
|
||||||
exit_item.SetBitmap(images.Exit.GetBitmap())
|
|
||||||
self.Bind(wx.EVT_MENU, self._on_exit_app, exit_item)
|
|
||||||
self.menuBar.Append(file_menu, "&File")
|
|
||||||
|
|
||||||
# Help menu
|
|
||||||
help_menu = wx.Menu()
|
|
||||||
help_item = help_menu.Append(wx.ID_ABOUT, '&About NodeMCU PyFlasher', 'About')
|
|
||||||
self.Bind(wx.EVT_MENU, self._on_help_about, help_item)
|
|
||||||
self.menuBar.Append(help_menu, '&Help')
|
|
||||||
|
|
||||||
self.SetMenuBar(self.menuBar)
|
|
||||||
|
|
||||||
def _get_config_file_path(self):
|
|
||||||
return wx.StandardPaths.Get().GetUserConfigDir() + "/nodemcu-pyflasher.json"
|
|
||||||
|
|
||||||
# Menu methods
|
|
||||||
def _on_exit_app(self, event):
|
|
||||||
self._config.safe(self._get_config_file_path())
|
|
||||||
self.Close(True)
|
|
||||||
|
|
||||||
def _on_help_about(self, event):
|
|
||||||
from About import AboutDlg
|
|
||||||
about = AboutDlg(self)
|
|
||||||
about.ShowModal()
|
|
||||||
about.Destroy()
|
|
||||||
|
|
||||||
def report_error(self, message):
|
|
||||||
self.console_ctrl.SetValue(message)
|
|
||||||
|
|
||||||
def log_message(self, message):
|
|
||||||
self.console_ctrl.AppendText(message)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
class MySplashScreen(wx.SplashScreen):
|
|
||||||
def __init__(self):
|
|
||||||
wx.SplashScreen.__init__(self, images.Splash.GetBitmap(),
|
|
||||||
wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
|
|
||||||
2500, None, -1)
|
|
||||||
self.Bind(wx.EVT_CLOSE, self._on_close)
|
|
||||||
self.__fc = wx.FutureCall(2000, self._show_main)
|
|
||||||
|
|
||||||
def _on_close(self, evt):
|
|
||||||
# Make sure the default handler runs too so this window gets
|
|
||||||
# destroyed
|
|
||||||
evt.Skip()
|
|
||||||
self.Hide()
|
|
||||||
|
|
||||||
# if the timer is still running then go ahead and show the
|
|
||||||
# main frame now
|
|
||||||
if self.__fc.IsRunning():
|
|
||||||
self.__fc.Stop()
|
|
||||||
self._show_main()
|
|
||||||
|
|
||||||
def _show_main(self):
|
|
||||||
frame = NodeMcuFlasher(None, "NodeMCU PyFlasher")
|
|
||||||
frame.Show()
|
|
||||||
if self.__fc.IsRunning():
|
|
||||||
self.Raise()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
|
|
||||||
def OnInit(self):
|
|
||||||
wx.SystemOptions.SetOptionInt("mac.window-plain-transition", 1)
|
|
||||||
self.SetAppName("NodeMCU PyFlasher")
|
|
||||||
|
|
||||||
# Create and show the splash screen. It will then create and
|
|
||||||
# show the main frame when it is time to do so. Normally when
|
|
||||||
# using a SplashScreen you would create it, show it and then
|
|
||||||
# continue on with the application's initialization, finally
|
|
||||||
# creating and showing the main application window(s). In
|
|
||||||
# this case we have nothing else to do so we'll delay showing
|
|
||||||
# the main frame until later (see ShowMain above) so the users
|
|
||||||
# can see the SplashScreen effect.
|
|
||||||
splash = MySplashScreen()
|
|
||||||
splash.Show()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
def main():
|
|
||||||
app = App(False)
|
|
||||||
app.MainLoop()
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
__name__ = 'Main'
|
|
||||||
main()
|
|
94
README.md
|
@ -1,64 +1,66 @@
|
||||||
# NodeMCU PyFlasher
|
# Deprecated
|
||||||
[![License](https://marcelstoer.github.io/nodemcu-pyflasher/images/mit-license-badge.svg)](https://github.com/marcelstoer/nodemcu-pyflasher/blob/master/LICENSE)
|
|
||||||
[![Github Releases](https://img.shields.io/github/downloads/marcelstoer/nodemcu-pyflasher/total.svg?style=flat)](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
|
|
||||||
[![PayPal Donation](https://marcelstoer.github.io/nodemcu-pyflasher/images/donate-paypal-badge.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
|
|
||||||
[![Twitter URL](https://marcelstoer.github.io/nodemcu-pyflasher/images/twitter-badge.svg)](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fmarcelstoer%2Fnodemcu-pyflasher)
|
|
||||||
[![Facebook URL](https://marcelstoer.github.io/nodemcu-pyflasher/images/facebook-badge.svg)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fmarcelstoer%2Fnodemcu-pyflasher)
|
|
||||||
|
|
||||||
Self-contained [NodeMCU](https://github.com/nodemcu/nodemcu-firmware) flasher with GUI based on [esptool.py](https://github.com/espressif/esptool) and [wxPython](https://www.wxpython.org/).
|
This project is deprecated in favour of browser based flashing with [ESP Web Tools](https://github.com/esphome/esp-web-tools). For example: https://web.esphome.io
|
||||||
|
|
||||||
![Image of NodeMCU PyFlasher GUI](images/gui.png)
|
|
||||||
|
# ESPHome-Flasher
|
||||||
|
|
||||||
|
ESPHome-Flasher is a utility app for the [ESPHome](https://esphome.io/)
|
||||||
|
framework and is designed to make flashing ESPs with ESPHome as simple as possible by:
|
||||||
|
|
||||||
|
* Having pre-built binaries for most operating systems.
|
||||||
|
* Hiding all non-essential options for flashing. All necessary options for flashing
|
||||||
|
(bootloader, flash mode) are automatically extracted from the binary.
|
||||||
|
|
||||||
|
This project was originally intended to be a simple command-line tool,
|
||||||
|
but then I decided that a GUI would be nice. As I don't like writing graphical
|
||||||
|
front end code, the GUI largely is based on the
|
||||||
|
[NodeMCU PyFlasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
||||||
|
project.
|
||||||
|
|
||||||
|
The flashing process is done using the [esptool](https://github.com/espressif/esptool)
|
||||||
|
library by espressif.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
NodeMCU PyFlasher doesn't have to be installed, just double-click it and it'll start. Check the [releases section](https://github.com/marcelstoer/nodemcu-pyflasher/releases) for downloads for your platform. For every release there's at least a .exe file for Windows.
|
|
||||||
|
|
||||||
## Status
|
It doesn't have to be installed, just double-click it and it'll start.
|
||||||
Scan the [list of open issues](https://github.com/marcelstoer/nodemcu-pyflasher/issues) for bugs and pending features.
|
Check the [releases section](https://github.com/esphome/esphome-flasher/releases)
|
||||||
|
for downloads for your platform.
|
||||||
|
|
||||||
- Due to [pyinstaller/pyinstaller#2355](https://github.com/pyinstaller/pyinstaller/issues/2355) I can't provide an app bundle for macOS yet. The PyInstaller `.spec` file and the build script are ready, though. Of course you can still *run* the application on macOS. Clone this repo and then do `python nodemcu-pyflasher.py` (see "[Build it yourself](#build-it-yourself)" for pre-requisits).
|
## Installation Using `pip`
|
||||||
|
|
||||||
**Note**
|
If you want to install this application from `pip`:
|
||||||
|
|
||||||
This is my first Python project. If you have constructive feedback as for how to improve the code please do reach out to me.
|
- Install Python 3.x
|
||||||
|
- Install [wxPython 4.x](https://wxpython.org/) manually or run `pip3 install wxpython` (see also linux notes below)
|
||||||
## Getting help
|
- Install this project using `pip3 install esphomeflasher`
|
||||||
In the unlikely event that you're stuck with this simple tool the best way to get help is to turn to the ["Tools and IDE" subforum on esp8266.com](http://www.esp8266.com/viewforum.php?f=22).
|
- Start the GUI using `esphomeflasher`. Alternatively, you can use the command line interface (
|
||||||
|
type `esphomeflasher -h` for info)
|
||||||
## Donationware
|
|
||||||
All open-source development by the author is donationware. Show your love and support for open-source development by donating to the good cause through PayPal.
|
|
||||||
|
|
||||||
[![PayPal Donations](./images/paypal-256.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
|
|
||||||
|
|
||||||
## Build it yourself
|
## Build it yourself
|
||||||
|
|
||||||
If you want to build this application yourself you need to:
|
If you want to build this application yourself you need to:
|
||||||
|
|
||||||
- Install Python
|
- Install Python 3.x
|
||||||
- Install [wxPython 3.x](https://sourceforge.net/projects/wxpython/files/wxPython/) (not 4.0 betas!)
|
- Install [wxPython 4.x](https://wxpython.org/) manually or run `pip3 install wxpython`
|
||||||
- Install [esptool.py](https://github.com/espressif/esptool#easy-installation) which brings pySerial or install pySerial standalone
|
- Download this project and run `pip3 install -e .` in the project's root.
|
||||||
|
- Start the GUI using `esphomeflasher`. Alternatively, you can use the command line interface (
|
||||||
|
type `esphomeflasher -h` for info)
|
||||||
|
|
||||||
## Why this project exists
|
|
||||||
|
|
||||||
### Motivation
|
## Linux Notes
|
||||||
This addresses an issue the NodeMCU community touched on several times in the past, most recently at
|
|
||||||
[#1500 (comment)](https://github.com/nodemcu/nodemcu-firmware/pull/1500#issuecomment-247884981).
|
|
||||||
|
|
||||||
I stated that based on my experience doing NodeMCU user support it should be a lot simpler to flash NodeMCU for Windows users.
|
Installing wxpython for linux can be a bit challenging (especially when you don't want to install from source).
|
||||||
|
You can use the following command to install a wxpython suitable with your OS:
|
||||||
|
|
||||||
- A number of flashing tools are available but only two are actively maintained: esptool-ck and esptool.py. Only one is endorsed by Espressif: [esptool.py](https://github.com/espressif/esptool) (they hired the developer(s)).
|
```bash
|
||||||
- 70% of the users of my [nodemcu-build.com](https://nodemcu-build.com) service are on Windows.
|
# Go to https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ and select the correct OS type
|
||||||
- BUT Windows doesn't come with Python installed - which is required for esptool.py.
|
# here, we assume ubuntu 18.03 bionic
|
||||||
- BUT Windows users in general are more reluctant to use the CLI than Linux/Mac users - which is required for esptool.py.
|
pip3 install -U \
|
||||||
|
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||||
To conclude: this is not a comfortable situation for NodeMCU's largest user group.
|
wxPython
|
||||||
|
```
|
||||||
### The plan
|
|
||||||
For quite a while I planned to write a self-contained GUI tool which would use esptool.py in the background. It should primarily target Windows users but since I'm on Mac it should be cross-platform. Even though I had never used Python before I felt confident to pull this off.
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- Uses the cross-platform wxPython GUI framework. I also tried PyForms/PyQt4 but settled for wxPython.
|
|
||||||
- Requires absolutely minimal user input.
|
|
||||||
- The esptool.py "console" output is redirected to text control on the GUI.
|
|
||||||
- Uses [PyInstaller](https://github.com/pyinstaller/pyinstaller) to create self-contained executable for Windows and Mac. The packaged app can run standalone i.e. without installing itself, a Python interpreter or any modules.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
[MIT](http://opensource.org/licenses/MIT) © Marcel Stör
|
|
||||||
|
[MIT](http://opensource.org/licenses/MIT) © Marcel Stör, Otto Winter
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
`pyinstaller -F -w -n ESPHome-Flasher -i icon.icns esphomeflasher/__main__.py`
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
|
||||||
|
1. Start up VM
|
||||||
|
2. Install Python (3) from App Store
|
||||||
|
3. Download esphome-flasher from GitHub
|
||||||
|
4. `pip install -e.` and `pip install pyinstaller`
|
||||||
|
5. Check with `python -m esphomeflasher.__main__`
|
||||||
|
6. `python -m PyInstaller.__main__ -F -w -n ESPHome-Flasher -i icon.ico esphomeflasher\__main__.py`
|
||||||
|
7. Go to `dist` folder, check ESPHome-Flasher.exe works.
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- mode: python -*-
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['nodemcu-pyflasher.py'],
|
|
||||||
binaries=None,
|
|
||||||
datas=[("images", "images")],
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data,
|
|
||||||
cipher=block_cipher)
|
|
||||||
exe = EXE(pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
name='NodeMCU-PyFlasher',
|
|
||||||
debug=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False , icon='images/icon-256.icns')
|
|
||||||
app = BUNDLE(exe,
|
|
||||||
name='NodeMCU-PyFlasher.app',
|
|
||||||
icon='./images/icon-256.icns',
|
|
||||||
bundle_identifier='com.frightanic.nodemcu-pyflasher')
|
|
|
@ -1,26 +0,0 @@
|
||||||
# -*- mode: python -*-
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
a = Analysis(['nodemcu-pyflasher.py'],
|
|
||||||
binaries=[],
|
|
||||||
datas=[("images", "images")],
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data,
|
|
||||||
cipher=block_cipher)
|
|
||||||
exe = EXE(pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
name='NodeMCU-PyFlasher',
|
|
||||||
debug=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False , icon='images\\icon-256.ico')
|
|
|
@ -1,3 +0,0 @@
|
||||||
pyinstaller --log-level=DEBUG ^
|
|
||||||
--noconfirm ^
|
|
||||||
build-on-win.spec
|
|
5
build.sh
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
#rm -fr build dist
|
|
||||||
pyinstaller --log-level=DEBUG \
|
|
||||||
--noconfirm \
|
|
||||||
build-on-mac.spec
|
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
"""
|
|
||||||
This is a way to save the startup time when running img2py on lots of
|
|
||||||
files...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from wx.tools import img2py
|
|
||||||
|
|
||||||
command_lines = [
|
|
||||||
"-F -n Exit images/exit.png images.py",
|
|
||||||
"-a -F -n Reload images/reload.png images.py",
|
|
||||||
"-a -F -n Splash images/splash.png images.py",
|
|
||||||
"-a -F -n Info images/info.png images.py",
|
|
||||||
"-a -F -i -n Icon images/icon-256.png images.py",
|
|
||||||
]
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
for line in command_lines:
|
|
||||||
args = line.split()
|
|
||||||
img2py.main(args)
|
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import esptool
|
||||||
|
import serial
|
||||||
|
|
||||||
|
from esphomeflasher import const
|
||||||
|
from esphomeflasher.common import (
|
||||||
|
ESP32ChipInfo,
|
||||||
|
EsphomeflasherError,
|
||||||
|
chip_run_stub,
|
||||||
|
configure_write_flash_args,
|
||||||
|
detect_chip,
|
||||||
|
detect_flash_size,
|
||||||
|
read_chip_info,
|
||||||
|
)
|
||||||
|
from esphomeflasher.const import (
|
||||||
|
ESP32_DEFAULT_BOOTLOADER_FORMAT,
|
||||||
|
ESP32_DEFAULT_OTA_DATA,
|
||||||
|
ESP32_DEFAULT_PARTITIONS,
|
||||||
|
)
|
||||||
|
from esphomeflasher.helpers import list_serial_ports
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(argv):
|
||||||
|
parser = argparse.ArgumentParser(prog=f"esphomeflasher {const.__version__}")
|
||||||
|
parser.add_argument("-p", "--port", help="Select the USB/COM port for uploading.")
|
||||||
|
group = parser.add_mutually_exclusive_group(required=False)
|
||||||
|
group.add_argument("--esp8266", action="store_true")
|
||||||
|
group.add_argument("--esp32", action="store_true")
|
||||||
|
group.add_argument(
|
||||||
|
"--upload-baud-rate",
|
||||||
|
type=int,
|
||||||
|
default=460800,
|
||||||
|
help="Baud rate to upload with (not for logging)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--bootloader",
|
||||||
|
help="(ESP32-only) The bootloader to flash.",
|
||||||
|
default=ESP32_DEFAULT_BOOTLOADER_FORMAT,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--partitions",
|
||||||
|
help="(ESP32-only) The partitions to flash.",
|
||||||
|
default=ESP32_DEFAULT_PARTITIONS,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--otadata",
|
||||||
|
help="(ESP32-only) The otadata file to flash.",
|
||||||
|
default=ESP32_DEFAULT_OTA_DATA,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-erase", help="Do not erase flash before flashing", action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument("--show-logs", help="Only show logs", action="store_true")
|
||||||
|
parser.add_argument("binary", help="The binary image to flash.")
|
||||||
|
|
||||||
|
return parser.parse_args(argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def select_port(args):
|
||||||
|
if args.port is not None:
|
||||||
|
print(f"Using '{args.port}' as serial port.")
|
||||||
|
return args.port
|
||||||
|
ports = list_serial_ports()
|
||||||
|
if not ports:
|
||||||
|
raise EsphomeflasherError("No serial port found!")
|
||||||
|
if len(ports) != 1:
|
||||||
|
print("Found more than one serial port:")
|
||||||
|
for port, desc in ports:
|
||||||
|
print(f" * {port} ({desc})")
|
||||||
|
print("Please choose one with the --port argument.")
|
||||||
|
raise EsphomeflasherError
|
||||||
|
print(f"Auto-detected serial port: {ports[0][0]}")
|
||||||
|
return ports[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def show_logs(serial_port):
|
||||||
|
print("Showing logs:")
|
||||||
|
with serial_port:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
raw = serial_port.readline()
|
||||||
|
except serial.SerialException:
|
||||||
|
print("Serial port closed!")
|
||||||
|
return
|
||||||
|
text = raw.decode(errors="ignore")
|
||||||
|
line = text.replace("\r", "").replace("\n", "")
|
||||||
|
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
|
message = time_ + line
|
||||||
|
try:
|
||||||
|
print(message)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
print(message.encode("ascii", "backslashreplace"))
|
||||||
|
|
||||||
|
|
||||||
|
def run_esphomeflasher(argv):
|
||||||
|
args = parse_args(argv)
|
||||||
|
port = select_port(args)
|
||||||
|
|
||||||
|
if args.show_logs:
|
||||||
|
serial_port = serial.Serial(port, baudrate=115200)
|
||||||
|
show_logs(serial_port)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=consider-using-with
|
||||||
|
firmware = open(args.binary, "rb")
|
||||||
|
except IOError as err:
|
||||||
|
raise EsphomeflasherError(f"Error opening binary: {err}") from err
|
||||||
|
chip = detect_chip(port, args.esp8266, args.esp32)
|
||||||
|
info = read_chip_info(chip)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Chip Info:")
|
||||||
|
print(f" - Chip Family: {info.family}")
|
||||||
|
print(f" - Chip Model: {info.model}")
|
||||||
|
if isinstance(info, ESP32ChipInfo):
|
||||||
|
print(f" - Number of Cores: {info.num_cores}")
|
||||||
|
print(f" - Max CPU Frequency: {info.cpu_frequency}")
|
||||||
|
print(f" - Has Bluetooth: {'YES' if info.has_bluetooth else 'NO'}")
|
||||||
|
print(f" - Has Embedded Flash: {'YES' if info.has_embedded_flash else 'NO'}")
|
||||||
|
print(
|
||||||
|
f" - Has Factory-Calibrated ADC: {'YES' if info.has_factory_calibrated_adc else 'NO'}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f" - Chip ID: {info.chip_id:08X}")
|
||||||
|
|
||||||
|
print(f" - MAC Address: {info.mac}")
|
||||||
|
|
||||||
|
stub_chip = chip_run_stub(chip)
|
||||||
|
flash_size = None
|
||||||
|
|
||||||
|
if args.upload_baud_rate != 115200:
|
||||||
|
try:
|
||||||
|
stub_chip.change_baud(args.upload_baud_rate)
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"Error changing ESP upload baud rate: {err}"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
# Check if the higher baud rate works
|
||||||
|
try:
|
||||||
|
flash_size = detect_flash_size(stub_chip)
|
||||||
|
except EsphomeflasherError:
|
||||||
|
# Go back to old baud rate by recreating chip instance
|
||||||
|
print(
|
||||||
|
f"Chip does not support baud rate {args.upload_baud_rate}, changing to 115200"
|
||||||
|
)
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
stub_chip._port.close()
|
||||||
|
chip = detect_chip(port, args.esp8266, args.esp32)
|
||||||
|
stub_chip = chip_run_stub(chip)
|
||||||
|
|
||||||
|
if flash_size is None:
|
||||||
|
flash_size = detect_flash_size(stub_chip)
|
||||||
|
|
||||||
|
print(f" - Flash Size: {flash_size}")
|
||||||
|
|
||||||
|
mock_args = configure_write_flash_args(
|
||||||
|
info, firmware, flash_size, args.bootloader, args.partitions, args.otadata
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" - Flash Mode: {mock_args.flash_mode}")
|
||||||
|
print(f" - Flash Frequency: {mock_args.flash_freq.upper()}Hz")
|
||||||
|
|
||||||
|
try:
|
||||||
|
stub_chip.flash_set_parameters(esptool.flash_size_bytes(flash_size))
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(f"Error setting flash parameters: {err}") from err
|
||||||
|
|
||||||
|
if not args.no_erase:
|
||||||
|
try:
|
||||||
|
esptool.erase_flash(stub_chip, mock_args)
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(f"Error while erasing flash: {err}") from err
|
||||||
|
|
||||||
|
try:
|
||||||
|
esptool.write_flash(stub_chip, mock_args)
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(f"Error while writing flash: {err}") from err
|
||||||
|
|
||||||
|
print("Hard Resetting...")
|
||||||
|
stub_chip.hard_reset()
|
||||||
|
|
||||||
|
print("Done! Flashing is complete!")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if args.upload_baud_rate != 115200:
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
stub_chip._port.baudrate = 115200
|
||||||
|
time.sleep(0.05) # get rid of crap sent during baud rate change
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
stub_chip._port.flushInput()
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
show_logs(stub_chip._port)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
if len(sys.argv) <= 1:
|
||||||
|
from esphomeflasher import gui
|
||||||
|
|
||||||
|
return gui.main() or 0
|
||||||
|
return run_esphomeflasher(sys.argv) or 0
|
||||||
|
except EsphomeflasherError as err:
|
||||||
|
msg = str(err)
|
||||||
|
if msg:
|
||||||
|
print(msg)
|
||||||
|
return 1
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
|
@ -0,0 +1,234 @@
|
||||||
|
import io
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import esptool
|
||||||
|
|
||||||
|
from esphomeflasher.const import HTTP_REGEX
|
||||||
|
from esphomeflasher.helpers import prevent_print
|
||||||
|
|
||||||
|
|
||||||
|
class EsphomeflasherError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MockEsptoolArgs:
|
||||||
|
def __init__(self, flash_size, addr_filename, flash_mode, flash_freq):
|
||||||
|
self.compress = True
|
||||||
|
self.no_compress = False
|
||||||
|
self.flash_size = flash_size
|
||||||
|
self.addr_filename = addr_filename
|
||||||
|
self.flash_mode = flash_mode
|
||||||
|
self.flash_freq = flash_freq
|
||||||
|
self.no_stub = False
|
||||||
|
self.verify = False
|
||||||
|
self.erase_all = False
|
||||||
|
self.encrypt = False
|
||||||
|
self.encrypt_files = None
|
||||||
|
|
||||||
|
|
||||||
|
class ChipInfo:
|
||||||
|
def __init__(self, family, model, mac):
|
||||||
|
self.family = family
|
||||||
|
self.model = model
|
||||||
|
self.mac = mac
|
||||||
|
self.is_esp32 = None
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"family": self.family,
|
||||||
|
"model": self.model,
|
||||||
|
"mac": self.mac,
|
||||||
|
"is_esp32": self.is_esp32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ESP32ChipInfo(ChipInfo):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model,
|
||||||
|
mac,
|
||||||
|
num_cores,
|
||||||
|
cpu_frequency,
|
||||||
|
has_bluetooth,
|
||||||
|
has_embedded_flash,
|
||||||
|
has_factory_calibrated_adc,
|
||||||
|
):
|
||||||
|
super().__init__("ESP32", model, mac)
|
||||||
|
self.num_cores = num_cores
|
||||||
|
self.cpu_frequency = cpu_frequency
|
||||||
|
self.has_bluetooth = has_bluetooth
|
||||||
|
self.has_embedded_flash = has_embedded_flash
|
||||||
|
self.has_factory_calibrated_adc = has_factory_calibrated_adc
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
data = ChipInfo.as_dict(self)
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
"num_cores": self.num_cores,
|
||||||
|
"cpu_frequency": self.cpu_frequency,
|
||||||
|
"has_bluetooth": self.has_bluetooth,
|
||||||
|
"has_embedded_flash": self.has_embedded_flash,
|
||||||
|
"has_factory_calibrated_adc": self.has_factory_calibrated_adc,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ESP8266ChipInfo(ChipInfo):
|
||||||
|
def __init__(self, model, mac, chip_id):
|
||||||
|
super().__init__("ESP8266", model, mac)
|
||||||
|
self.chip_id = chip_id
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
data = ChipInfo.as_dict(self)
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
"chip_id": self.chip_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def read_chip_property(func, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return prevent_print(func, *args, **kwargs)
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(f"Reading chip details failed: {err}") from err
|
||||||
|
|
||||||
|
|
||||||
|
def read_chip_info(chip):
|
||||||
|
mac = ":".join(f"{x:02X}" for x in read_chip_property(chip.read_mac))
|
||||||
|
if isinstance(chip, esptool.ESP32ROM):
|
||||||
|
model = read_chip_property(chip.get_chip_description)
|
||||||
|
features = read_chip_property(chip.get_chip_features)
|
||||||
|
num_cores = 2 if "Dual Core" in features else 1
|
||||||
|
frequency = next((x for x in ("160MHz", "240MHz") if x in features), "80MHz")
|
||||||
|
has_bluetooth = "BT" in features
|
||||||
|
has_embedded_flash = "Embedded Flash" in features
|
||||||
|
has_factory_calibrated_adc = "VRef calibration in efuse" in features
|
||||||
|
return ESP32ChipInfo(
|
||||||
|
model,
|
||||||
|
mac,
|
||||||
|
num_cores,
|
||||||
|
frequency,
|
||||||
|
has_bluetooth,
|
||||||
|
has_embedded_flash,
|
||||||
|
has_factory_calibrated_adc,
|
||||||
|
)
|
||||||
|
if isinstance(chip, esptool.ESP8266ROM):
|
||||||
|
model = read_chip_property(chip.get_chip_description)
|
||||||
|
chip_id = read_chip_property(chip.chip_id)
|
||||||
|
return ESP8266ChipInfo(model, mac, chip_id)
|
||||||
|
raise EsphomeflasherError(f"Unknown chip type {type(chip)}")
|
||||||
|
|
||||||
|
|
||||||
|
def chip_run_stub(chip):
|
||||||
|
try:
|
||||||
|
return chip.run_stub()
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"Error putting ESP in stub flash mode: {err}"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
|
||||||
|
def detect_flash_size(stub_chip):
|
||||||
|
flash_id = read_chip_property(stub_chip.flash_id)
|
||||||
|
return esptool.DETECTED_FLASH_SIZES.get(flash_id >> 16, "4MB")
|
||||||
|
|
||||||
|
|
||||||
|
def read_firmware_info(firmware):
|
||||||
|
header = firmware.read(4)
|
||||||
|
firmware.seek(0)
|
||||||
|
|
||||||
|
magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header)
|
||||||
|
if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC:
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"The firmware binary is invalid (magic byte={magic:02X}, should be {esptool.ESPLoader.ESP_IMAGE_MAGIC:02X})"
|
||||||
|
)
|
||||||
|
flash_freq_raw = flash_size_freq & 0x0F
|
||||||
|
flash_mode = {0: "qio", 1: "qout", 2: "dio", 3: "dout"}.get(flash_mode_raw)
|
||||||
|
flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw)
|
||||||
|
return flash_mode, flash_freq
|
||||||
|
|
||||||
|
|
||||||
|
def open_downloadable_binary(path):
|
||||||
|
if hasattr(path, "seek"):
|
||||||
|
path.seek(0)
|
||||||
|
return path
|
||||||
|
|
||||||
|
if HTTP_REGEX.match(path) is not None:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(path)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.exceptions.Timeout as err:
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"Timeout while retrieving firmware file '{path}': {err}"
|
||||||
|
) from err
|
||||||
|
except requests.exceptions.RequestException as err:
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"Error while retrieving firmware file '{path}': {err}"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
binary = io.BytesIO()
|
||||||
|
binary.write(response.content)
|
||||||
|
binary.seek(0)
|
||||||
|
return binary
|
||||||
|
|
||||||
|
try:
|
||||||
|
return open(path, "rb")
|
||||||
|
except IOError as err:
|
||||||
|
raise EsphomeflasherError(f"Error opening binary '{path}': {err}") from err
|
||||||
|
|
||||||
|
|
||||||
|
def format_bootloader_path(path, flash_mode, flash_freq):
|
||||||
|
return path.replace("$FLASH_MODE$", flash_mode).replace("$FLASH_FREQ$", flash_freq)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_write_flash_args(
|
||||||
|
info, firmware_path, flash_size, bootloader_path, partitions_path, otadata_path
|
||||||
|
):
|
||||||
|
addr_filename = []
|
||||||
|
firmware = open_downloadable_binary(firmware_path)
|
||||||
|
flash_mode, flash_freq = read_firmware_info(firmware)
|
||||||
|
if isinstance(info, ESP32ChipInfo):
|
||||||
|
if flash_freq in ("26m", "20m"):
|
||||||
|
raise EsphomeflasherError(
|
||||||
|
f"No bootloader available for flash frequency {flash_freq}"
|
||||||
|
)
|
||||||
|
bootloader = open_downloadable_binary(
|
||||||
|
format_bootloader_path(bootloader_path, flash_mode, flash_freq)
|
||||||
|
)
|
||||||
|
partitions = open_downloadable_binary(partitions_path)
|
||||||
|
otadata = open_downloadable_binary(otadata_path)
|
||||||
|
|
||||||
|
addr_filename.append((0x1000, bootloader))
|
||||||
|
addr_filename.append((0x8000, partitions))
|
||||||
|
addr_filename.append((0xE000, otadata))
|
||||||
|
addr_filename.append((0x10000, firmware))
|
||||||
|
else:
|
||||||
|
addr_filename.append((0x0, firmware))
|
||||||
|
return MockEsptoolArgs(flash_size, addr_filename, flash_mode, flash_freq)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_chip(port, force_esp8266=False, force_esp32=False):
|
||||||
|
if force_esp8266 or force_esp32:
|
||||||
|
klass = esptool.ESP32ROM if force_esp32 else esptool.ESP8266ROM
|
||||||
|
chip = klass(port)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
chip = esptool.ESPLoader.detect_chip(port)
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
if "Wrong boot mode detected" in str(err):
|
||||||
|
msg = "ESP is not in flash boot mode. If your board has a flashing pin, try again while keeping it pressed."
|
||||||
|
else:
|
||||||
|
msg = f"ESP Chip Auto-Detection failed: {err}"
|
||||||
|
raise EsphomeflasherError(msg) from err
|
||||||
|
|
||||||
|
try:
|
||||||
|
chip.connect()
|
||||||
|
except esptool.FatalError as err:
|
||||||
|
raise EsphomeflasherError(f"Error connecting to ESP: {err}") from err
|
||||||
|
|
||||||
|
return chip
|
|
@ -0,0 +1,17 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
__version__ = "1.4.0"
|
||||||
|
|
||||||
|
ESP32_DEFAULT_OTA_DATA = "https://raw.githubusercontent.com/espressif/arduino-esp32/1.0.0/tools/partitions/boot_app0.bin"
|
||||||
|
ESP32_DEFAULT_BOOTLOADER_FORMAT = (
|
||||||
|
"https://raw.githubusercontent.com/espressif/arduino-esp32/"
|
||||||
|
"1.0.4/tools/sdk/bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.bin"
|
||||||
|
)
|
||||||
|
ESP32_DEFAULT_PARTITIONS = (
|
||||||
|
"https://raw.githubusercontent.com/esphome/esphomeflasher/main/partitions.bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://stackoverflow.com/a/3809435/8924614
|
||||||
|
HTTP_REGEX = re.compile(
|
||||||
|
r"https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)"
|
||||||
|
)
|
|
@ -0,0 +1,371 @@
|
||||||
|
# This GUI is a fork of the brilliant https://github.com/marcelstoer/nodemcu-pyflasher
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from io import TextIOBase
|
||||||
|
|
||||||
|
import wx
|
||||||
|
import wx.adv
|
||||||
|
import wx.lib.inspection
|
||||||
|
import wx.lib.mixins.inspection
|
||||||
|
from wx.lib.embeddedimage import PyEmbeddedImage
|
||||||
|
|
||||||
|
from esphomeflasher.helpers import list_serial_ports
|
||||||
|
|
||||||
|
# pylint: disable=no-member
|
||||||
|
|
||||||
|
COLOR_RE = re.compile(r"(?:\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))")
|
||||||
|
COLORS = {
|
||||||
|
"black": wx.BLACK,
|
||||||
|
"red": wx.RED,
|
||||||
|
"green": wx.GREEN,
|
||||||
|
"yellow": wx.YELLOW,
|
||||||
|
"blue": wx.BLUE,
|
||||||
|
"magenta": wx.Colour(255, 0, 255),
|
||||||
|
"cyan": wx.CYAN,
|
||||||
|
"white": wx.WHITE,
|
||||||
|
}
|
||||||
|
FORE_COLORS = {**COLORS, None: wx.WHITE}
|
||||||
|
BACK_COLORS = {**COLORS, None: wx.BLACK}
|
||||||
|
|
||||||
|
|
||||||
|
# See discussion at http://stackoverflow.com/q/41101897/131929
|
||||||
|
class RedirectText(TextIOBase):
|
||||||
|
def __init__(self, text_ctrl):
|
||||||
|
super().__init__()
|
||||||
|
self._out = text_ctrl
|
||||||
|
self._i = 0
|
||||||
|
self._line = ""
|
||||||
|
self._bold = False
|
||||||
|
self._italic = False
|
||||||
|
self._underline = False
|
||||||
|
self._foreground = None
|
||||||
|
self._background = None
|
||||||
|
self._secret = False
|
||||||
|
|
||||||
|
def _add_content(self, value):
|
||||||
|
attr = wx.TextAttr()
|
||||||
|
if self._bold:
|
||||||
|
attr.SetFontWeight(wx.FONTWEIGHT_BOLD)
|
||||||
|
attr.SetTextColour(FORE_COLORS[self._foreground])
|
||||||
|
attr.SetBackgroundColour(BACK_COLORS[self._background])
|
||||||
|
wx.CallAfter(self._out.SetDefaultStyle, attr)
|
||||||
|
wx.CallAfter(self._out.AppendText, value)
|
||||||
|
|
||||||
|
def _write_line(self):
|
||||||
|
pos = 0
|
||||||
|
while True:
|
||||||
|
match = COLOR_RE.search(self._line, pos)
|
||||||
|
if match is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
j = match.start()
|
||||||
|
self._add_content(self._line[pos:j])
|
||||||
|
pos = match.end()
|
||||||
|
|
||||||
|
for code in match.group(1).split(";"):
|
||||||
|
code = int(code)
|
||||||
|
if code == 0:
|
||||||
|
self._bold = False
|
||||||
|
self._italic = False
|
||||||
|
self._underline = False
|
||||||
|
self._foreground = None
|
||||||
|
self._background = None
|
||||||
|
self._secret = False
|
||||||
|
elif code == 1:
|
||||||
|
self._bold = True
|
||||||
|
elif code == 3:
|
||||||
|
self._italic = True
|
||||||
|
elif code == 4:
|
||||||
|
self._underline = True
|
||||||
|
elif code == 5:
|
||||||
|
self._secret = True
|
||||||
|
elif code == 6:
|
||||||
|
self._secret = False
|
||||||
|
elif code == 22:
|
||||||
|
self._bold = False
|
||||||
|
elif code == 23:
|
||||||
|
self._italic = False
|
||||||
|
elif code == 24:
|
||||||
|
self._underline = False
|
||||||
|
elif code == 30:
|
||||||
|
self._foreground = "black"
|
||||||
|
elif code == 31:
|
||||||
|
self._foreground = "red"
|
||||||
|
elif code == 32:
|
||||||
|
self._foreground = "green"
|
||||||
|
elif code == 33:
|
||||||
|
self._foreground = "yellow"
|
||||||
|
elif code == 34:
|
||||||
|
self._foreground = "blue"
|
||||||
|
elif code == 35:
|
||||||
|
self._foreground = "magenta"
|
||||||
|
elif code == 36:
|
||||||
|
self._foreground = "cyan"
|
||||||
|
elif code == 37:
|
||||||
|
self._foreground = "white"
|
||||||
|
elif code == 39:
|
||||||
|
self._foreground = None
|
||||||
|
elif code == 40:
|
||||||
|
self._background = "black"
|
||||||
|
elif code == 41:
|
||||||
|
self._background = "red"
|
||||||
|
elif code == 42:
|
||||||
|
self._background = "green"
|
||||||
|
elif code == 43:
|
||||||
|
self._background = "yellow"
|
||||||
|
elif code == 44:
|
||||||
|
self._background = "blue"
|
||||||
|
elif code == 45:
|
||||||
|
self._background = "magenta"
|
||||||
|
elif code == 46:
|
||||||
|
self._background = "cyan"
|
||||||
|
elif code == 47:
|
||||||
|
self._background = "white"
|
||||||
|
elif code == 49:
|
||||||
|
self._background = None
|
||||||
|
|
||||||
|
self._add_content(self._line[pos:])
|
||||||
|
|
||||||
|
def write(self, string):
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
for s in string:
|
||||||
|
if s == "\r":
|
||||||
|
current_value = self._out.GetValue()
|
||||||
|
last_newline = current_value.rfind("\n")
|
||||||
|
wx.CallAfter(self._out.Remove, last_newline + 1, len(current_value))
|
||||||
|
# self._line += '\n'
|
||||||
|
self._write_line()
|
||||||
|
self._line = ""
|
||||||
|
continue
|
||||||
|
self._line += s
|
||||||
|
if s == "\n":
|
||||||
|
self._write_line()
|
||||||
|
self._line = ""
|
||||||
|
continue
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isatty(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class FlashingThread(threading.Thread):
|
||||||
|
def __init__(self, parent, firmware, port, show_logs=False):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self._parent = parent
|
||||||
|
self._firmware = firmware
|
||||||
|
self._port = port
|
||||||
|
self._show_logs = show_logs
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
from esphomeflasher.__main__ import run_esphomeflasher
|
||||||
|
|
||||||
|
argv = ["esphomeflasher", "--port", self._port, self._firmware]
|
||||||
|
if self._show_logs:
|
||||||
|
argv.append("--show-logs")
|
||||||
|
run_esphomeflasher(argv)
|
||||||
|
except Exception as err:
|
||||||
|
print(f"Unexpected error: {err}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class MainFrame(wx.Frame):
|
||||||
|
def __init__(self, parent, title):
|
||||||
|
wx.Frame.__init__(
|
||||||
|
self,
|
||||||
|
parent,
|
||||||
|
-1,
|
||||||
|
title,
|
||||||
|
size=(725, 650),
|
||||||
|
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._firmware = None
|
||||||
|
self._port = None
|
||||||
|
|
||||||
|
self._init_ui()
|
||||||
|
|
||||||
|
sys.stdout = RedirectText(self.console_ctrl)
|
||||||
|
|
||||||
|
self.SetMinSize((640, 480))
|
||||||
|
self.Centre(wx.BOTH)
|
||||||
|
self.Show(True)
|
||||||
|
|
||||||
|
def _init_ui(self):
|
||||||
|
def on_reload(event):
|
||||||
|
self.choice.SetItems(self._get_serial_ports())
|
||||||
|
|
||||||
|
def on_clicked(event):
|
||||||
|
self.console_ctrl.SetValue("")
|
||||||
|
worker = FlashingThread(self, self._firmware, self._port)
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
def on_logs_clicked(event):
|
||||||
|
self.console_ctrl.SetValue("")
|
||||||
|
worker = FlashingThread(self, "dummy", self._port, show_logs=True)
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
def on_select_port(event):
|
||||||
|
choice = event.GetEventObject()
|
||||||
|
self._port = choice.GetString(choice.GetSelection())
|
||||||
|
|
||||||
|
def on_pick_file(event):
|
||||||
|
self._firmware = event.GetPath().replace("'", "")
|
||||||
|
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
|
||||||
|
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
|
||||||
|
fgs = wx.FlexGridSizer(7, 2, 10, 10)
|
||||||
|
|
||||||
|
self.choice = wx.Choice(panel, choices=self._get_serial_ports())
|
||||||
|
self.choice.Bind(wx.EVT_CHOICE, on_select_port)
|
||||||
|
bmp = Reload.GetBitmap()
|
||||||
|
reload_button = wx.BitmapButton(
|
||||||
|
panel,
|
||||||
|
id=wx.ID_ANY,
|
||||||
|
bitmap=bmp,
|
||||||
|
size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7),
|
||||||
|
)
|
||||||
|
reload_button.Bind(wx.EVT_BUTTON, on_reload)
|
||||||
|
reload_button.SetToolTip("Reload serial device list")
|
||||||
|
|
||||||
|
file_picker = wx.FilePickerCtrl(panel, style=wx.FLP_USE_TEXTCTRL)
|
||||||
|
file_picker.Bind(wx.EVT_FILEPICKER_CHANGED, on_pick_file)
|
||||||
|
|
||||||
|
serial_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
serial_boxsizer.Add(self.choice, 1, wx.EXPAND)
|
||||||
|
serial_boxsizer.AddStretchSpacer(0)
|
||||||
|
serial_boxsizer.Add(reload_button, 0, wx.ALIGN_NOT, 20)
|
||||||
|
|
||||||
|
button = wx.Button(panel, -1, "Flash ESP")
|
||||||
|
button.Bind(wx.EVT_BUTTON, on_clicked)
|
||||||
|
|
||||||
|
logs_button = wx.Button(panel, -1, "View Logs")
|
||||||
|
logs_button.Bind(wx.EVT_BUTTON, on_logs_clicked)
|
||||||
|
|
||||||
|
self.console_ctrl = wx.TextCtrl(
|
||||||
|
panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL
|
||||||
|
)
|
||||||
|
self.console_ctrl.SetFont(
|
||||||
|
wx.Font(
|
||||||
|
(0, 13),
|
||||||
|
wx.FONTFAMILY_TELETYPE,
|
||||||
|
wx.FONTSTYLE_NORMAL,
|
||||||
|
wx.FONTWEIGHT_NORMAL,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.console_ctrl.SetBackgroundColour(wx.BLACK)
|
||||||
|
self.console_ctrl.SetForegroundColour(wx.WHITE)
|
||||||
|
self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.WHITE))
|
||||||
|
|
||||||
|
port_label = wx.StaticText(panel, label="Serial port")
|
||||||
|
file_label = wx.StaticText(panel, label="Firmware")
|
||||||
|
|
||||||
|
console_label = wx.StaticText(panel, label="Console")
|
||||||
|
|
||||||
|
fgs.AddMany(
|
||||||
|
[
|
||||||
|
# Port selection row
|
||||||
|
port_label,
|
||||||
|
(serial_boxsizer, 1, wx.EXPAND),
|
||||||
|
# Firmware selection row (growable)
|
||||||
|
file_label,
|
||||||
|
(file_picker, 1, wx.EXPAND),
|
||||||
|
# Flash ESP button
|
||||||
|
(wx.StaticText(panel, label="")),
|
||||||
|
(button, 1, wx.EXPAND),
|
||||||
|
# View Logs button
|
||||||
|
(wx.StaticText(panel, label="")),
|
||||||
|
(logs_button, 1, wx.EXPAND),
|
||||||
|
# Console View (growable)
|
||||||
|
(console_label, 1, wx.EXPAND),
|
||||||
|
(self.console_ctrl, 1, wx.EXPAND),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
fgs.AddGrowableRow(4, 1)
|
||||||
|
fgs.AddGrowableCol(1, 1)
|
||||||
|
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
|
||||||
|
panel.SetSizer(hbox)
|
||||||
|
|
||||||
|
def _get_serial_ports(self):
|
||||||
|
ports = []
|
||||||
|
for port, _ in list_serial_ports():
|
||||||
|
ports.append(port)
|
||||||
|
if not self._port and ports:
|
||||||
|
self._port = ports[0]
|
||||||
|
if not ports:
|
||||||
|
ports.append("")
|
||||||
|
return ports
|
||||||
|
|
||||||
|
# Menu methods
|
||||||
|
def _on_exit_app(self, event):
|
||||||
|
self.Close(True)
|
||||||
|
|
||||||
|
def log_message(self, message):
|
||||||
|
self.console_ctrl.AppendText(message)
|
||||||
|
|
||||||
|
|
||||||
|
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
def OnInit(self):
|
||||||
|
wx.SystemOptions.SetOption("mac.window-plain-transition", 1)
|
||||||
|
self.SetAppName("esphome-flasher (Based on NodeMCU PyFlasher)")
|
||||||
|
|
||||||
|
frame = MainFrame(None, "esphome-flasher (Based on NodeMCU PyFlasher)")
|
||||||
|
frame.Show()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
Exit = PyEmbeddedImage(
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0"
|
||||||
|
"RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAN1SURBVHjaYvz//z8DJQAggFhA"
|
||||||
|
"xEpGRgaQMX+B+A8DgwYLM1M+r4K8P4+8vMi/P38Y3j18+O7Fs+fbvv7+0w9Uc/kHVG070HKA"
|
||||||
|
"AGJBNg0omC5jZtynnpfHJeHkzPDmxQuGf6/eMIj+/yP+9MD+xFPrN8Reu3W3Gqi0D2IXAwNA"
|
||||||
|
"AIEN+A/hpWuEBMwwmj6TgUVEjOHTo0cM9y9dZfj76ycDCysrg4K5FYMUvyAL7+pVnYfOXwJp"
|
||||||
|
"6wIRAAHECAqDJYyMWpLmpmftN2/mYBEVZ3h38SLD9wcPGP6LioIN/7Z+PQM3UB3vv/8MXB/f"
|
||||||
|
"MSzdvv3vpecvzfr+/z8HEEBMYFMYGXM0iwrAmu+sXcvw4OxZhqenTjEwAv3P9OsXw+unTxne"
|
||||||
|
"6Osz3Ll3l+HvyzcMVlLSzMBwqgTpBQggsAG8MuKB4r9eM7zfv5PhHxMzg4qLCwPD0ycMDL9/"
|
||||||
|
"MzD+/cvw/8kTBgUbGwbB1DSGe1cuMbD8+8EgwMPjCtILEEDgMOCSkhT+t20Nw4v7nxkkNuxm"
|
||||||
|
"eLNmFYO0sCgDCwcHAwMzM4Pkl68MLzs7GGS6uhmOCwgxcD2+x8DLysID0gsQQGAD/gH99vPL"
|
||||||
|
"dwZGDjaG/0An/z19goHp/z+Gn9dvgoP4/7dPDD9OnGD4+/0bA5uCAsPPW8DA5eACxxxAAIEN"
|
||||||
|
"+PDuw/ufirJizE9fMzALCjD8efOO4dHObQx/d29k+PObgeHr268MQta2DCw8fAz/X75k+M/I"
|
||||||
|
"xPDh1+9vIL0AAQQOg9dPX2x7w8TDwPL2FcOvI8cYxFs7GFjFpRl+PP/K8O3NVwZuIREGpe5u"
|
||||||
|
"hp83rjF8u3iO4RsnO8OzHz8PgvQCBBA4GrsZGfUUtNXPWiuLsny59YxBch3Qdl4uhq/rNzP8"
|
||||||
|
"BwYin58PAysbG8MFLy+Gnw9uM5xkYPp38fNX22X//x8DCCAmqD8u3bh6s+Lssy8MrCLcDC/8"
|
||||||
|
"3Rl+LVvOwG1syMBrYcbwfetmhmsOdgy/795iuMXEwnDh89c2oJ7jIL0AAQR2wQRgXvgKNAfo"
|
||||||
|
"qRIlJfk2NR42Rj5gEmb5+4/h35+/DJ+/fmd4DUyNN4B+v/DlWwcwcTWzA9PXQqBegACCGwAK"
|
||||||
|
"ERD+zsBgwszOXirEwe7OzvCP5y/QCx/+/v/26vfv/R///O0GOvkII1AdKxCDDAAIIEZKszNA"
|
||||||
|
"gAEA1sFjF+2KokIAAAAASUVORK5CYII="
|
||||||
|
)
|
||||||
|
|
||||||
|
Reload = PyEmbeddedImage(
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBj"
|
||||||
|
"SFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAABmJLR0QA/wD/AP+g"
|
||||||
|
"vaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAYAAAAGAB4TKWmAAACZUlEQVRI"
|
||||||
|
"x7XVT4iXRRgH8M/8Mv9tUFgRZiBESRIhbFAo8kJ0EYoOwtJBokvTxUtBQnUokIjAoCi6+HiR"
|
||||||
|
"CNKoU4GHOvQieygMJKRDEUiahC4UtGkb63TY+cnb6/rb3276vQwzzzPf5/9MKqW4kRj8n8s5"
|
||||||
|
"53U55y03xEDOeRu+xe5ReqtWQDzAC3gTa3D7KP20nBrknDfhMB7vHH+Dj3AWxyPitxUZyDnv"
|
||||||
|
"xsElPL6MT/BiRJwbaaBN6eamlH9yzmvxPp5bRibPYDIizg96pIM2pak2pSexGiLiEr7H3DIM"
|
||||||
|
"3IMP/hNBm9It+BDzmGp6oeWcd+BIvdzFRZzGvUOnOtg6qOTrcRxP4ZVmkbxFxDQm8WVPtDMi"
|
||||||
|
"tmIDPu7JJocpehnb8F1Tyo/XijsizmMX9teCwq1VNlvrdKFzZeOgTelOvFQPfurV5NE2pc09"
|
||||||
|
"I/MR8TqewAxu68hmMd1RPzXAw1hXD9b3nL4bJ9qUdi0SzbF699ee6K9ObU6swoMd4Y42pYmm"
|
||||||
|
"lNm6/91C33/RpvQG9jelzHeMnK4F7uK+ur49bNNzHeEdONSmNFH3f9R1gNdwrKZ0UeSc77fQ"
|
||||||
|
"CCfxFqSveQA/9HTn8DM2d9I3xBk83ZQy3SNPFqb4JjwTEX9S56BN6SimjI857GtKea+ST+Cx"
|
||||||
|
"6synETHssCuv6V5sd/UQXQur8VCb0tqmlEuYi4jPF1PsTvJGvFMjGfVPzOD5ppTPxvHkqseu"
|
||||||
|
"Teku7MQm7MEjHfFXeLYp5ey4uRz5XLcpHbAwhH/jVbzblHJ5TG4s/aPN4BT2NKWcXA7xuBFs"
|
||||||
|
"wS9NKRdXQr6kgeuBfwEbWdzTvan9igAAADV0RVh0Y29tbWVudABSZWZyZXNoIGZyb20gSWNv"
|
||||||
|
"biBHYWxsZXJ5IGh0dHA6Ly9pY29uZ2FsLmNvbS/RLzdIAAAAJXRFWHRkYXRlOmNyZWF0ZQAy"
|
||||||
|
"MDExLTA4LTIxVDE0OjAxOjU2LTA2OjAwdNJAnQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0w"
|
||||||
|
"OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII="
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = App(False)
|
||||||
|
app.MainLoop()
|
|
@ -0,0 +1,38 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
# pylint: disable=unspecified-encoding,consider-using-with
|
||||||
|
DEVNULL = open(os.devnull, "w")
|
||||||
|
|
||||||
|
|
||||||
|
def list_serial_ports():
|
||||||
|
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||||
|
from serial.tools.list_ports import comports
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for port, desc, info in comports():
|
||||||
|
if not port or "VID:PID" not in info:
|
||||||
|
continue
|
||||||
|
split_desc = desc.split(" - ")
|
||||||
|
if len(split_desc) == 2 and split_desc[0] == split_desc[1]:
|
||||||
|
desc = split_desc[0]
|
||||||
|
result.append((port, desc))
|
||||||
|
result.sort()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def prevent_print(func, *args, **kwargs):
|
||||||
|
orig_sys_stdout = sys.stdout
|
||||||
|
sys.stdout = DEVNULL
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except serial.SerialException as err:
|
||||||
|
from esphomeflasher.common import EsphomeflasherError
|
||||||
|
|
||||||
|
raise EsphomeflasherError(f"Serial port closed: {err}") from err
|
||||||
|
finally:
|
||||||
|
sys.stdout = orig_sys_stdout
|
|
@ -1,12 +0,0 @@
|
||||||
If esptool.py is installed using python setup.py` from a checked out version it creates something like the following
|
|
||||||
in the Python environment
|
|
||||||
|
|
||||||
esptool.py file containing
|
|
||||||
|
|
||||||
#!/usr/local/opt/python/bin/python2.7
|
|
||||||
# EASY-INSTALL-SCRIPT: 'esptool==1.3.dev0','esptool.py'
|
|
||||||
__requires__ = 'esptool==1.3.dev0'
|
|
||||||
__import__('pkg_resources').run_script('esptool==1.3.dev0', 'esptool.py')
|
|
||||||
|
|
||||||
PyInstaller (and cx_Freeze) doesn't support pkg_resources and complains about 'ImportError: "No module named
|
|
||||||
pkg_resources"'. This can be avoided if the application maintains a local copy of esptool.py.
|
|
2592
esptool.py
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 11 KiB |
BIN
images/exit.png
Before Width: | Height: | Size: 995 B |
BIN
images/gui.png
Before Width: | Height: | Size: 405 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 4.8 KiB |
BIN
images/info.png
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 953 B |
Before Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 9.3 KiB |
|
@ -1,4 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import Main
|
|
||||||
Main.main()
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
multi_line_output = 3
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
target-version = ['py37']
|
||||||
|
|
||||||
|
[tool.pylint.MASTER]
|
||||||
|
reports = 'no'
|
||||||
|
disable = [
|
||||||
|
"too-many-branches",
|
||||||
|
"missing-function-docstring",
|
||||||
|
"missing-module-docstring",
|
||||||
|
"too-many-statements",
|
||||||
|
"import-outside-toplevel",
|
||||||
|
"line-too-long",
|
||||||
|
"missing-class-docstring",
|
||||||
|
"too-few-public-methods",
|
||||||
|
"too-many-arguments",
|
||||||
|
"too-many-instance-attributes",
|
||||||
|
"too-many-locals",
|
||||||
|
"unused-argument",
|
||||||
|
"cyclic-import",
|
||||||
|
]
|
|
@ -0,0 +1,3 @@
|
||||||
|
wxpython>=4.1.1,<5.0
|
||||||
|
esptool==3.2
|
||||||
|
requests>=2.26.0,<3
|
|
@ -0,0 +1,2 @@
|
||||||
|
pyinstaller>=4.5.1,<5
|
||||||
|
wheel
|
|
@ -0,0 +1,4 @@
|
||||||
|
pylint==2.13.4
|
||||||
|
black==22.3.0
|
||||||
|
flake8==4.0.1
|
||||||
|
isort==5.10.1
|
|
@ -0,0 +1,14 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 120
|
||||||
|
# Following 4 for black compatibility
|
||||||
|
# E501: line too long
|
||||||
|
# W503: Line break occurred before a binary operator
|
||||||
|
# E203: Whitespace before ':'
|
||||||
|
# D202 No blank lines allowed after function docstring
|
||||||
|
|
||||||
|
ignore =
|
||||||
|
E501,
|
||||||
|
W503,
|
||||||
|
E203,
|
||||||
|
D202,
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""esphomeflasher setup script."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
from esphomeflasher import const
|
||||||
|
|
||||||
|
PROJECT_NAME = 'esphomeflasher'
|
||||||
|
PROJECT_PACKAGE_NAME = 'esphomeflasher'
|
||||||
|
PROJECT_LICENSE = 'MIT'
|
||||||
|
PROJECT_AUTHOR = 'ESPHome'
|
||||||
|
PROJECT_COPYRIGHT = '2021, ESPHome'
|
||||||
|
PROJECT_URL = 'https://esphome.io/guides/faq.html'
|
||||||
|
PROJECT_EMAIL = 'contact@esphome.io'
|
||||||
|
|
||||||
|
PROJECT_GITHUB_USERNAME = 'esphome'
|
||||||
|
PROJECT_GITHUB_REPOSITORY = 'esphome-flasher'
|
||||||
|
|
||||||
|
PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
|
||||||
|
GITHUB_PATH = '{}/{}'.format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
|
||||||
|
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
|
||||||
|
|
||||||
|
DOWNLOAD_URL = '{}/archive/{}.zip'.format(GITHUB_URL, const.__version__)
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
with open(os.path.join(here, 'requirements.txt')) as requirements_txt:
|
||||||
|
REQUIRES = requirements_txt.read().splitlines()
|
||||||
|
|
||||||
|
with open(os.path.join(here, 'README.md')) as readme:
|
||||||
|
LONG_DESCRIPTION = readme.read()
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=PROJECT_PACKAGE_NAME,
|
||||||
|
version=const.__version__,
|
||||||
|
license=PROJECT_LICENSE,
|
||||||
|
url=GITHUB_URL,
|
||||||
|
download_url=DOWNLOAD_URL,
|
||||||
|
author=PROJECT_AUTHOR,
|
||||||
|
author_email=PROJECT_EMAIL,
|
||||||
|
description="ESP8266/ESP32 firmware flasher for ESPHome",
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
platforms='any',
|
||||||
|
test_suite='tests',
|
||||||
|
python_requires='>=3.7,<4.0',
|
||||||
|
install_requires=REQUIRES,
|
||||||
|
long_description=LONG_DESCRIPTION,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
keywords=['home', 'automation'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'esphomeflasher = esphomeflasher.__main__:main'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
packages=find_packages(include="esphomerelease.*")
|
||||||
|
)
|