Improve buttons, use application data dir for cache and downloads
This commit is contained in:
parent
504199a8c9
commit
018b0431df
11
app.py
11
app.py
@ -1,7 +1,8 @@
|
|||||||
|
from app.main import BTClockOTAUpdater
|
||||||
import wx
|
import wx
|
||||||
|
|
||||||
from app.main import BTClockOTAUpdater
|
if __name__ == "__main__":
|
||||||
|
app = wx.App(False)
|
||||||
app = wx.App(False)
|
frame = BTClockOTAUpdater(None, 'BTClock OTA updater')
|
||||||
frame = BTClockOTAUpdater(None, 'BTClock OTA updater')
|
|
||||||
app.MainLoop()
|
app.MainLoop()
|
||||||
|
@ -7,6 +7,8 @@ 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
|
||||||
@ -72,7 +74,7 @@ class FwUpdater:
|
|||||||
if (hw_rev == "REV_B_EPD_2_13"):
|
if (hw_rev == "REV_B_EPD_2_13"):
|
||||||
model_name = "btclock_rev_b_213epd"
|
model_name = "btclock_rev_b_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
|
||||||
@ -88,7 +90,7 @@ class FwUpdater:
|
|||||||
|
|
||||||
def start_fs_update(self, release_name, address):
|
def start_fs_update(self, release_name, address):
|
||||||
# Path to the firmware file
|
# Path to the firmware file
|
||||||
local_filename = f"firmware/{release_name}_littlefs.bin"
|
local_filename = f"{get_app_data_folder()}/{release_name}_littlefs.bin"
|
||||||
|
|
||||||
self.updatingName = address
|
self.updatingName = address
|
||||||
self.currentlyUpdating = True
|
self.currentlyUpdating = True
|
||||||
|
@ -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 Filesystem")
|
self.update_fs_button = wx.Button(self, label="Update WebUI")
|
||||||
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")
|
||||||
|
27
app/main.py
27
app/main.py
@ -1,5 +1,6 @@
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
from app.gui.action_button_panel import ActionButtonPanel
|
from app.gui.action_button_panel import ActionButtonPanel
|
||||||
@ -15,11 +16,14 @@ 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):
|
class RichTextCtrlHandler(logging.Handler):
|
||||||
def __init__(self, ctrl):
|
def __init__(self, ctrl):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -27,7 +31,7 @@ class RichTextCtrlHandler(logging.Handler):
|
|||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
msg = self.format(record)
|
msg = self.format(record)
|
||||||
wx.CallAfter(self.append_text, msg + '\n')
|
wx.CallAfter(self.append_text, "\n" + msg)
|
||||||
|
|
||||||
def append_text(self, text):
|
def append_text(self, text):
|
||||||
self.ctrl.AppendText(text)
|
self.ctrl.AppendText(text)
|
||||||
@ -46,6 +50,7 @@ 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()
|
||||||
@ -62,7 +67,7 @@ class BTClockOTAUpdater(wx.Frame):
|
|||||||
self.log_ctrl.SetFont(monospace_font)
|
self.log_ctrl.SetFont(monospace_font)
|
||||||
|
|
||||||
handler = RichTextCtrlHandler(self.log_ctrl)
|
handler = RichTextCtrlHandler(self.log_ctrl)
|
||||||
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S'))
|
||||||
logging.getLogger().addHandler(handler)
|
logging.getLogger().addHandler(handler)
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
@ -93,7 +98,7 @@ class BTClockOTAUpdater(wx.Frame):
|
|||||||
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)
|
||||||
@ -102,6 +107,8 @@ 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(
|
||||||
@ -109,8 +116,9 @@ 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)
|
||||||
|
|
||||||
@ -175,6 +183,9 @@ 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)
|
||||||
@ -186,7 +197,11 @@ 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)
|
||||||
|
@ -6,9 +6,9 @@ import wx
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from app.utils import keep_latest_versions
|
from app.utils import get_app_data_folder, keep_latest_versions
|
||||||
|
|
||||||
CACHE_FILE = 'firmware/cache.json'
|
CACHE_FILE = get_app_data_folder() + '/cache.json'
|
||||||
CACHE_DURATION = timedelta(minutes=30)
|
CACHE_DURATION = timedelta(minutes=30)
|
||||||
|
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ class ReleaseChecker:
|
|||||||
cache = self.load_cache()
|
cache = self.load_cache()
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
if not os.path.exists("firmware"):
|
|
||||||
os.makedirs("firmware")
|
|
||||||
|
|
||||||
if 'latest_release' in cache and (now - datetime.fromisoformat(cache['latest_release']['timestamp'])) < CACHE_DURATION:
|
if 'latest_release' in cache and (now - datetime.fromisoformat(cache['latest_release']['timestamp'])) < CACHE_DURATION:
|
||||||
latest_release = cache['latest_release']['data']
|
latest_release = cache['latest_release']['data']
|
||||||
@ -106,14 +104,12 @@ class ReleaseChecker:
|
|||||||
'''Downloads Fimware Files'''
|
'''Downloads Fimware Files'''
|
||||||
local_filename = f"{release_name}_{url.split('/')[-1]}"
|
local_filename = f"{release_name}_{url.split('/')[-1]}"
|
||||||
|
|
||||||
if not os.path.exists("firmware"):
|
if os.path.exists(f"{get_app_data_folder()}/{local_filename}"):
|
||||||
os.makedirs("firmware")
|
|
||||||
if os.path.exists(f"firmware/{local_filename}"):
|
|
||||||
return
|
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('firmware', 2)
|
keep_latest_versions(get_app_data_folder(), 2)
|
||||||
|
|
||||||
if total_length is None:
|
if total_length is None:
|
||||||
raise ReleaseCheckerException("No content length header")
|
raise ReleaseCheckerException("No content length header")
|
||||||
@ -121,7 +117,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"firmware/{local_filename}", 'wb') as f:
|
with open(f"{get_app_data_folder()}/{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 shutil
|
import wx
|
||||||
|
|
||||||
|
|
||||||
def count_versions(folder_path):
|
def count_versions(folder_path):
|
||||||
@ -30,3 +30,16 @@ 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
|
Loading…
Reference in New Issue
Block a user