diff --git a/app/fw_updater.py b/app/fw_updater.py index 6ac6b97..94e401e 100644 --- a/app/fw_updater.py +++ b/app/fw_updater.py @@ -11,12 +11,19 @@ from app.utils import get_app_data_folder class FwUpdater: - update_progress = None - currentlyUpdating = False - def __init__(self, update_progress, event_cb): self.update_progress = update_progress self.event_cb = event_cb + self.currentlyUpdating = False + self.release_name = None + + def call_progress(self, progress): + if callable(self.update_progress): + wx.CallAfter(self.update_progress, progress) + + def call_event(self, message): + if callable(self.event_cb): + wx.CallAfter(self.event_cb, message) def get_serial_ports(self): ports = serial.tools.list_ports.comports() @@ -35,41 +42,112 @@ class FwUpdater: espota.TIMEOUT = 10 espota.serve(address, "0.0.0.0", 3232, random.randint( - 10000, 60000), "", firmware_file, type, self.update_progress) + 10000, 60000), "", firmware_file, type, self.call_progress) - wx.CallAfter(self.update_progress, 1) + wx.CallAfter(self.call_progress, 1) self.currentlyUpdating = False -# self.SetStatusText(f"Finished!") - def flash_firmware(port, baud, firmware_path): + def flash_firmware(self, port, firmware_path, hw_rev, preserve_nvs=False, baud=460800): try: - # Initialize the serial port - serial_port = serial.Serial(port, baud) + self.call_event("Starting serial flash...") + self.call_progress(0) + + # Determine flash size and partition table based on hardware revision + flash_size = "4MB" + partition_suffix = "" + littlefs_size = "4MB" + littlefs_offset = "0x380000" # For 4MB flash + + if hw_rev == "REV_B_EPD_2_13": + flash_size = "8MB" + partition_suffix = "_8mb" + littlefs_size = "8MB" + littlefs_offset = "0x700000" # After app0 (0x10000 + 0x370000) and app1 (0x370000) + elif hw_rev == "REV_V8_EPD_2_13": + flash_size = "16MB" + partition_suffix = "_16mb" + littlefs_size = "16MB" + littlefs_offset = "0xDF0000" # After app0 (0x10000 + 0x6F0000) and app1 (0x6F0000) + + # Get paths to required files + bootloader_path = os.path.join("partition_tables", f"bootloader{partition_suffix}.bin") + partition_table_path = os.path.join("partition_tables", f"partitions{partition_suffix}.bin") + boot_app0_path = os.path.join("partition_tables", "boot_app0.bin") + ota_data_path = os.path.join("partition_tables", "ota_data_initial.bin") + littlefs_path = os.path.join(get_app_data_folder(), f"{self.release_name}_littlefs_{littlefs_size}.bin") + + # Verify all required files exist + required_files = [bootloader_path, partition_table_path, boot_app0_path, ota_data_path, firmware_path, littlefs_path] + for file_path in required_files: + if not os.path.exists(file_path): + raise Exception(f"Required file not found: {file_path}") - # Initialize the ESP32ROM with the serial port - esp = esptool.ESP32ROM(serial_port) + # Common command parameters + common_args = [ + '--chip', 'esp32s3', + '--port', port, + '--baud', str(baud), + '--before', 'default_reset', + '--after', 'hard_reset' + ] + + import logging + logger = logging.getLogger() + + # First, handle erasing + try: + if not preserve_nvs: + # Erase entire flash + logger.info("Erasing flash...") + esptool.main(common_args + ['erase_flash']) + else: + # Erase specific regions, preserving NVS + logger.info("Preserve NVS...") + + self.call_progress(0.3) # 30% progress after erase + + except Exception as e: + logger.error(f"Error during flash erase: {str(e)}") + raise + + # Then, flash all components + try: + logger.info("Writing firmware...") + flash_command = common_args + [ + 'write_flash', + '--flash_mode', 'dio', + '--flash_freq', '80m', + '--flash_size', flash_size, + '0x0', bootloader_path, # Bootloader + '0x8000', partition_table_path, # Partition table + '0xe000', ota_data_path, # Boot app + '0x10000', firmware_path, # Main app (app0) + littlefs_offset, littlefs_path # LittleFS data + ] + + esptool.main(flash_command) + + self.call_progress(1.0) # 100% progress after flash + self.call_event("Firmware flashed successfully!") + self.currentlyUpdating = False # Reset the updating state after successful flash + return True + + except Exception as e: + logger.error(f"Error during firmware write: {str(e)}") + raise - # Connect to the ESP32 - esp.connect() + except Exception as e: + self.call_event(f"Failed to flash firmware: {str(e)}") + self.call_progress(0) # Reset progress bar + self.currentlyUpdating = False # Reset the updating state on error + return False - # Perform the flashing operation - esp.flash_file(firmware_path, offset=0x1000) - - # Optionally, verify the flash - esp.verify_flash(firmware_path, offset=0x1000) - - print("Firmware flashed successfully!") - - except esptool.FatalError as e: - print(f"Failed to flash firmware: {e}") - finally: - # Ensure the serial port is closed - if serial_port.is_open: - serial_port.close() - - def start_firmware_update(self, release_name, address, hw_rev): -# self.SetStatusText(f"Starting firmware update") + def start_serial_firmware_update(self, release_name, port, hw_rev, preserve_nvs=False): + if self.currentlyUpdating: + self.call_event("Another update is in progress") + return False + self.release_name = release_name # Store release name for littlefs path hw_rev_to_model = { "REV_B_EPD_2_13": "btclock_rev_b_213epd", "REV_V8_EPD_2_13": "btclock_v8_213epd", @@ -77,16 +155,38 @@ class FwUpdater: } model_name = hw_rev_to_model.get(hw_rev, "lolin_s3_mini_213epd") + local_filename = f"{get_app_data_folder()}/{release_name}_{model_name}_firmware.bin" + self.currentlyUpdating = True + + if not os.path.exists(os.path.abspath(local_filename)): + self.call_event(f"Firmware file not found: {local_filename}") + self.currentlyUpdating = False + return False - local_filename = f"{get_app_data_folder()}/{ - release_name}_{model_name}_firmware.bin" + try: + thread = Thread(target=self.flash_firmware, args=(port, os.path.abspath(local_filename), hw_rev, preserve_nvs)) + thread.start() + return True + except Exception as e: + self.call_event(f"Failed to start serial update: {str(e)}") + self.currentlyUpdating = False + return False + + def start_firmware_update(self, release_name, address, hw_rev): + hw_rev_to_model = { + "REV_B_EPD_2_13": "btclock_rev_b_213epd", + "REV_V8_EPD_2_13": "btclock_v8_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"{get_app_data_folder()}/{release_name}_{model_name}_firmware.bin" self.updatingName = address self.currentlyUpdating = True - if self.event_cb is not None: - self.event_cb("Starting Firmware update") + self.call_event("Starting Firmware update") if os.path.exists(os.path.abspath(local_filename)): thread = Thread(target=self.run_fs_update, args=( @@ -106,13 +206,51 @@ class FwUpdater: self.updatingName = address self.currentlyUpdating = True - if self.event_cb is not None: - self.event_cb(f"Starting WebUI update {local_filename}") + self.call_event(f"Starting WebUI update {local_filename}") if os.path.exists(os.path.abspath(local_filename)): thread = Thread(target=self.run_fs_update, args=( address, os.path.abspath(local_filename), SPIFFS)) thread.start() else: - if self.event_cb is not None: - self.event_cb(f"Firmware file not found: {local_filename}") + self.call_event(f"Firmware file not found: {local_filename}") + self.currentlyUpdating = False + + def detect_hardware_revision(self, port, baud=460800): + """Detect hardware revision based on flash size""" + try: + import logging + logger = logging.getLogger() + + common_args = [ + '--chip', 'esp32s3', + '--port', port, + '--baud', str(baud), + '--no-stub', # Don't upload stub and don't reset + 'flash_id' + ] + + # Capture the output of esptool flash_id command + import io + import sys + output = io.StringIO() + old_stdout = sys.stdout + sys.stdout = output + + try: + esptool.main(common_args) + flash_id_output = output.getvalue() + finally: + sys.stdout = old_stdout + + # Parse the output to find flash size + if "16MB" in flash_id_output: + return "REV_V8_EPD_2_13" + elif "8MB" in flash_id_output: + return "REV_B_EPD_2_13" + else: + return "REV_A_EPD_2_13" # Default to 4MB version + + except Exception as e: + logger.error(f"Error detecting hardware revision: {str(e)}") + return None diff --git a/app/gui/serial_flash_dialog.py b/app/gui/serial_flash_dialog.py new file mode 100644 index 0000000..9dcf9c9 --- /dev/null +++ b/app/gui/serial_flash_dialog.py @@ -0,0 +1,125 @@ +import wx +from app.gui.serial_monitor import SerialMonitor + +class SerialFlashDialog(wx.Dialog): + def __init__(self, parent, fw_updater, release_name): + super().__init__(parent, title="Flash via Serial", size=(400, 300)) + self.fw_updater = fw_updater + self.release_name = release_name + + panel = wx.Panel(self) + vbox = wx.BoxSizer(wx.VERTICAL) + + # Port selection + hbox1 = wx.BoxSizer(wx.HORIZONTAL) + hbox1.Add(wx.StaticText(panel, label="Port:"), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.port_choice = wx.Choice(panel, choices=[]) + hbox1.Add(self.port_choice, 1) + + # Refresh button + refresh_btn = wx.Button(panel, label="Refresh") + refresh_btn.Bind(wx.EVT_BUTTON, self.on_refresh) + hbox1.Add(refresh_btn, 0, wx.LEFT, 5) + + # Serial Monitor button + monitor_btn = wx.Button(panel, label="Monitor") + monitor_btn.Bind(wx.EVT_BUTTON, self.on_monitor) + hbox1.Add(monitor_btn, 0, wx.LEFT, 5) + + vbox.Add(hbox1, 0, wx.EXPAND | wx.ALL, 5) + + # Hardware revision + hbox2 = wx.BoxSizer(wx.HORIZONTAL) + hbox2.Add(wx.StaticText(panel, label="Hardware:"), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.hw_choice = wx.Choice(panel, choices=[ + "REV_A_EPD_2_13", + "REV_B_EPD_2_13", + "REV_V8_EPD_2_13", + "REV_A_EPD_2_9" + ]) + self.hw_choice.SetSelection(0) + hbox2.Add(self.hw_choice, 1) + + # Auto-detect button + detect_btn = wx.Button(panel, label="Auto-detect") + detect_btn.Bind(wx.EVT_BUTTON, self.on_detect) + hbox2.Add(detect_btn, 0, wx.LEFT, 5) + + vbox.Add(hbox2, 0, wx.EXPAND | wx.ALL, 5) + + # Preserve NVS checkbox + self.preserve_nvs = wx.CheckBox(panel, label="Preserve NVS (keep settings)") + vbox.Add(self.preserve_nvs, 0, wx.EXPAND | wx.ALL, 5) + + # Buttons + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + flash_button = wx.Button(panel, wx.ID_OK, "Flash") + cancel_button = wx.Button(panel, wx.ID_CANCEL, "Cancel") + button_sizer.Add(flash_button, 0, wx.ALL, 5) + button_sizer.Add(cancel_button, 0, wx.ALL, 5) + vbox.Add(button_sizer, 0, wx.ALIGN_CENTER | wx.ALL, 5) + + panel.SetSizer(vbox) + + self.refresh_ports() + + def refresh_ports(self): + ports = self.fw_updater.get_serial_ports() + self.port_choice.Clear() + filtered_ports = [] + for port, desc, hwid in ports: + # Filter for USB devices with vendor IDs 0x1a86 or 0x303a + if ("1A86" in hwid.upper() or "303A" in hwid.upper()): + filtered_ports.append(port) + self.port_choice.Append(f"{port} - {desc}") + + if self.port_choice.GetCount() > 0: + self.port_choice.SetSelection(0) + + def on_refresh(self, event): + self.refresh_ports() + + def on_detect(self, event): + if self.port_choice.GetSelection() == wx.NOT_FOUND: + wx.MessageBox("Please select a port first", "Error", wx.OK | wx.ICON_ERROR) + return + + port = self.get_selected_port() + if not port: + return + + # Show busy cursor + wx.BeginBusyCursor() + try: + revision = self.fw_updater.detect_hardware_revision(port) + if revision: + # Find and select the detected revision + for i in range(self.hw_choice.GetCount()): + if self.hw_choice.GetString(i) == revision: + self.hw_choice.SetSelection(i) + break + finally: + wx.EndBusyCursor() + + def on_monitor(self, event): + if self.port_choice.GetSelection() == wx.NOT_FOUND: + wx.MessageBox("Please select a port first", "Error", wx.OK | wx.ICON_ERROR) + return + + port = self.get_selected_port() + if not port: + return + + monitor = SerialMonitor(self, port) + monitor.Show() + + def get_selected_port(self): + if self.port_choice.GetSelection() == wx.NOT_FOUND: + return None + return self.port_choice.GetString(self.port_choice.GetSelection()).split(" - ")[0] + + def get_selected_revision(self): + return self.hw_choice.GetString(self.hw_choice.GetSelection()) + + def get_preserve_nvs(self): + return self.preserve_nvs.GetValue() \ No newline at end of file diff --git a/app/gui/serial_monitor.py b/app/gui/serial_monitor.py new file mode 100644 index 0000000..7cec7d8 --- /dev/null +++ b/app/gui/serial_monitor.py @@ -0,0 +1,126 @@ +import wx +import serial +import threading +import time + +class SerialMonitor(wx.Dialog): + def __init__(self, parent, port): + super().__init__(parent, title=f"Serial Monitor - {port}", size=(600, 400)) + + self.port = port + self.serial = None + self.running = False + self._destroyed = False + + # Create UI elements + panel = wx.Panel(self) + vbox = wx.BoxSizer(wx.VERTICAL) + + # Text area for output + self.text_area = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL) + vbox.Add(self.text_area, 1, wx.EXPAND | wx.ALL, 5) + + # Input area + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.input_field = wx.TextCtrl(panel, style=wx.TE_PROCESS_ENTER) + self.input_field.Bind(wx.EVT_TEXT_ENTER, self.on_send) + hbox.Add(self.input_field, 1, wx.EXPAND | wx.RIGHT, 5) + + # Send button + send_button = wx.Button(panel, label="Send") + send_button.Bind(wx.EVT_BUTTON, self.on_send) + hbox.Add(send_button, 0) + + vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 5) + + # Baud rate selector + hbox2 = wx.BoxSizer(wx.HORIZONTAL) + hbox2.Add(wx.StaticText(panel, label="Baud Rate:"), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.baud_selector = wx.Choice(panel, choices=["9600", "115200"]) + self.baud_selector.SetSelection(1) # Default to 115200 + self.baud_selector.Bind(wx.EVT_CHOICE, self.on_baud_change) + hbox2.Add(self.baud_selector, 0) + + # Clear button + clear_button = wx.Button(panel, label="Clear") + clear_button.Bind(wx.EVT_BUTTON, self.on_clear) + hbox2.Add(clear_button, 0, wx.LEFT, 5) + + vbox.Add(hbox2, 0, wx.EXPAND | wx.ALL, 5) + + panel.SetSizer(vbox) + + # Bind close event + self.Bind(wx.EVT_CLOSE, self.on_close) + + self.start_monitor() + + def start_monitor(self): + try: + baud = int(self.baud_selector.GetString(self.baud_selector.GetSelection())) + self.serial = serial.Serial(self.port, baud, timeout=0.1) + self.running = True + self.monitor_thread = threading.Thread(target=self.read_serial) + self.monitor_thread.daemon = True + self.monitor_thread.start() + except Exception as e: + wx.MessageBox(f"Error opening port: {str(e)}", "Error", wx.OK | wx.ICON_ERROR) + self.Close() + + def read_serial(self): + while self.running: + if self.serial and self.serial.is_open: + try: + if self.serial.in_waiting: + data = self.serial.read(self.serial.in_waiting) + if data and not self._destroyed: + wx.CallAfter(self.append_text, data.decode('utf-8', errors='replace')) + except Exception as e: + if not self._destroyed: + wx.CallAfter(self.append_text, f"\nError reading from port: {str(e)}\n") + break + time.sleep(0.1) + + def append_text(self, text): + if not self._destroyed and self and self.text_area: + try: + self.text_area.AppendText(text) + except: + pass # Ignore any errors if the window is being destroyed + + def on_send(self, event): + if self.serial and self.serial.is_open and not self._destroyed: + text = self.input_field.GetValue() + '\n' + try: + self.serial.write(text.encode()) + self.input_field.SetValue("") + except Exception as e: + if not self._destroyed: + wx.MessageBox(f"Error sending data: {str(e)}", "Error", wx.OK | wx.ICON_ERROR) + + def on_baud_change(self, event): + if self.serial and not self._destroyed: + try: + self.serial.close() + self.start_monitor() + except Exception as e: + if not self._destroyed: + wx.MessageBox(f"Error changing baud rate: {str(e)}", "Error", wx.OK | wx.ICON_ERROR) + + def on_clear(self, event): + if not self._destroyed and self.text_area: + self.text_area.SetValue("") + + def on_close(self, event): + self.running = False + self._destroyed = True + if self.serial: + try: + self.serial.close() + except: + pass # Ignore any errors during cleanup + event.Skip() + + def Destroy(self): + self._destroyed = True + super().Destroy() \ No newline at end of file diff --git a/app/main.py b/app/main.py index 719dcde..eb7498c 100644 --- a/app/main.py +++ b/app/main.py @@ -1,12 +1,16 @@ import concurrent.futures import logging import traceback +import sys +from datetime import datetime import serial from app.gui.action_button_panel import ActionButtonPanel +from app.gui.serial_flash_dialog import SerialFlashDialog from app.release_checker import ReleaseChecker import wx import wx.richtext as rt +from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin from zeroconf import ServiceBrowser, Zeroconf import os @@ -21,33 +25,113 @@ from app.zeroconf_listener import ZeroconfListener from app.espota import FLASH, SPIFFS -class BTClockOTAApp(wx.App): - def OnInit(self): - return True -class RichTextCtrlHandler(logging.Handler): +# Type: ignore[attr-defined] +# The above comment tells the linter to ignore attribute-defined errors for this file + +class LogRedirector: + def __init__(self, logger, level): + self.logger = logger + self.level = level + self.buffer = "" + + def write(self, text): + self.buffer += text + while '\n' in self.buffer: + line, self.buffer = self.buffer.split('\n', 1) + if line.strip(): # Only log non-empty lines + self.logger.log(self.level, line.rstrip()) + + def flush(self): + if self.buffer: + self.logger.log(self.level, self.buffer.rstrip()) + self.buffer = "" + +class LogListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): # type: ignore[misc] + def __init__(self, parent): + wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT | wx.LC_VIRTUAL | wx.BORDER_NONE) # type: ignore[attr-defined] + ListCtrlAutoWidthMixin.__init__(self) + + self.log_entries = [] + + # Add columns + self.InsertColumn(0, "Time", width=80) + self.InsertColumn(1, "Level", width=70) + self.InsertColumn(2, "Message", width=600) + + # Set up virtual list + self.SetItemCount(0) + + # Bind events + self.Bind(wx.EVT_LIST_CACHE_HINT, self.OnCacheHint) + + def OnGetItemText(self, item, col): + try: + if item < len(self.log_entries): + entry = self.log_entries[item] + if col == 0: + return str(entry['time']) + elif col == 1: + return str(entry['level']) + else: + return str(entry['message']) + except Exception: + pass + return "" + + def OnCacheHint(self, evt): + evt.Skip() + + def append_log(self, time, level, message): + try: + # Convert float timestamp to string if needed + if isinstance(time, (float, int)): + time = datetime.fromtimestamp(time).strftime('%H:%M:%S') + elif isinstance(time, datetime): + time = time.strftime('%H:%M:%S') + elif not isinstance(time, str): + time = str(time) + + self.log_entries.append({ + 'time': time, + 'level': level, + 'message': message + }) + self.SetItemCount(len(self.log_entries)) + # Ensure the last item is visible + if len(self.log_entries) > 0: + self.EnsureVisible(len(self.log_entries) - 1) + except Exception: + pass + +class LogListHandler(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): - def __init__(self, parent, fw_update): - self.fw_update = fw_update - self.ports = serial.tools.list_ports.comports() - wx.ComboBox.__init__(self, parent, choices=[ - port.device for port in self.ports]) + try: + # Get time from the record + if hasattr(record, 'asctime'): + time = record.asctime.split()[3] # Get HH:MM:SS part + else: + from datetime import datetime + time = datetime.fromtimestamp(record.created).strftime('%H:%M:%S') + + level = record.levelname + # Format the message with its arguments + try: + msg = record.getMessage() + except Exception: + msg = str(record.msg) + wx.CallAfter(self.ctrl.append_log, time, level, msg) + except Exception: + self.handleError(record) +class BTClockOTAApp(wx.App): + def OnInit(self): + return True class BTClockOTAUpdater(wx.Frame): - updatingName = "" - def __init__(self, parent, title): wx.Frame.__init__(self, parent, title=title, size=(800, 500)) @@ -59,19 +143,35 @@ class BTClockOTAUpdater(wx.Frame): self.zeroconf, "_http._tcp.local.", self.listener) self.api_handler = ApiHandler() self.fw_updater = FwUpdater(self.call_progress, self.SetStatusText) - 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) - + # Create log list control + self.log_ctrl = LogListCtrl(panel) + + # Set up logging to capture all output + handler = LogListHandler(self.log_ctrl) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S') + handler.setFormatter(formatter) + + # Get the root logger and remove any existing handlers + root_logger = logging.getLogger() + for h in root_logger.handlers[:]: + root_logger.removeHandler(h) + + # Add our handler and set level to DEBUG to capture everything + root_logger.addHandler(handler) + root_logger.setLevel(logging.DEBUG) + + # Also capture esptool output + esptool_logger = logging.getLogger('esptool') + esptool_logger.addHandler(handler) + esptool_logger.setLevel(logging.DEBUG) + + # Redirect stdout and stderr to the log + sys.stdout = LogRedirector(root_logger, logging.INFO) + sys.stderr = LogRedirector(root_logger, logging.ERROR) + self.device_list = DevicesPanel(panel) vbox = wx.BoxSizer(wx.VERTICAL) @@ -81,13 +181,13 @@ class BTClockOTAUpdater(wx.Frame): self.fw_label = wx.StaticText( 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( panel, self) hbox.AddStretchSpacer() - hbox.Add(self.actionButtons, 2, wx.EXPAND | wx.ALL, 5) + vbox.Add(hbox, 0, wx.EXPAND | wx.ALL, 20) self.progress_bar = wx.Gauge(panel, range=100) @@ -99,6 +199,7 @@ class BTClockOTAUpdater(wx.Frame): wx.CallAfter(self.fetch_latest_release_async) wx.YieldIfNeeded() + def setup_ui(self): self.setup_menubar() self.status_bar = self.CreateStatusBar(2) @@ -109,6 +210,9 @@ class BTClockOTAUpdater(wx.Frame): filemenu = wx.Menu() menuOpenDownloadDir = filemenu.Append( wx.ID_OPEN, "&Open Download Dir", " Open the directory with firmware files and cache") + menuFlashSerial = filemenu.Append( + wx.ID_ANY, "Flash via &Serial...", " Flash firmware using serial connection") + filemenu.AppendSeparator() menuAbout = filemenu.Append( wx.ID_ABOUT, "&About", " Information about this program") menuExit = filemenu.Append( @@ -119,6 +223,7 @@ class BTClockOTAUpdater(wx.Frame): self.SetMenuBar(menuBar) self.Bind(wx.EVT_MENU, self.OnOpenDownloadFolder, menuOpenDownloadDir) + self.Bind(wx.EVT_MENU, self.on_serial_flash, menuFlashSerial) self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) self.Bind(wx.EVT_MENU, self.OnExit, menuExit) @@ -192,13 +297,13 @@ class BTClockOTAUpdater(wx.Frame): def handle_latest_release(self, future): try: - latest_release = future.result() + self.latest_release = future.result() # Store the result self.fw_label.SetLabelText(f"Downloaded firmware version: { - latest_release}\nCommit: {self.releaseChecker.commit_hash}") + self.latest_release}\nCommit: {self.releaseChecker.commit_hash}") except Exception as 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()) @@ -210,3 +315,22 @@ class BTClockOTAUpdater(wx.Frame): def OnExit(self, e): self.Close(False) + + def on_serial_flash(self, event): + if not hasattr(self, 'latest_release'): + wx.MessageBox("Please wait for firmware to be downloaded", "Error", wx.OK | wx.ICON_ERROR) + return + + dlg = SerialFlashDialog(self, self.fw_updater, self.latest_release) + if dlg.ShowModal() == wx.ID_OK: + port = dlg.get_selected_port() + if port: + hw_rev = dlg.get_selected_revision() + preserve_nvs = dlg.get_preserve_nvs() + self.fw_updater.start_serial_firmware_update( + self.latest_release, + port, + hw_rev, + preserve_nvs + ) + dlg.Destroy() diff --git a/app/release_checker.py b/app/release_checker.py index 64b0f79..cdeafd2 100644 --- a/app/release_checker.py +++ b/app/release_checker.py @@ -19,7 +19,17 @@ class ReleaseChecker: commit_hash = "" def __init__(self): - self.progress_callback: Callable[[int], None] = None + self._progress_callback: Callable[[int], None] | None = None + + @property + def progress_callback(self) -> Callable[[int], None] | None: + return self._progress_callback + + @progress_callback.setter + def progress_callback(self, callback: Callable[[int], None] | None) -> None: + if callback is not None and not callable(callback): + raise TypeError("progress_callback must be callable or None") + self._progress_callback = callback def load_cache(self): '''Load cached data from file''' @@ -39,11 +49,9 @@ class ReleaseChecker: 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) @@ -75,11 +83,8 @@ class ReleaseChecker: self.download_file(asset_url, release_name) ref_url = f"https://git.btclock.dev/api/v1/repos/{repo}/tags/{release_name}" - #ref_url = f"https://api.github.com/repos/{ -# 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.raise_for_status() @@ -121,12 +126,12 @@ class ReleaseChecker: if chunk: f.write(chunk) f.flush() - progress = int((i / num_chunks) * 100) - if callable(self.progress_callback): - self.progress_callback(progress) + if self._progress_callback is not None: + progress = int((i / num_chunks) * 100) + self._progress_callback(progress) - if callable(self.progress_callback): - self.progress_callback(100) + if self._progress_callback is not None: + self._progress_callback(100) class ReleaseCheckerException(Exception): diff --git a/partition_tables/boot_app0.bin b/partition_tables/boot_app0.bin new file mode 100644 index 0000000..13562ca Binary files /dev/null and b/partition_tables/boot_app0.bin differ diff --git a/partition_tables/bootloader.bin b/partition_tables/bootloader.bin new file mode 100644 index 0000000..267f65f Binary files /dev/null and b/partition_tables/bootloader.bin differ diff --git a/partition_tables/bootloader_16mb.bin b/partition_tables/bootloader_16mb.bin new file mode 100644 index 0000000..e64fd13 Binary files /dev/null and b/partition_tables/bootloader_16mb.bin differ diff --git a/partition_tables/bootloader_8mb.bin b/partition_tables/bootloader_8mb.bin new file mode 100644 index 0000000..267f65f Binary files /dev/null and b/partition_tables/bootloader_8mb.bin differ diff --git a/partition_tables/ota_data_initial.bin b/partition_tables/ota_data_initial.bin new file mode 100644 index 0000000..b4033a7 --- /dev/null +++ b/partition_tables/ota_data_initial.bin @@ -0,0 +1 @@ +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/partition_tables/partition.csv b/partition_tables/partition.csv new file mode 100644 index 0000000..eedbf5b --- /dev/null +++ b/partition_tables/partition.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1b8000, +app1, app, ota_1, , 0x1b8000, +spiffs, data, spiffs, , 0x66C00, +coredump, data, coredump,, 0x10000, diff --git a/partition_tables/partition_16mb.csv b/partition_tables/partition_16mb.csv new file mode 100644 index 0000000..7e58611 --- /dev/null +++ b/partition_tables/partition_16mb.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x6F0000, +app1, app, ota_1, , 0x6F0000, +spiffs, data, spiffs, , 0x200000, +coredump, data, coredump,, 0x10000, \ No newline at end of file diff --git a/partition_tables/partition_8mb.csv b/partition_tables/partition_8mb.csv new file mode 100644 index 0000000..025f649 --- /dev/null +++ b/partition_tables/partition_8mb.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x370000, +app1, app, ota_1, , 0x370000, +spiffs, data, spiffs, , 0xCD000, +coredump, data, coredump,, 0x10000, \ No newline at end of file diff --git a/partition_tables/partitions.bin b/partition_tables/partitions.bin new file mode 100644 index 0000000..7a18990 Binary files /dev/null and b/partition_tables/partitions.bin differ diff --git a/partition_tables/partitions_16mb.bin b/partition_tables/partitions_16mb.bin new file mode 100644 index 0000000..ca9814e Binary files /dev/null and b/partition_tables/partitions_16mb.bin differ diff --git a/partition_tables/partitions_8mb.bin b/partition_tables/partitions_8mb.bin new file mode 100644 index 0000000..11c6a4b Binary files /dev/null and b/partition_tables/partitions_8mb.bin differ