Bugfixes and GitHub API cache
This commit is contained in:
parent
dfcf973176
commit
775ee5e904
6 changed files with 86 additions and 39 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -266,4 +266,5 @@ $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,5 +1,8 @@
|
||||||
# BTClock OTA Flasher interface
|
# BTClock OTA Flasher interface
|
||||||
|
|
||||||
|
![Screenshot Windows](screenshot-win.webp)
|
||||||
|
![Screenshot Mac](screenshot-mac.webp)
|
||||||
|
|
||||||
## 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`
|
||||||
|
@ -17,7 +20,7 @@ pyinstaller --hidden-import zeroconf._utils.ipaddress --hidden-import zeroconf._
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
````
|
````
|
||||||
pyinstaller.exe --hidden-import zeroconf._utils.ipaddress --hidden-import zeroconf._handlers.answers --hidden-import pyserial -n BTClockOTA --windowed --onefile app.py
|
pyinstaller.exe BTClockOTA.spec
|
||||||
````
|
````
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
24
app/main.py
24
app/main.py
|
@ -17,6 +17,7 @@ from app.zeroconf_listener import ZeroconfListener
|
||||||
|
|
||||||
from app.espota import FLASH, SPIFFS
|
from app.espota import FLASH, SPIFFS
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -51,11 +52,11 @@ 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)
|
||||||
|
|
||||||
|
@ -95,12 +96,19 @@ 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:
|
||||||
|
@ -109,23 +117,19 @@ 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)
|
||||||
if (info.properties.get(b"hw_rev") is not None):
|
self.device_list.SetItem(index, 3, hwRev)
|
||||||
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)
|
||||||
if (info.properties.get(b"hw_rev").decode()):
|
self.device_list.SetItem(index, 3, hwRev)
|
||||||
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, info.properties.get(b"hw_rev").decode(), address, fsHash]
|
name, version, fwHash, hwRev, 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)
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
import json
|
||||||
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 keep_latest_versions
|
from app.utils import keep_latest_versions
|
||||||
|
|
||||||
|
CACHE_FILE = 'firmware/cache.json'
|
||||||
|
CACHE_DURATION = timedelta(minutes=30)
|
||||||
|
|
||||||
|
|
||||||
class ReleaseChecker:
|
class ReleaseChecker:
|
||||||
'''Release Checker for firmware updates'''
|
'''Release Checker for firmware updates'''
|
||||||
|
@ -14,53 +19,87 @@ 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 not os.path.exists("firmware"):
|
if not os.path.exists("firmware"):
|
||||||
os.makedirs("firmware")
|
os.makedirs("firmware")
|
||||||
|
|
||||||
|
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"
|
||||||
|
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
|
||||||
|
|
||||||
filenames_to_download = ["lolin_s3_mini_213epd_firmware.bin",
|
filenames_to_download = ["lolin_s3_mini_213epd_firmware.bin",
|
||||||
"btclock_rev_b_213epd_firmware.bin", "littlefs.bin"]
|
"btclock_rev_b_213epd_firmware.bin", "littlefs.bin"]
|
||||||
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
latest_release = response.json()
|
|
||||||
release_name = latest_release['tag_name']
|
|
||||||
self.release_name = release_name
|
|
||||||
|
|
||||||
asset_url = None
|
asset_urls = [asset['browser_download_url']
|
||||||
asset_urls = []
|
for asset in latest_release['assets'] if asset['name'] in filenames_to_download]
|
||||||
for asset in latest_release['assets']:
|
|
||||||
if asset['name'] in filenames_to_download:
|
if asset_urls:
|
||||||
asset_urls.append(asset['browser_download_url'])
|
for asset_url in asset_urls:
|
||||||
if asset_urls:
|
self.download_file(asset_url, release_name)
|
||||||
for asset_url in asset_urls:
|
|
||||||
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()
|
||||||
if (ref_info["object"]["type"] == "commit"):
|
if ref_info["object"]["type"] == "commit":
|
||||||
self.commit_hash = ref_info["object"]["sha"]
|
commit_hash = ref_info["object"]["sha"]
|
||||||
else:
|
else:
|
||||||
tag_url = f"https://api.github.com/repos/{
|
tag_url = f"https://api.github.com/repos/{
|
||||||
repo}/git/tags/{ref_info["object"]["sha"]}"
|
repo}/git/tags/{ref_info['object']['sha']}"
|
||||||
response = requests.get(tag_url)
|
response = requests.get(tag_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
tag_info = response.json()
|
tag_info = response.json()
|
||||||
self.commit_hash = tag_info["object"]["sha"]
|
commit_hash = tag_info["object"]["sha"]
|
||||||
|
cache[ref_url] = {
|
||||||
|
'data': commit_hash,
|
||||||
|
'timestamp': now.isoformat()
|
||||||
|
}
|
||||||
|
self.save_cache(cache)
|
||||||
|
|
||||||
return self.release_name
|
self.commit_hash = commit_hash
|
||||||
|
|
||||||
else:
|
return self.release_name
|
||||||
raise ReleaseCheckerException(
|
else:
|
||||||
f"File {filenames_to_download} not found in latest release")
|
|
||||||
except requests.RequestException as e:
|
|
||||||
raise ReleaseCheckerException(
|
raise ReleaseCheckerException(
|
||||||
f"Error fetching release: {e}") from e
|
f"File {filenames_to_download} not found in latest release")
|
||||||
|
|
||||||
def download_file(self, url, release_name):
|
def download_file(self, url, release_name):
|
||||||
'''Downloads Fimware Files'''
|
'''Downloads Fimware Files'''
|
||||||
|
|
BIN
screenshot-mac.webp
Normal file
BIN
screenshot-mac.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
screenshot-win.webp
Normal file
BIN
screenshot-win.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue