Add flash over serial functionality
This commit is contained in:
parent
c8c69a39b4
commit
63fe5a92b2
16 changed files with 623 additions and 83 deletions
|
@ -11,12 +11,19 @@ from app.utils import get_app_data_folder
|
||||||
|
|
||||||
|
|
||||||
class FwUpdater:
|
class FwUpdater:
|
||||||
update_progress = None
|
|
||||||
currentlyUpdating = False
|
|
||||||
|
|
||||||
def __init__(self, update_progress, event_cb):
|
def __init__(self, update_progress, event_cb):
|
||||||
self.update_progress = update_progress
|
self.update_progress = update_progress
|
||||||
self.event_cb = event_cb
|
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):
|
def get_serial_ports(self):
|
||||||
ports = serial.tools.list_ports.comports()
|
ports = serial.tools.list_ports.comports()
|
||||||
|
@ -35,41 +42,112 @@ class FwUpdater:
|
||||||
espota.TIMEOUT = 10
|
espota.TIMEOUT = 10
|
||||||
|
|
||||||
espota.serve(address, "0.0.0.0", 3232, random.randint(
|
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.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:
|
try:
|
||||||
# Initialize the serial port
|
self.call_event("Starting serial flash...")
|
||||||
serial_port = serial.Serial(port, baud)
|
self.call_progress(0)
|
||||||
|
|
||||||
# Initialize the ESP32ROM with the serial port
|
# Determine flash size and partition table based on hardware revision
|
||||||
esp = esptool.ESP32ROM(serial_port)
|
flash_size = "4MB"
|
||||||
|
partition_suffix = ""
|
||||||
|
littlefs_size = "4MB"
|
||||||
|
littlefs_offset = "0x380000" # For 4MB flash
|
||||||
|
|
||||||
# Connect to the ESP32
|
if hw_rev == "REV_B_EPD_2_13":
|
||||||
esp.connect()
|
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)
|
||||||
|
|
||||||
# Perform the flashing operation
|
# Get paths to required files
|
||||||
esp.flash_file(firmware_path, offset=0x1000)
|
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")
|
||||||
|
|
||||||
# Optionally, verify the flash
|
# Verify all required files exist
|
||||||
esp.verify_flash(firmware_path, offset=0x1000)
|
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}")
|
||||||
|
|
||||||
print("Firmware flashed successfully!")
|
# Common command parameters
|
||||||
|
common_args = [
|
||||||
|
'--chip', 'esp32s3',
|
||||||
|
'--port', port,
|
||||||
|
'--baud', str(baud),
|
||||||
|
'--before', 'default_reset',
|
||||||
|
'--after', 'hard_reset'
|
||||||
|
]
|
||||||
|
|
||||||
except esptool.FatalError as e:
|
import logging
|
||||||
print(f"Failed to flash firmware: {e}")
|
logger = logging.getLogger()
|
||||||
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):
|
# First, handle erasing
|
||||||
# self.SetStatusText(f"Starting firmware update")
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = {
|
hw_rev_to_model = {
|
||||||
"REV_B_EPD_2_13": "btclock_rev_b_213epd",
|
"REV_B_EPD_2_13": "btclock_rev_b_213epd",
|
||||||
"REV_V8_EPD_2_13": "btclock_v8_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")
|
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
|
||||||
|
|
||||||
local_filename = f"{get_app_data_folder()}/{
|
if not os.path.exists(os.path.abspath(local_filename)):
|
||||||
release_name}_{model_name}_firmware.bin"
|
self.call_event(f"Firmware file not found: {local_filename}")
|
||||||
|
self.currentlyUpdating = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
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.updatingName = address
|
||||||
self.currentlyUpdating = True
|
self.currentlyUpdating = True
|
||||||
|
|
||||||
if self.event_cb is not None:
|
self.call_event("Starting Firmware update")
|
||||||
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=(
|
||||||
|
@ -106,13 +206,51 @@ class FwUpdater:
|
||||||
self.updatingName = address
|
self.updatingName = address
|
||||||
self.currentlyUpdating = True
|
self.currentlyUpdating = True
|
||||||
|
|
||||||
if self.event_cb is not None:
|
self.call_event(f"Starting WebUI update {local_filename}")
|
||||||
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:
|
else:
|
||||||
if self.event_cb is not None:
|
self.call_event(f"Firmware file not found: {local_filename}")
|
||||||
self.event_cb(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
|
||||||
|
|
125
app/gui/serial_flash_dialog.py
Normal file
125
app/gui/serial_flash_dialog.py
Normal file
|
@ -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()
|
126
app/gui/serial_monitor.py
Normal file
126
app/gui/serial_monitor.py
Normal file
|
@ -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()
|
182
app/main.py
182
app/main.py
|
@ -1,12 +1,16 @@
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
from app.gui.action_button_panel import ActionButtonPanel
|
from app.gui.action_button_panel import ActionButtonPanel
|
||||||
|
from app.gui.serial_flash_dialog import SerialFlashDialog
|
||||||
from app.release_checker import ReleaseChecker
|
from app.release_checker import ReleaseChecker
|
||||||
import wx
|
import wx
|
||||||
import wx.richtext as rt
|
import wx.richtext as rt
|
||||||
|
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
|
||||||
|
|
||||||
from zeroconf import ServiceBrowser, Zeroconf
|
from zeroconf import ServiceBrowser, Zeroconf
|
||||||
import os
|
import os
|
||||||
|
@ -21,33 +25,113 @@ from app.zeroconf_listener import ZeroconfListener
|
||||||
|
|
||||||
from app.espota import FLASH, SPIFFS
|
from app.espota import FLASH, SPIFFS
|
||||||
|
|
||||||
class BTClockOTAApp(wx.App):
|
# Type: ignore[attr-defined]
|
||||||
def OnInit(self):
|
# The above comment tells the linter to ignore attribute-defined errors for this file
|
||||||
return True
|
|
||||||
class RichTextCtrlHandler(logging.Handler):
|
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):
|
def __init__(self, ctrl):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctrl = ctrl
|
self.ctrl = ctrl
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
msg = self.format(record)
|
try:
|
||||||
wx.CallAfter(self.append_text, "\n" + msg)
|
# 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')
|
||||||
|
|
||||||
def append_text(self, text):
|
level = record.levelname
|
||||||
self.ctrl.AppendText(text)
|
# Format the message with its arguments
|
||||||
self.ctrl.ShowPosition(self.ctrl.GetLastPosition())
|
try:
|
||||||
|
msg = record.getMessage()
|
||||||
class SerialPortsComboBox(wx.ComboBox):
|
except Exception:
|
||||||
def __init__(self, parent, fw_update):
|
msg = str(record.msg)
|
||||||
self.fw_update = fw_update
|
wx.CallAfter(self.ctrl.append_log, time, level, msg)
|
||||||
self.ports = serial.tools.list_ports.comports()
|
except Exception:
|
||||||
wx.ComboBox.__init__(self, parent, choices=[
|
self.handleError(record)
|
||||||
port.device for port in self.ports])
|
|
||||||
|
|
||||||
|
class BTClockOTAApp(wx.App):
|
||||||
|
def OnInit(self):
|
||||||
|
return True
|
||||||
|
|
||||||
class BTClockOTAUpdater(wx.Frame):
|
class BTClockOTAUpdater(wx.Frame):
|
||||||
updatingName = ""
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
@ -60,17 +144,33 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
self.api_handler = ApiHandler()
|
self.api_handler = ApiHandler()
|
||||||
self.fw_updater = FwUpdater(self.call_progress, self.SetStatusText)
|
self.fw_updater = FwUpdater(self.call_progress, self.SetStatusText)
|
||||||
|
|
||||||
|
|
||||||
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)
|
# Create log list control
|
||||||
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S'))
|
self.log_ctrl = LogListCtrl(panel)
|
||||||
logging.getLogger().addHandler(handler)
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
|
# 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)
|
self.device_list = DevicesPanel(panel)
|
||||||
|
|
||||||
|
@ -86,8 +186,8 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
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)
|
||||||
|
@ -99,6 +199,7 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
wx.CallAfter(self.fetch_latest_release_async)
|
wx.CallAfter(self.fetch_latest_release_async)
|
||||||
wx.YieldIfNeeded()
|
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)
|
||||||
|
@ -109,6 +210,9 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
filemenu = wx.Menu()
|
filemenu = wx.Menu()
|
||||||
menuOpenDownloadDir = filemenu.Append(
|
menuOpenDownloadDir = filemenu.Append(
|
||||||
wx.ID_OPEN, "&Open Download Dir", " Open the directory with firmware files and cache")
|
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(
|
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(
|
||||||
|
@ -119,6 +223,7 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
self.SetMenuBar(menuBar)
|
self.SetMenuBar(menuBar)
|
||||||
self.Bind(wx.EVT_MENU, self.OnOpenDownloadFolder, menuOpenDownloadDir)
|
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.OnAbout, menuAbout)
|
||||||
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
|
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
|
||||||
|
|
||||||
|
@ -192,9 +297,9 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
def handle_latest_release(self, future):
|
def handle_latest_release(self, future):
|
||||||
try:
|
try:
|
||||||
latest_release = future.result()
|
self.latest_release = future.result() # Store the result
|
||||||
self.fw_label.SetLabelText(f"Downloaded firmware version: {
|
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:
|
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__)
|
traceback.print_tb(e.__traceback__)
|
||||||
|
@ -210,3 +315,22 @@ class BTClockOTAUpdater(wx.Frame):
|
||||||
|
|
||||||
def OnExit(self, e):
|
def OnExit(self, e):
|
||||||
self.Close(False)
|
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()
|
||||||
|
|
|
@ -19,7 +19,17 @@ class ReleaseChecker:
|
||||||
commit_hash = ""
|
commit_hash = ""
|
||||||
|
|
||||||
def __init__(self):
|
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):
|
def load_cache(self):
|
||||||
'''Load cached data from file'''
|
'''Load cached data from file'''
|
||||||
|
@ -39,11 +49,9 @@ class ReleaseChecker:
|
||||||
cache = self.load_cache()
|
cache = self.load_cache()
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
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']
|
||||||
else:
|
else:
|
||||||
# url = f"https://api.github.com/repos/{repo}/releases/latest"
|
|
||||||
url = f"https://git.btclock.dev/api/v1/repos/{repo}/releases/latest"
|
url = f"https://git.btclock.dev/api/v1/repos/{repo}/releases/latest"
|
||||||
try:
|
try:
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
@ -75,11 +83,8 @@ class ReleaseChecker:
|
||||||
self.download_file(asset_url, release_name)
|
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://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:
|
if ref_url in cache and (now - datetime.fromisoformat(cache[ref_url]['timestamp'])) < CACHE_DURATION:
|
||||||
commit_hash = cache[ref_url]['data']
|
commit_hash = cache[ref_url]['data']
|
||||||
|
|
||||||
else:
|
else:
|
||||||
response = requests.get(ref_url)
|
response = requests.get(ref_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
@ -121,12 +126,12 @@ class ReleaseChecker:
|
||||||
if chunk:
|
if chunk:
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
f.flush()
|
f.flush()
|
||||||
|
if self._progress_callback is not None:
|
||||||
progress = int((i / num_chunks) * 100)
|
progress = int((i / num_chunks) * 100)
|
||||||
if callable(self.progress_callback):
|
self._progress_callback(progress)
|
||||||
self.progress_callback(progress)
|
|
||||||
|
|
||||||
if callable(self.progress_callback):
|
if self._progress_callback is not None:
|
||||||
self.progress_callback(100)
|
self._progress_callback(100)
|
||||||
|
|
||||||
|
|
||||||
class ReleaseCheckerException(Exception):
|
class ReleaseCheckerException(Exception):
|
||||||
|
|
BIN
partition_tables/boot_app0.bin
Normal file
BIN
partition_tables/boot_app0.bin
Normal file
Binary file not shown.
BIN
partition_tables/bootloader.bin
Normal file
BIN
partition_tables/bootloader.bin
Normal file
Binary file not shown.
BIN
partition_tables/bootloader_16mb.bin
Normal file
BIN
partition_tables/bootloader_16mb.bin
Normal file
Binary file not shown.
BIN
partition_tables/bootloader_8mb.bin
Normal file
BIN
partition_tables/bootloader_8mb.bin
Normal file
Binary file not shown.
1
partition_tables/ota_data_initial.bin
Normal file
1
partition_tables/ota_data_initial.bin
Normal file
File diff suppressed because one or more lines are too long
7
partition_tables/partition.csv
Normal file
7
partition_tables/partition.csv
Normal file
|
@ -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,
|
|
7
partition_tables/partition_16mb.csv
Normal file
7
partition_tables/partition_16mb.csv
Normal file
|
@ -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,
|
|
7
partition_tables/partition_8mb.csv
Normal file
7
partition_tables/partition_8mb.csv
Normal file
|
@ -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,
|
|
BIN
partition_tables/partitions.bin
Normal file
BIN
partition_tables/partitions.bin
Normal file
Binary file not shown.
BIN
partition_tables/partitions_16mb.bin
Normal file
BIN
partition_tables/partitions_16mb.bin
Normal file
Binary file not shown.
BIN
partition_tables/partitions_8mb.bin
Normal file
BIN
partition_tables/partitions_8mb.bin
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue