Bugfixes and GitHub API cache

This commit is contained in:
Djuri Baars 2024-06-09 23:06:53 +02:00
parent dfcf973176
commit 775ee5e904
6 changed files with 86 additions and 39 deletions

3
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
screenshot-win.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB