Compare commits
No commits in common. "main" and "847226" have entirely different histories.
19 changed files with 105 additions and 706 deletions
|
@ -1,144 +0,0 @@
|
||||||
name: Build all artifacts and make release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
build:
|
|
||||||
description: 'Select build type'
|
|
||||||
required: true
|
|
||||||
default: 'all'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- all
|
|
||||||
- mac
|
|
||||||
- windows
|
|
||||||
- linux
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos:
|
|
||||||
if: ${{ github.event.inputs.build == 'all' || github.event.inputs.build == 'mac' }}
|
|
||||||
runs-on: macos-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download universal2 Python
|
|
||||||
run: |
|
|
||||||
curl -o python.pkg https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg
|
|
||||||
- name: Install Python
|
|
||||||
run: |
|
|
||||||
sudo installer -pkg python.pkg -target /
|
|
||||||
- name: Add Python to PATH
|
|
||||||
run: |
|
|
||||||
echo "/Library/Frameworks/Python.framework/Versions/3.12/bin" >> $GITHUB_PATH
|
|
||||||
- name: Verify Python installation
|
|
||||||
run: |
|
|
||||||
python3 --version
|
|
||||||
pip3 --version
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
pip3 install --upgrade pip
|
|
||||||
pip3 install pyinstaller
|
|
||||||
pip3 install --no-cache cffi --no-binary :all:
|
|
||||||
pip3 install --no-cache charset_normalizer --no-binary :all:
|
|
||||||
pip3 install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython
|
|
||||||
pip3 install -r requirements.txt
|
|
||||||
- name: Build with PyInstaller
|
|
||||||
run: |
|
|
||||||
pyinstaller BTClockOTA-universal.spec
|
|
||||||
- name: Zip the app bundle
|
|
||||||
run: |
|
|
||||||
cd dist
|
|
||||||
zip -r BTClockOTA-macos-universal2.zip BTClockOTA.app
|
|
||||||
- name: Create DMG
|
|
||||||
run: |
|
|
||||||
mkdir dmg_temp
|
|
||||||
cp -R dist/BTClockOTA.app dmg_temp/
|
|
||||||
# Create the DMG file
|
|
||||||
hdiutil create -volname "BTClockOTA" -srcfolder dmg_temp -ov -format UDZO "dist/BTClockOTA-universal.dmg"
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos-artifacts
|
|
||||||
path: |
|
|
||||||
dist/*
|
|
||||||
!dist/BTClockOTA.app
|
|
||||||
build-windows:
|
|
||||||
if: ${{ github.event.inputs.build == 'all' || github.event.inputs.build == 'windows' }}
|
|
||||||
runs-on: docker-amd64
|
|
||||||
container:
|
|
||||||
image: batonogov/pyinstaller-windows:latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Build with PyInstaller
|
|
||||||
run: |
|
|
||||||
python -m PyInstaller $SPECFILE
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-artifacts
|
|
||||||
path: dist/
|
|
||||||
build-linux:
|
|
||||||
if: ${{ github.event.inputs.build == 'all' || github.event.inputs.build == 'linux' }}
|
|
||||||
runs-on: docker-amd64
|
|
||||||
container:
|
|
||||||
image: ghcr.io/btclock/pyinstaller-wxpython-linux:latest
|
|
||||||
credentials:
|
|
||||||
username: dsbaars
|
|
||||||
password: ${{ secrets.GH_TOKEN }}
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Build with PyInstaller
|
|
||||||
run: |
|
|
||||||
python -m PyInstaller $SPECFILE &&
|
|
||||||
mv dist/BTClockOTA dist/BTClockOTA-linux-amd64
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux-artifacts
|
|
||||||
path: dist/
|
|
||||||
release:
|
|
||||||
needs: [build-macos, build-windows, build-linux]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Get current block
|
|
||||||
id: getBlockHeight
|
|
||||||
run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Get Windows Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-artifacts
|
|
||||||
path: windows
|
|
||||||
- name: Get macOS Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos-artifacts
|
|
||||||
path: macos
|
|
||||||
- name: Get Linux Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux-artifacts
|
|
||||||
path: linux
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
commit: main
|
|
||||||
name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
artifacts: "macos/**/*.dmg,macos/**/*.zip,windows/**/*.exe,linux/*"
|
|
||||||
allowUpdates: true
|
|
||||||
makeLatest: true
|
|
154
.github/workflows/build_all.yaml
vendored
154
.github/workflows/build_all.yaml
vendored
|
@ -1,154 +0,0 @@
|
||||||
name: Build all artifacts and make release
|
|
||||||
|
|
||||||
on: workflow_dispatch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download universal2 Python
|
|
||||||
run: |
|
|
||||||
curl -o python.pkg https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg
|
|
||||||
- name: Install Python
|
|
||||||
run: |
|
|
||||||
sudo installer -pkg python.pkg -target /
|
|
||||||
- name: Add Python to PATH
|
|
||||||
run: |
|
|
||||||
echo "/Library/Frameworks/Python.framework/Versions/3.12/bin" >> $GITHUB_PATH
|
|
||||||
- name: Verify Python installation
|
|
||||||
run: |
|
|
||||||
python3 --version
|
|
||||||
pip3 --version
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
pip3 install --upgrade pip
|
|
||||||
pip3 install pyinstaller
|
|
||||||
pip3 install --no-cache cffi --no-binary :all:
|
|
||||||
pip3 install --no-cache charset_normalizer --no-binary :all:
|
|
||||||
pip3 install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython
|
|
||||||
pip3 install -r requirements.txt
|
|
||||||
- name: Build with PyInstaller
|
|
||||||
run: |
|
|
||||||
pyinstaller BTClockOTA-universal.spec
|
|
||||||
- name: Zip the app bundle
|
|
||||||
run: |
|
|
||||||
cd dist
|
|
||||||
zip -r BTClockOTA-macos-universal2.zip BTClockOTA.app
|
|
||||||
- name: Create DMG
|
|
||||||
run: |
|
|
||||||
mkdir dmg_temp
|
|
||||||
cp -R dist/BTClockOTA.app dmg_temp/
|
|
||||||
# Create the DMG file
|
|
||||||
hdiutil create -volname "BTClockOTA" -srcfolder dmg_temp -ov -format UDZO "dist/BTClockOTA-universal.dmg"
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos-artifacts
|
|
||||||
path: |
|
|
||||||
dist/*
|
|
||||||
!dist/BTClockOTA.app
|
|
||||||
build-windows:
|
|
||||||
runs-on:
|
|
||||||
- ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
- name: Run Docker Container
|
|
||||||
run: |
|
|
||||||
docker run --rm \
|
|
||||||
--volume "${{ github.workspace }}:/src/" \
|
|
||||||
--env SPECFILE=./BTClockOTA.spec \
|
|
||||||
batonogov/pyinstaller-windows:latest
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-artifacts
|
|
||||||
path: dist/
|
|
||||||
build-linux:
|
|
||||||
runs-on:
|
|
||||||
- ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
- name: Run Docker Container
|
|
||||||
run: |
|
|
||||||
docker run --rm \
|
|
||||||
--volume "${{ github.workspace }}:/src/" \
|
|
||||||
--env SPECFILE=./BTClockOTA.spec \
|
|
||||||
ghcr.io/btclock/pyinstaller-wxpython-linux:latest &&
|
|
||||||
mv dist/BTClockOTA dist/BTClockOTA-linux-amd64
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux-artifacts
|
|
||||||
path: dist/
|
|
||||||
release:
|
|
||||||
needs: [build-macos, build-windows, build-linux]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Get current block
|
|
||||||
id: getBlockHeight
|
|
||||||
run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Get Windows Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-artifacts
|
|
||||||
path: windows
|
|
||||||
- name: Get macOS Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos-artifacts
|
|
||||||
path: macos
|
|
||||||
- name: Get Linux Artifacts
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux-artifacts
|
|
||||||
path: linux
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
commit: main
|
|
||||||
name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
artifacts: "macos/**/*.dmg,macos/**/*.zip,windows/**/*.exe,linux/*"
|
|
||||||
allowUpdates: true
|
|
||||||
makeLatest: true
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: Build Windows artifacts and make release
|
name: Build artifacts and make release
|
||||||
|
|
||||||
on: workflow_dispatch
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
@ -32,23 +32,23 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--volume "${{ github.workspace }}:/src/" \
|
--volume "${{ github.workspace }}:/src/" \
|
||||||
--env SPECFILE=./BTClockOTA-debug.spec \
|
--env SPECFILE=./BTClockOTA.spec \
|
||||||
batonogov/pyinstaller-windows:latest
|
batonogov/pyinstaller-windows:latest
|
||||||
# - name: Get current block
|
- name: Get current block
|
||||||
# id: getBlockHeight
|
id: getBlockHeight
|
||||||
# run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
|
run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-artifacts
|
name: windows-artifacts
|
||||||
path: dist/
|
path: dist/
|
||||||
# - name: Create release
|
- name: Create release
|
||||||
# uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
# with:
|
with:
|
||||||
# tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
|
tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
|
||||||
# commit: main
|
commit: main
|
||||||
# name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
|
name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
|
||||||
# artifacts: 'dist/**'
|
artifacts: 'dist/**'
|
||||||
# allowUpdates: true
|
allowUpdates: true
|
||||||
# removeArtifacts: true
|
removeArtifacts: true
|
||||||
# makeLatest: true
|
makeLatest: true
|
39
.github/workflows/build_linux.yaml
vendored
39
.github/workflows/build_linux.yaml
vendored
|
@ -1,39 +0,0 @@
|
||||||
name: Build Linux artifacts
|
|
||||||
|
|
||||||
on: workflow_dispatch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-linux:
|
|
||||||
runs-on:
|
|
||||||
- ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
- name: Run Docker Container
|
|
||||||
run: |
|
|
||||||
docker run --rm \
|
|
||||||
--volume "${{ github.workspace }}:/src/" \
|
|
||||||
--env SPECFILE=./BTClockOTA.spec \
|
|
||||||
ghcr.io/btclock/pyinstaller-wxpython-linux:latest
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux-artifacts
|
|
||||||
path: dist/
|
|
65
.github/workflows/build_macos.yaml
vendored
65
.github/workflows/build_macos.yaml
vendored
|
@ -1,65 +0,0 @@
|
||||||
name: Build macOS artifacts and make release
|
|
||||||
|
|
||||||
on: workflow_dispatch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download universal2 Python
|
|
||||||
run: |
|
|
||||||
curl -o python.pkg https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg
|
|
||||||
- name: Install Python
|
|
||||||
run: |
|
|
||||||
sudo installer -pkg python.pkg -target /
|
|
||||||
- name: Add Python to PATH
|
|
||||||
run: |
|
|
||||||
echo "/Library/Frameworks/Python.framework/Versions/3.12/bin" >> $GITHUB_PATH
|
|
||||||
- name: Verify Python installation
|
|
||||||
run: |
|
|
||||||
python3 --version
|
|
||||||
pip3 --version
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
pip3 install --upgrade pip
|
|
||||||
pip3 install pyinstaller
|
|
||||||
pip3 install --no-cache cffi --no-binary :all:
|
|
||||||
pip3 install --no-cache charset_normalizer --no-binary :all:
|
|
||||||
pip3 install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython
|
|
||||||
pip3 install -r requirements.txt
|
|
||||||
- name: Build with PyInstaller
|
|
||||||
run: |
|
|
||||||
pyinstaller BTClockOTA-universal.spec
|
|
||||||
# - name: Get current block
|
|
||||||
# id: getBlockHeight
|
|
||||||
# run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Zip the app bundle
|
|
||||||
run: |
|
|
||||||
cd dist
|
|
||||||
zip -r BTClockOTA-macos-universal2.zip BTClockOTA.app
|
|
||||||
- name: Create DMG
|
|
||||||
run: |
|
|
||||||
mkdir dmg_temp
|
|
||||||
cp -R dist/BTClockOTA.app dmg_temp/
|
|
||||||
# Create the DMG file
|
|
||||||
hdiutil create -volname "BTClockOTA" -srcfolder dmg_temp -ov -format UDZO "dist/BTClockOTA-universal.dmg"
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: macos-artifacts
|
|
||||||
path: |
|
|
||||||
dist/*
|
|
||||||
!dist/BTClockOTA.app
|
|
||||||
# - name: Create release
|
|
||||||
# uses: ncipollo/release-action@v1
|
|
||||||
# with:
|
|
||||||
# tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
# commit: main
|
|
||||||
# name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
|
|
||||||
# artifacts: "dist/*.dmg,dist/*.zip"
|
|
||||||
# allowUpdates: true
|
|
||||||
# makeLatest: true
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -266,5 +266,4 @@ $RECYCLE.BIN/
|
||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,windows,linux
|
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,windows,linux
|
||||||
firmware/*.bin
|
firmware/*.bin
|
||||||
firmware/*.json
|
|
|
@ -1,39 +0,0 @@
|
||||||
# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['app.py'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=[],
|
|
||||||
hiddenimports=['zeroconf._utils.ipaddress', 'zeroconf._handlers.answers', 'pyserial', 'wx', 'wx._xml'],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
noarchive=False,
|
|
||||||
optimize=0,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.datas,
|
|
||||||
[],
|
|
||||||
name='BTClockOTA-debug',
|
|
||||||
debug=True,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
runtime_tmpdir=None,
|
|
||||||
console=True,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
icon=['update-icon.ico'],
|
|
||||||
)
|
|
|
@ -1,45 +0,0 @@
|
||||||
# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['app.py'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=[],
|
|
||||||
hiddenimports=['zeroconf._utils.ipaddress',
|
|
||||||
'zeroconf._handlers.answers', 'pyserial', 'wx', 'wx._xml'],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
noarchive=False,
|
|
||||||
optimize=0,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.datas,
|
|
||||||
[],
|
|
||||||
name='BTClockOTA',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
runtime_tmpdir=None,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch="universal2",
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
icon=['update-icon.ico'],
|
|
||||||
)
|
|
||||||
|
|
||||||
app = BUNDLE(exe,
|
|
||||||
name='BTClockOTA.app',
|
|
||||||
icon='update-icon.icns',
|
|
||||||
bundle_identifier=None)
|
|
|
@ -6,7 +6,7 @@ a = Analysis(
|
||||||
pathex=[],
|
pathex=[],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=[],
|
||||||
hiddenimports=['zeroconf._utils.ipaddress', 'zeroconf._handlers.answers', 'pyserial', 'wx', 'wx._xml'],
|
hiddenimports=['zeroconf._utils.ipaddress', 'zeroconf._handlers.answers', 'pyserial', 'wx'],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
hooksconfig={},
|
hooksconfig={},
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
# BTClock OTA Flasher interface
|
# BTClock OTA Flasher interface
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
- Make sure you have Python (tested with Python 3.12)
|
- Make sure you have Python (tested with Python 3.12)
|
||||||
- Run `pip3 install -r requirements.txt`
|
- Run `pip3 install -r requirements.txt`
|
||||||
|
@ -20,7 +17,7 @@ pyinstaller --hidden-import zeroconf._utils.ipaddress --hidden-import zeroconf._
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
````
|
````
|
||||||
pyinstaller.exe BTClockOTA.spec
|
pyinstaller.exe --hidden-import zeroconf._utils.ipaddress --hidden-import zeroconf._handlers.answers --hidden-import pyserial -n BTClockOTA --windowed --onefile app.py
|
||||||
````
|
````
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
11
app.py
11
app.py
|
@ -1,8 +1,7 @@
|
||||||
from app.main import BTClockOTAUpdater
|
|
||||||
import wx
|
import wx
|
||||||
|
|
||||||
if __name__ == "__main__":
|
from app.main import BTClockOTAUpdater
|
||||||
app = wx.App(False)
|
|
||||||
frame = BTClockOTAUpdater(None, 'BTClock OTA updater')
|
app = wx.App(False)
|
||||||
|
frame = BTClockOTAUpdater(None, 'BTClock OTA updater')
|
||||||
app.MainLoop()
|
app.MainLoop()
|
|
@ -102,8 +102,8 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
inv_tries = 0
|
inv_tries = 0
|
||||||
data = ""
|
data = ""
|
||||||
msg = "Sending invitation to %s " % remote_addr
|
msg = "Sending invitation to %s " % remote_addr
|
||||||
logging.info(msg)
|
sys.stderr.write(msg)
|
||||||
|
sys.stderr.flush()
|
||||||
while inv_tries < 10:
|
while inv_tries < 10:
|
||||||
inv_tries += 1
|
inv_tries += 1
|
||||||
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
@ -111,7 +111,8 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
try:
|
try:
|
||||||
sent = sock2.sendto(message.encode(), remote_address) # noqa: F841
|
sent = sock2.sendto(message.encode(), remote_address) # noqa: F841
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
logging.info("failed\n")
|
sys.stderr.write("failed\n")
|
||||||
|
sys.stderr.flush()
|
||||||
sock2.close()
|
sock2.close()
|
||||||
logging.error("Host %s Not Found", remote_addr)
|
logging.error("Host %s Not Found", remote_addr)
|
||||||
return 1
|
return 1
|
||||||
|
@ -120,8 +121,11 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
data = sock2.recv(37).decode()
|
data = sock2.recv(37).decode()
|
||||||
break
|
break
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
# logging.info(".")
|
sys.stderr.write(".")
|
||||||
|
sys.stderr.flush()
|
||||||
sock2.close()
|
sock2.close()
|
||||||
|
sys.stderr.write("\n")
|
||||||
|
sys.stderr.flush()
|
||||||
if inv_tries == 10:
|
if inv_tries == 10:
|
||||||
logging.error("No response from the ESP")
|
logging.error("No response from the ESP")
|
||||||
return 1
|
return 1
|
||||||
|
@ -173,7 +177,8 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
if PROGRESS:
|
if PROGRESS:
|
||||||
progress_handler(0)
|
progress_handler(0)
|
||||||
else:
|
else:
|
||||||
logging.info("Uploading")
|
sys.stderr.write("Uploading")
|
||||||
|
sys.stderr.flush()
|
||||||
offset = 0
|
offset = 0
|
||||||
while True:
|
while True:
|
||||||
chunk = f.read(1024)
|
chunk = f.read(1024)
|
||||||
|
@ -187,6 +192,7 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
res = connection.recv(10)
|
res = connection.recv(10)
|
||||||
last_response_contained_ok = "OK" in res.decode()
|
last_response_contained_ok = "OK" in res.decode()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
sys.stderr.write("\n")
|
||||||
logging.error("Error Uploading: %s", str(e))
|
logging.error("Error Uploading: %s", str(e))
|
||||||
connection.close()
|
connection.close()
|
||||||
return 1
|
return 1
|
||||||
|
@ -196,6 +202,7 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
|
||||||
connection.close()
|
connection.close()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
sys.stderr.write("\n")
|
||||||
logging.info("Waiting for result...")
|
logging.info("Waiting for result...")
|
||||||
count = 0
|
count = 0
|
||||||
while count < 5:
|
while count < 5:
|
||||||
|
|
|
@ -7,16 +7,13 @@ import esptool
|
||||||
import serial
|
import serial
|
||||||
import wx
|
import wx
|
||||||
|
|
||||||
from app.utils import get_app_data_folder
|
|
||||||
|
|
||||||
|
|
||||||
class FwUpdater:
|
class FwUpdater:
|
||||||
update_progress = None
|
update_progress = None
|
||||||
currentlyUpdating = False
|
currentlyUpdating = False
|
||||||
|
|
||||||
def __init__(self, update_progress, event_cb):
|
def __init__(self, update_progress):
|
||||||
self.update_progress = update_progress
|
self.update_progress = update_progress
|
||||||
self.event_cb = event_cb
|
|
||||||
|
|
||||||
def get_serial_ports(self):
|
def get_serial_ports(self):
|
||||||
ports = serial.tools.list_ports.comports()
|
ports = serial.tools.list_ports.comports()
|
||||||
|
@ -70,49 +67,30 @@ class FwUpdater:
|
||||||
def start_firmware_update(self, release_name, address, hw_rev):
|
def start_firmware_update(self, release_name, address, hw_rev):
|
||||||
# self.SetStatusText(f"Starting firmware update")
|
# self.SetStatusText(f"Starting firmware update")
|
||||||
|
|
||||||
hw_rev_to_model = {
|
model_name = "lolin_s3_mini_213epd"
|
||||||
"REV_B_EPD_2_13": "btclock_rev_b_213epd",
|
if (hw_rev == "REV_B_EPD_2_13"):
|
||||||
"REV_V8_EPD_2_13": "btclock_v8_213epd",
|
model_name = "btclock_rev_b_213epd"
|
||||||
"REV_A_EPD_2_9": "lolin_s3_mini_29epd"
|
|
||||||
}
|
|
||||||
|
|
||||||
model_name = hw_rev_to_model.get(hw_rev, "lolin_s3_mini_213epd")
|
local_filename = f"firmware/{
|
||||||
|
|
||||||
|
|
||||||
local_filename = f"{get_app_data_folder()}/{
|
|
||||||
release_name}_{model_name}_firmware.bin"
|
release_name}_{model_name}_firmware.bin"
|
||||||
|
|
||||||
self.updatingName = address
|
self.updatingName = address
|
||||||
self.currentlyUpdating = True
|
self.currentlyUpdating = True
|
||||||
|
|
||||||
if self.event_cb is not None:
|
|
||||||
self.event_cb("Starting Firmware update")
|
|
||||||
|
|
||||||
if os.path.exists(os.path.abspath(local_filename)):
|
if os.path.exists(os.path.abspath(local_filename)):
|
||||||
thread = Thread(target=self.run_fs_update, args=(
|
thread = Thread(target=self.run_fs_update, args=(
|
||||||
address, os.path.abspath(local_filename), FLASH))
|
address, os.path.abspath(local_filename), FLASH))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def start_fs_update(self, release_name, address, hw_rev):
|
def start_fs_update(self, release_name, address):
|
||||||
hw_rev_to_model = {
|
|
||||||
"REV_B_EPD_2_13": "littlefs_8MB",
|
|
||||||
"REV_V8_EPD_2_13": "littlefs_16MB",
|
|
||||||
"REV_A_EPD_2_9": "littlefs_4MB"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Path to the firmware file
|
# Path to the firmware file
|
||||||
local_filename = f"{get_app_data_folder()}/{release_name}_{hw_rev_to_model.get(hw_rev, "littlefs_4MB")}.bin"
|
local_filename = f"firmware/{release_name}_littlefs.bin"
|
||||||
|
|
||||||
self.updatingName = address
|
self.updatingName = address
|
||||||
self.currentlyUpdating = True
|
self.currentlyUpdating = True
|
||||||
|
|
||||||
if self.event_cb is not None:
|
|
||||||
self.event_cb(f"Starting WebUI update {local_filename}")
|
|
||||||
|
|
||||||
if os.path.exists(os.path.abspath(local_filename)):
|
if os.path.exists(os.path.abspath(local_filename)):
|
||||||
thread = Thread(target=self.run_fs_update, args=(
|
thread = Thread(target=self.run_fs_update, args=(
|
||||||
address, os.path.abspath(local_filename), SPIFFS))
|
address, os.path.abspath(local_filename), SPIFFS))
|
||||||
thread.start()
|
thread.start()
|
||||||
else:
|
|
||||||
if self.event_cb is not None:
|
|
||||||
self.event_cb(f"Firmware file not found: {local_filename}")
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class ActionButtonPanel(wx.Panel):
|
||||||
|
|
||||||
self.update_button = wx.Button(self, label="Update Firmware")
|
self.update_button = wx.Button(self, label="Update Firmware")
|
||||||
self.update_button.Bind(wx.EVT_BUTTON, self.on_click_update_firmware)
|
self.update_button.Bind(wx.EVT_BUTTON, self.on_click_update_firmware)
|
||||||
self.update_fs_button = wx.Button(self, label="Update WebUI")
|
self.update_fs_button = wx.Button(self, label="Update Filesystem")
|
||||||
self.update_fs_button.Bind(wx.EVT_BUTTON, self.on_click_update_fs)
|
self.update_fs_button.Bind(wx.EVT_BUTTON, self.on_click_update_fs)
|
||||||
|
|
||||||
self.identify_button = wx.Button(self, label="Identify")
|
self.identify_button = wx.Button(self, label="Identify")
|
||||||
|
@ -80,7 +80,6 @@ class ActionButtonPanel(wx.Panel):
|
||||||
selected_index = self.device_list.GetFirstSelected()
|
selected_index = self.device_list.GetFirstSelected()
|
||||||
if selected_index != -1:
|
if selected_index != -1:
|
||||||
service_name = self.device_list.GetItemText(selected_index, 0)
|
service_name = self.device_list.GetItemText(selected_index, 0)
|
||||||
hw_rev = self.device_list.GetItemText(selected_index, 3)
|
|
||||||
info = self.listener.services.get(service_name)
|
info = self.listener.services.get(service_name)
|
||||||
if self.currentlyUpdating:
|
if self.currentlyUpdating:
|
||||||
wx.MessageBox("Please wait, already updating",
|
wx.MessageBox("Please wait, already updating",
|
||||||
|
@ -90,7 +89,7 @@ class ActionButtonPanel(wx.Panel):
|
||||||
if info:
|
if info:
|
||||||
address = info.parsed_addresses(
|
address = info.parsed_addresses(
|
||||||
)[0] if info.parsed_addresses() else "N/A"
|
)[0] if info.parsed_addresses() else "N/A"
|
||||||
self.parent_frame.fw_updater.start_fs_update(self.parent_frame.releaseChecker.release_name, address, hw_rev)
|
self.parent_frame.fw_updater.start_fs_update(self.parent_frame.releaseChecker.release_name, address)
|
||||||
else:
|
else:
|
||||||
wx.MessageBox(
|
wx.MessageBox(
|
||||||
"No service information available for selected device", "Error", wx.ICON_ERROR)
|
"No service information available for selected device", "Error", wx.ICON_ERROR)
|
||||||
|
|
77
app/main.py
77
app/main.py
|
@ -1,12 +1,9 @@
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
from app.gui.action_button_panel import ActionButtonPanel
|
from app.gui.action_button_panel import ActionButtonPanel
|
||||||
from app.release_checker import ReleaseChecker
|
from app.release_checker import ReleaseChecker
|
||||||
import wx
|
import wx
|
||||||
import wx.richtext as rt
|
|
||||||
|
|
||||||
from zeroconf import ServiceBrowser, Zeroconf
|
from zeroconf import ServiceBrowser, Zeroconf
|
||||||
import os
|
import os
|
||||||
|
@ -16,27 +13,10 @@ from app import espota
|
||||||
from app.api import ApiHandler
|
from app.api import ApiHandler
|
||||||
from app.fw_updater import FwUpdater
|
from app.fw_updater import FwUpdater
|
||||||
from app.gui.devices_panel import DevicesPanel
|
from app.gui.devices_panel import DevicesPanel
|
||||||
from app.utils import get_app_data_folder
|
|
||||||
from app.zeroconf_listener import ZeroconfListener
|
from app.zeroconf_listener import ZeroconfListener
|
||||||
|
|
||||||
from app.espota import FLASH, SPIFFS
|
from app.espota import FLASH, SPIFFS
|
||||||
|
|
||||||
class BTClockOTAApp(wx.App):
|
|
||||||
def OnInit(self):
|
|
||||||
return True
|
|
||||||
class RichTextCtrlHandler(logging.Handler):
|
|
||||||
def __init__(self, ctrl):
|
|
||||||
super().__init__()
|
|
||||||
self.ctrl = ctrl
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
msg = self.format(record)
|
|
||||||
wx.CallAfter(self.append_text, "\n" + msg)
|
|
||||||
|
|
||||||
def append_text(self, text):
|
|
||||||
self.ctrl.AppendText(text)
|
|
||||||
self.ctrl.ShowPosition(self.ctrl.GetLastPosition())
|
|
||||||
|
|
||||||
class SerialPortsComboBox(wx.ComboBox):
|
class SerialPortsComboBox(wx.ComboBox):
|
||||||
def __init__(self, parent, fw_update):
|
def __init__(self, parent, fw_update):
|
||||||
self.fw_update = fw_update
|
self.fw_update = fw_update
|
||||||
|
@ -50,7 +30,6 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
def __init__(self, parent, title):
|
def __init__(self, parent, title):
|
||||||
wx.Frame.__init__(self, parent, title=title, size=(800, 500))
|
wx.Frame.__init__(self, parent, title=title, size=(800, 500))
|
||||||
|
|
||||||
self.SetMinSize((800, 500))
|
self.SetMinSize((800, 500))
|
||||||
self.releaseChecker = ReleaseChecker()
|
self.releaseChecker = ReleaseChecker()
|
||||||
self.zeroconf = Zeroconf()
|
self.zeroconf = Zeroconf()
|
||||||
|
@ -58,23 +37,13 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
self.browser = ServiceBrowser(
|
self.browser = ServiceBrowser(
|
||||||
self.zeroconf, "_http._tcp.local.", self.listener)
|
self.zeroconf, "_http._tcp.local.", self.listener)
|
||||||
self.api_handler = ApiHandler()
|
self.api_handler = ApiHandler()
|
||||||
self.fw_updater = FwUpdater(self.call_progress, self.SetStatusText)
|
self.fw_updater = FwUpdater(self.call_progress)
|
||||||
|
|
||||||
|
|
||||||
panel = wx.Panel(self)
|
panel = wx.Panel(self)
|
||||||
self.log_ctrl = rt.RichTextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2)
|
|
||||||
monospace_font = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
|
|
||||||
self.log_ctrl.SetFont(monospace_font)
|
|
||||||
|
|
||||||
handler = RichTextCtrlHandler(self.log_ctrl)
|
|
||||||
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S'))
|
|
||||||
logging.getLogger().addHandler(handler)
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
self.device_list = DevicesPanel(panel)
|
self.device_list = DevicesPanel(panel)
|
||||||
|
|
||||||
vbox = wx.BoxSizer(wx.VERTICAL)
|
vbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
vbox.Add(self.device_list, proportion=2,
|
vbox.Add(self.device_list, proportion=2,
|
||||||
flag=wx.EXPAND | wx.ALL, border=20)
|
flag=wx.EXPAND | wx.ALL, border=20)
|
||||||
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
@ -82,23 +51,22 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
self.fw_label = wx.StaticText(
|
self.fw_label = wx.StaticText(
|
||||||
panel, label=f"Fetching latest version from GitHub...")
|
panel, label=f"Fetching latest version from GitHub...")
|
||||||
hbox.Add(self.fw_label, 1, wx.EXPAND | wx.ALL, 5)
|
hbox.Add(self.fw_label, 1, wx.EXPAND | wx.ALL, 5)
|
||||||
|
|
||||||
self.actionButtons = ActionButtonPanel(
|
self.actionButtons = ActionButtonPanel(
|
||||||
panel, self)
|
panel, self)
|
||||||
hbox.AddStretchSpacer()
|
hbox.AddStretchSpacer()
|
||||||
|
|
||||||
hbox.Add(self.actionButtons, 2, wx.EXPAND | wx.ALL, 5)
|
hbox.Add(self.actionButtons, 2, wx.EXPAND | wx.ALL, 5)
|
||||||
vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 20)
|
vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 20)
|
||||||
|
|
||||||
self.progress_bar = wx.Gauge(panel, range=100)
|
self.progress_bar = wx.Gauge(panel, range=100)
|
||||||
vbox.Add(self.progress_bar, 0, wx.EXPAND | wx.ALL, 20)
|
vbox.Add(self.progress_bar, 0, wx.EXPAND | wx.ALL, 20)
|
||||||
vbox.Add(self.log_ctrl, 1, flag=wx.EXPAND | wx.ALL, border=20)
|
|
||||||
|
|
||||||
panel.SetSizer(vbox)
|
panel.SetSizer(vbox)
|
||||||
self.setup_ui()
|
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
wx.CallAfter(self.fetch_latest_release_async)
|
wx.CallAfter(self.fetch_latest_release_async)
|
||||||
wx.YieldIfNeeded()
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
self.setup_menubar()
|
self.setup_menubar()
|
||||||
self.status_bar = self.CreateStatusBar(2)
|
self.status_bar = self.CreateStatusBar(2)
|
||||||
|
@ -107,8 +75,6 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
def setup_menubar(self):
|
def setup_menubar(self):
|
||||||
filemenu = wx.Menu()
|
filemenu = wx.Menu()
|
||||||
menuOpenDownloadDir = filemenu.Append(
|
|
||||||
wx.ID_OPEN, "&Open Download Dir", " Open the directory with firmware files and cache")
|
|
||||||
menuAbout = filemenu.Append(
|
menuAbout = filemenu.Append(
|
||||||
wx.ID_ABOUT, "&About", " Information about this program")
|
wx.ID_ABOUT, "&About", " Information about this program")
|
||||||
menuExit = filemenu.Append(
|
menuExit = filemenu.Append(
|
||||||
|
@ -116,9 +82,8 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
menuBar = wx.MenuBar()
|
menuBar = wx.MenuBar()
|
||||||
menuBar.Append(filemenu, "&File")
|
menuBar.Append(filemenu, "&File")
|
||||||
|
|
||||||
self.SetMenuBar(menuBar)
|
self.SetMenuBar(menuBar)
|
||||||
self.Bind(wx.EVT_MENU, self.OnOpenDownloadFolder, menuOpenDownloadDir)
|
|
||||||
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
|
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
|
||||||
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
|
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
|
||||||
|
|
||||||
|
@ -130,19 +95,12 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
info.parsed_addresses()[0])
|
info.parsed_addresses()[0])
|
||||||
|
|
||||||
version = info.properties.get(b"rev").decode()
|
version = info.properties.get(b"rev").decode()
|
||||||
fsHash = "Too old"
|
|
||||||
hwRev = "REV_A_EPD_2_13"
|
|
||||||
|
|
||||||
if 'gitTag' in deviceSettings:
|
if 'gitTag' in deviceSettings:
|
||||||
version = deviceSettings["gitTag"]
|
version = deviceSettings["gitTag"]
|
||||||
|
|
||||||
if 'fsRev' in deviceSettings:
|
|
||||||
fsHash = deviceSettings['fsRev'][:7]
|
|
||||||
|
|
||||||
if (info.properties.get(b"hw_rev") is not None):
|
|
||||||
hwRev = info.properties.get(b"hw_rev").decode()
|
|
||||||
|
|
||||||
fwHash = info.properties.get(b"rev").decode()[:7]
|
fwHash = info.properties.get(b"rev").decode()[:7]
|
||||||
|
fsHash = deviceSettings['fsRev'][:7]
|
||||||
address = info.parsed_addresses()[0]
|
address = info.parsed_addresses()[0]
|
||||||
|
|
||||||
if index == wx.NOT_FOUND:
|
if index == wx.NOT_FOUND:
|
||||||
|
@ -151,19 +109,23 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
self.device_list.SetItem(index, 0, name)
|
self.device_list.SetItem(index, 0, name)
|
||||||
self.device_list.SetItem(index, 1, version)
|
self.device_list.SetItem(index, 1, version)
|
||||||
self.device_list.SetItem(index, 2, fwHash)
|
self.device_list.SetItem(index, 2, fwHash)
|
||||||
self.device_list.SetItem(index, 3, hwRev)
|
if (info.properties.get(b"hw_rev") is not None):
|
||||||
|
self.device_list.SetItem(
|
||||||
|
index, 3, info.properties.get(b"hw_rev").decode())
|
||||||
self.device_list.SetItem(index, 4, address)
|
self.device_list.SetItem(index, 4, address)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.device_list.SetItem(index, 0, name)
|
self.device_list.SetItem(index, 0, name)
|
||||||
self.device_list.SetItem(index, 1, version)
|
self.device_list.SetItem(index, 1, version)
|
||||||
self.device_list.SetItem(index, 2, fwHash)
|
self.device_list.SetItem(index, 2, fwHash)
|
||||||
self.device_list.SetItem(index, 3, hwRev)
|
if (info.properties.get(b"hw_rev").decode()):
|
||||||
|
self.device_list.SetItem(
|
||||||
|
index, 3, info.properties.get(b"hw_rev").decode())
|
||||||
self.device_list.SetItem(index, 4, address)
|
self.device_list.SetItem(index, 4, address)
|
||||||
self.device_list.SetItem(index, 5, fsHash)
|
self.device_list.SetItem(index, 5, fsHash)
|
||||||
self.device_list.SetItemData(index, index)
|
self.device_list.SetItemData(index, index)
|
||||||
self.device_list.itemDataMap[index] = [
|
self.device_list.itemDataMap[index] = [
|
||||||
name, version, fwHash, hwRev, address, fsHash]
|
name, version, fwHash, info.properties.get(b"hw_rev").decode(), address, fsHash]
|
||||||
for col in range(0, len(self.device_list.column_headings)):
|
for col in range(0, len(self.device_list.column_headings)):
|
||||||
self.device_list.SetColumnWidth(
|
self.device_list.SetColumnWidth(
|
||||||
col, wx.LIST_AUTOSIZE_USEHEADER)
|
col, wx.LIST_AUTOSIZE_USEHEADER)
|
||||||
|
@ -183,9 +145,6 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
def fetch_latest_release_async(self):
|
def fetch_latest_release_async(self):
|
||||||
# Start a new thread to execute fetch_latest_release
|
# Start a new thread to execute fetch_latest_release
|
||||||
app_folder = get_app_data_folder()
|
|
||||||
if not os.path.exists(app_folder):
|
|
||||||
os.makedirs(app_folder)
|
|
||||||
executor = concurrent.futures.ThreadPoolExecutor()
|
executor = concurrent.futures.ThreadPoolExecutor()
|
||||||
future = executor.submit(self.releaseChecker.fetch_latest_release)
|
future = executor.submit(self.releaseChecker.fetch_latest_release)
|
||||||
future.add_done_callback(self.handle_latest_release)
|
future.add_done_callback(self.handle_latest_release)
|
||||||
|
@ -197,11 +156,7 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
latest_release}\nCommit: {self.releaseChecker.commit_hash}")
|
latest_release}\nCommit: {self.releaseChecker.commit_hash}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fw_label.SetLabel(f"Error occurred: {str(e)}")
|
self.fw_label.SetLabel(f"Error occurred: {str(e)}")
|
||||||
traceback.print_tb(e.__traceback__)
|
|
||||||
|
|
||||||
def OnOpenDownloadFolder(self, e):
|
|
||||||
wx.LaunchDefaultBrowser(get_app_data_folder())
|
|
||||||
|
|
||||||
def OnAbout(self, e):
|
def OnAbout(self, e):
|
||||||
dlg = wx.MessageDialog(
|
dlg = wx.MessageDialog(
|
||||||
self, "An updater for BTClocks", "About BTClock OTA Updater", wx.OK)
|
self, "An updater for BTClocks", "About BTClock OTA Updater", wx.OK)
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import wx
|
import wx
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from app.utils import get_app_data_folder, keep_latest_versions
|
from app.utils import keep_latest_versions
|
||||||
|
|
||||||
CACHE_FILE = get_app_data_folder() + '/cache.json'
|
|
||||||
CACHE_DURATION = timedelta(minutes=30)
|
|
||||||
|
|
||||||
LATEST_RELEASE_ENDPOINT = "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/tags"
|
|
||||||
|
|
||||||
class ReleaseChecker:
|
class ReleaseChecker:
|
||||||
'''Release Checker for firmware updates'''
|
'''Release Checker for firmware updates'''
|
||||||
|
@ -21,94 +14,65 @@ class ReleaseChecker:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.progress_callback: Callable[[int], None] = None
|
self.progress_callback: Callable[[int], None] = None
|
||||||
|
|
||||||
def load_cache(self):
|
|
||||||
'''Load cached data from file'''
|
|
||||||
if os.path.exists(CACHE_FILE):
|
|
||||||
with open(CACHE_FILE, 'r') as f:
|
|
||||||
return json.load(f)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def save_cache(self, cache_data):
|
|
||||||
'''Save cache data to file'''
|
|
||||||
with open(CACHE_FILE, 'w') as f:
|
|
||||||
json.dump(cache_data, f)
|
|
||||||
|
|
||||||
def fetch_latest_release(self):
|
def fetch_latest_release(self):
|
||||||
'''Fetch latest firmware release from GitHub'''
|
'''Fetch latest firmware release from GitHub'''
|
||||||
repo = "btclock/btclock_v3"
|
repo = "btclock/btclock_v3"
|
||||||
cache = self.load_cache()
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
|
|
||||||
if 'latest_release' in cache and (now - datetime.fromisoformat(cache['latest_release']['timestamp'])) < CACHE_DURATION:
|
|
||||||
latest_release = cache['latest_release']['data']
|
|
||||||
else:
|
|
||||||
# url = f"https://api.github.com/repos/{repo}/releases/latest"
|
|
||||||
url = f"https://git.btclock.dev/api/v1/repos/{repo}/releases/latest"
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
latest_release = response.json()
|
|
||||||
cache['latest_release'] = {
|
|
||||||
'data': latest_release,
|
|
||||||
'timestamp': now.isoformat()
|
|
||||||
}
|
|
||||||
self.save_cache(cache)
|
|
||||||
except requests.RequestException as e:
|
|
||||||
raise ReleaseCheckerException(
|
|
||||||
f"Error fetching release: {e}") from e
|
|
||||||
|
|
||||||
release_name = latest_release['tag_name']
|
|
||||||
self.release_name = release_name
|
|
||||||
|
|
||||||
|
if not os.path.exists("firmware"):
|
||||||
|
os.makedirs("firmware")
|
||||||
filenames_to_download = ["lolin_s3_mini_213epd_firmware.bin",
|
filenames_to_download = ["lolin_s3_mini_213epd_firmware.bin",
|
||||||
"lolin_s3_mini_29epd_firmware.bin",
|
"btclock_rev_b_213epd_firmware.bin", "littlefs.bin"]
|
||||||
"btclock_v8_213epd_firmware.bin",
|
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
||||||
"btclock_rev_b_213epd_firmware.bin",
|
try:
|
||||||
"littlefs_4MB.bin", "littlefs_8MB.bin", "littlefs_16MB.bin"]
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
latest_release = response.json()
|
||||||
|
release_name = latest_release['tag_name']
|
||||||
|
self.release_name = release_name
|
||||||
|
|
||||||
asset_urls = [asset['browser_download_url']
|
asset_url = None
|
||||||
for asset in latest_release['assets'] if asset['name'] in filenames_to_download]
|
asset_urls = []
|
||||||
|
for asset in latest_release['assets']:
|
||||||
if asset_urls:
|
if asset['name'] in filenames_to_download:
|
||||||
for asset_url in asset_urls:
|
asset_urls.append(asset['browser_download_url'])
|
||||||
self.download_file(asset_url, release_name)
|
if asset_urls:
|
||||||
|
for asset_url in asset_urls:
|
||||||
ref_url = f"https://git.btclock.dev/api/v1/repos/{repo}/tags/{release_name}"
|
self.download_file(asset_url, release_name)
|
||||||
#ref_url = f"https://api.github.com/repos/{
|
ref_url = f"https://api.github.com/repos/{
|
||||||
# repo}/git/ref/tags/{release_name}"
|
repo}/git/ref/tags/{release_name}"
|
||||||
if ref_url in cache and (now - datetime.fromisoformat(cache[ref_url]['timestamp'])) < CACHE_DURATION:
|
|
||||||
commit_hash = cache[ref_url]['data']
|
|
||||||
|
|
||||||
else:
|
|
||||||
response = requests.get(ref_url)
|
response = requests.get(ref_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
ref_info = response.json()
|
ref_info = response.json()
|
||||||
commit_hash = ref_info["commit"]["sha"]
|
if (ref_info["object"]["type"] == "commit"):
|
||||||
|
self.commit_hash = ref_info["object"]["sha"]
|
||||||
cache[ref_url] = {
|
else:
|
||||||
'data': commit_hash,
|
tag_url = f"https://api.github.com/repos/{
|
||||||
'timestamp': now.isoformat()
|
repo}/git/tags/{ref_info["object"]["sha"]}"
|
||||||
}
|
response = requests.get(tag_url)
|
||||||
self.save_cache(cache)
|
response.raise_for_status()
|
||||||
|
tag_info = response.json()
|
||||||
|
self.commit_hash = tag_info["object"]["sha"]
|
||||||
|
|
||||||
self.commit_hash = commit_hash
|
return self.release_name
|
||||||
|
|
||||||
return self.release_name
|
else:
|
||||||
else:
|
raise ReleaseCheckerException(
|
||||||
|
f"File {filenames_to_download} not found in latest release")
|
||||||
|
except requests.RequestException as e:
|
||||||
raise ReleaseCheckerException(
|
raise ReleaseCheckerException(
|
||||||
f"File {filenames_to_download} not found in latest release")
|
f"Error fetching release: {e}") from e
|
||||||
|
|
||||||
def download_file(self, url, release_name):
|
def download_file(self, url, release_name):
|
||||||
'''Downloads Fimware Files'''
|
'''Downloads Fimware Files'''
|
||||||
local_filename = f"{release_name}_{url.split('/')[-1]}"
|
local_filename = f"{release_name}_{url.split('/')[-1]}"
|
||||||
|
|
||||||
if os.path.exists(f"{get_app_data_folder()}/{local_filename}"):
|
|
||||||
return
|
|
||||||
|
|
||||||
response = requests.get(url, stream=True)
|
response = requests.get(url, stream=True)
|
||||||
total_length = response.headers.get('content-length')
|
total_length = response.headers.get('content-length')
|
||||||
keep_latest_versions(get_app_data_folder(), 2)
|
if not os.path.exists("firmware"):
|
||||||
|
os.makedirs("firmware")
|
||||||
|
if os.path.exists(f"firmware/{local_filename}"):
|
||||||
|
return
|
||||||
|
|
||||||
|
keep_latest_versions('firmware', 2)
|
||||||
|
|
||||||
if total_length is None:
|
if total_length is None:
|
||||||
raise ReleaseCheckerException("No content length header")
|
raise ReleaseCheckerException("No content length header")
|
||||||
|
@ -116,7 +80,7 @@ class ReleaseChecker:
|
||||||
total_length = int(total_length)
|
total_length = int(total_length)
|
||||||
chunk_size = 1024
|
chunk_size = 1024
|
||||||
num_chunks = total_length // chunk_size
|
num_chunks = total_length // chunk_size
|
||||||
with open(f"{get_app_data_folder()}/{local_filename}", 'wb') as f:
|
with open(f"firmware/{local_filename}", 'wb') as f:
|
||||||
for i, chunk in enumerate(response.iter_content(chunk_size=chunk_size)):
|
for i, chunk in enumerate(response.iter_content(chunk_size=chunk_size)):
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
15
app/utils.py
15
app/utils.py
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import wx
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def count_versions(folder_path):
|
def count_versions(folder_path):
|
||||||
|
@ -30,16 +30,3 @@ def keep_latest_versions(folder_path, num_versions_to_keep=2):
|
||||||
for version in versions_to_remove:
|
for version in versions_to_remove:
|
||||||
for file_name in version_files[version]:
|
for file_name in version_files[version]:
|
||||||
os.remove(os.path.join(folder_path, file_name))
|
os.remove(os.path.join(folder_path, file_name))
|
||||||
|
|
||||||
def get_app_data_folder():
|
|
||||||
app = wx.GetApp()
|
|
||||||
if app is None:
|
|
||||||
app = wx.App(False)
|
|
||||||
standard_paths = wx.StandardPaths.Get()
|
|
||||||
app_data_dir = standard_paths.GetAppDocumentsDir() + "/BTClockOTA"
|
|
||||||
app.Destroy()
|
|
||||||
return app_data_dir
|
|
||||||
else:
|
|
||||||
standard_paths = wx.StandardPaths.Get()
|
|
||||||
app_data_dir = standard_paths.GetAppDocumentsDir() + "/BTClockOTA"
|
|
||||||
return app_data_dir
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
Loading…
Add table
Reference in a new issue