From ae29ba078387b3ae6537f8702cd6742482683c46 Mon Sep 17 00:00:00 2001 From: Qasim Saadat Date: Sun, 2 Nov 2025 20:36:01 -0500 Subject: [PATCH 1/4] wip, added metadata feature --- scripts/canflash/flash_logic.py | 3 +++ scripts/canflash/main.py | 30 ++++++++++++++++++++++++++++++ scripts/canflash/metadata.py | 26 ++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 scripts/canflash/metadata.py diff --git a/scripts/canflash/flash_logic.py b/scripts/canflash/flash_logic.py index 9e8da61b1..0235f748b 100644 --- a/scripts/canflash/flash_logic.py +++ b/scripts/canflash/flash_logic.py @@ -2,6 +2,7 @@ import subprocess from datetime import datetime from constants import ECU_CONFIG, UPLOAD_DIR +from metadata import load_metadata, save_metadata def save_uploaded_file(ecu: str, file_name: str, file_content: bytes) -> str: @@ -12,6 +13,8 @@ def save_uploaded_file(ecu: str, file_name: str, file_content: bytes) -> str: os.makedirs(full_path, exist_ok=True) file_path = os.path.join(full_path, file_name) + save_metadata(file_name, "", timestamp, ecu.lower(), ecu_dir) + with open(file_path, "wb") as f: f.write(file_content) diff --git a/scripts/canflash/main.py b/scripts/canflash/main.py index 723f0ad27..fc0d6fa12 100644 --- a/scripts/canflash/main.py +++ b/scripts/canflash/main.py @@ -1,8 +1,10 @@ from nicegui import ui from nicegui.events import UploadEventArguments from flash_logic import save_uploaded_file, flash_file +from metadata import load_metadata, save_metadata from constants import ECU_CONFIG import os +import json class CanFlashApp: @@ -35,6 +37,21 @@ def handle_flash(self): else: ui.notify(str(exception), type="negative") + def save_notes(self, notes: str, file_name: str): + try: + with open("metadata.json", 'r') as f: + content = f.read().strip() + metadata = json.loads(content) if content else {} + except FileNotFoundError: + metadata = {} + + # Update or add the notes for this file + if file_name not in metadata: + metadata[file_name] = {} + metadata[file_name]["notes"] = notes + + with open("metadata.json", 'w') as f: + json.dump(metadata, f, indent=2) def build_ui(self): ui.colors(primary="#AA1F26") @@ -58,6 +75,19 @@ def build_ui(self): self.flash_button = ui.button("Flash", on_click=self.handle_flash) self.flash_button.disable() + file_dropdown = ui.select( + label='Select file', + options=list(load_metadata().keys()) +) + + self.textbox = ui.input(label='Enter Metadata Notes') + + ui.button('Submit', on_click=lambda: ( + self.save_notes(self.textbox.value, file_dropdown.value), + setattr(self.textbox, 'value', ''))) + + + @ui.page("/") def main_page(): diff --git a/scripts/canflash/metadata.py b/scripts/canflash/metadata.py new file mode 100644 index 000000000..ff0c63809 --- /dev/null +++ b/scripts/canflash/metadata.py @@ -0,0 +1,26 @@ +import os +import subprocess +import json + +METADATA_FILE = "metadata.json" + +def load_metadata(): + if os.path.exists(METADATA_FILE): + with open(METADATA_FILE, "r") as f: + return json.load(f) + return {} + +def save_metadata(file_name: str, notes: str, timestamp: str, ecu: str, upload_name: str): + + metadata = load_metadata() + + metadata[upload_name] = { + "file name": file_name, + "ecu": ecu, + "notes": notes, + "uploaded_at": timestamp, + } + + + with open(METADATA_FILE, "w") as f: + json.dump(metadata, f, indent=2) \ No newline at end of file From 18d1972a5b3389daa8571066716a14df116dad6e Mon Sep 17 00:00:00 2001 From: Qasim Saadat Date: Sat, 8 Nov 2025 02:06:10 -0500 Subject: [PATCH 2/4] added seperate pages for uploading and reusing old bin files --- scripts/canflash/main.py | 125 ++++++++++++++++++++++++++------- scripts/canflash/metadata.json | 2 + scripts/canflash/metadata.py | 3 +- 3 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 scripts/canflash/metadata.json diff --git a/scripts/canflash/main.py b/scripts/canflash/main.py index fc0d6fa12..be275f06f 100644 --- a/scripts/canflash/main.py +++ b/scripts/canflash/main.py @@ -2,11 +2,23 @@ from nicegui.events import UploadEventArguments from flash_logic import save_uploaded_file, flash_file from metadata import load_metadata, save_metadata -from constants import ECU_CONFIG +from constants import ECU_CONFIG, UPLOAD_DIR import os import json +class home_page: + def __init__(self): + self.build_ui() + def build_ui(self): + ui.colors(primary="#AA1F26") + ui.label('Welcome to CAN Flash!') + ui.label('Select to flash a new binary file') + ui.button('Upload a new binary file', on_click=lambda e: ui.navigate.to('/upload_new_bin')) + ui.label('Select to flash an existing binary file or update its notes') + ui.button('Flash an existing binary file', on_click=lambda e: ui.navigate.to('/upload_new_bin')) + +# Upload new binary file and flash class CanFlashApp: def __init__(self): self.starting_ecu = "Select ECU" @@ -37,22 +49,63 @@ def handle_flash(self): else: ui.notify(str(exception), type="negative") + def build_ui(self): + ui.colors(primary="#AA1F26") + ui.markdown("Welcome to CAN Flash! Select an ECU and upload a file to flash. See [docs](https://macformula.github.io/racecar/) for more information.") + + # ECU Selection + self.ecu_select = ui.select([self.starting_ecu] + list(ECU_CONFIG.keys())) + self.ecu_select.bind_value(self, "selected_ecu") + + # File Upload + self.upload_box = ui.upload( + label="Select File", + on_upload=self.handle_upload, + auto_upload=True, + ).props("accept=.bin") + self.upload_box.bind_enabled_from( + self.ecu_select, "value", lambda v: v != self.starting_ecu + ) + + # Flash Button + self.flash_button = ui.button("Flash", on_click=self.handle_flash) + self.flash_button.disable() + + +# Flash an existing binary file or update notes +# Upload new binary file and flash +class CanFlashUploadExisting(CanFlashApp): + def __init__(self): + self.starting_ecu = "Select ECU" + self.selected_ecu = self.starting_ecu + self.bin_file = "Select Binary File" + self.selected_bin_file = self.bin_file + self.uploaded_file_path = None + self.build_ui() + def save_notes(self, notes: str, file_name: str): try: - with open("metadata.json", 'r') as f: - content = f.read().strip() - metadata = json.loads(content) if content else {} + metadata = load_metadata() except FileNotFoundError: metadata = {} # Update or add the notes for this file - if file_name not in metadata: - metadata[file_name] = {} metadata[file_name]["notes"] = notes with open("metadata.json", 'w') as f: json.dump(metadata, f, indent=2) + def on_file_selection(self): + if hasattr(self, "flash_button"): + self.flash_button.enable() + if hasattr(self, "save_notes_button"): + self.save_notes_button.enable() + + path = os.path.join(UPLOAD_DIR, load_metadata().get(self.selected_bin_file, {}).get("upload name", "")) + self.uploaded_file_path = os.path.join(path, load_metadata().get(self.selected_bin_file, {}).get("file name", "")) + + + def build_ui(self): ui.colors(primary="#AA1F26") ui.markdown("Welcome to CAN Flash! Select an ECU and upload a file to flash. See [docs](https://macformula.github.io/racecar/) for more information.") @@ -61,38 +114,62 @@ def build_ui(self): self.ecu_select = ui.select([self.starting_ecu] + list(ECU_CONFIG.keys())) self.ecu_select.bind_value(self, "selected_ecu") - # File Upload - self.upload_box = ui.upload( - label="Select File", - on_upload=self.handle_upload, - auto_upload=True, - ).props("accept=.bin") - self.upload_box.bind_enabled_from( - self.ecu_select, "value", lambda v: v != self.starting_ecu - ) + # Binary File Selection + self.file_dropdown = ui.select([self.selected_bin_file] + list(load_metadata().keys()), + on_change=self.on_file_selection) + self.file_dropdown.bind_value(self, "selected_bin_file") # Flash Button + ui.markdown("") + ui.markdown("Flash Binary File") self.flash_button = ui.button("Flash", on_click=self.handle_flash) self.flash_button.disable() - file_dropdown = ui.select( - label='Select file', - options=list(load_metadata().keys()) -) + # Update Notes + + ui.markdown("") + ui.markdown("Update Notes for previously Uploaded Files") self.textbox = ui.input(label='Enter Metadata Notes') - ui.button('Submit', on_click=lambda: ( + self.save_notes_button = ui.button('Submit', on_click=lambda: ( self.save_notes(self.textbox.value, file_dropdown.value), - setattr(self.textbox, 'value', ''))) - - + setattr(self.textbox, 'value', ''))) + self.save_notes_button.disable() + + # Delete Binary File + + #FIXME + '''ui.markdown("") + ui.markdown("Delete Binary File") + self.delete_button = ui.button("Delete Binary File", on_click=lambda: delete_metadata("uploads")) + self.delete_button.disable()''' @ui.page("/") def main_page(): + home_page() + + +@ui.page("/upload_new_bin") +def upload_new_bin(): CanFlashApp() - + + #Navigation + ui.markdown("") + ui.markdown("Navigation") + ui.button('Home', on_click=lambda e: ui.navigate.to('/')) + ui.button('Flash an existing binary file or update notes', on_click=lambda e: ui.navigate.to('/upload_old_bin')) + +@ui.page("/upload_old_bin") +def reupload_existing_bin(): + CanFlashUploadExisting() + + #Navigation + ui.markdown("") + ui.markdown("Navigation") + ui.button('Home', on_click=lambda e: ui.navigate.to('/')) + ui.button('Upload a new binary file', on_click=lambda e: ui.navigate.to('/upload_new_bin')) if __name__ in {"__main__", "__mp_main__"}: ui.run( diff --git a/scripts/canflash/metadata.json b/scripts/canflash/metadata.json new file mode 100644 index 000000000..7a73a41bf --- /dev/null +++ b/scripts/canflash/metadata.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/scripts/canflash/metadata.py b/scripts/canflash/metadata.py index ff0c63809..1368a1dda 100644 --- a/scripts/canflash/metadata.py +++ b/scripts/canflash/metadata.py @@ -16,6 +16,7 @@ def save_metadata(file_name: str, notes: str, timestamp: str, ecu: str, upload_n metadata[upload_name] = { "file name": file_name, + "upload name": upload_name, "ecu": ecu, "notes": notes, "uploaded_at": timestamp, @@ -23,4 +24,4 @@ def save_metadata(file_name: str, notes: str, timestamp: str, ecu: str, upload_n with open(METADATA_FILE, "w") as f: - json.dump(metadata, f, indent=2) \ No newline at end of file + json.dump(metadata, f, indent=2) From b9ff2b7940d24c4b4c941f34472a4001720e28a9 Mon Sep 17 00:00:00 2001 From: Qasim Saadat Date: Sat, 8 Nov 2025 02:12:04 -0500 Subject: [PATCH 3/4] fixed navigation --- scripts/canflash/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/canflash/main.py b/scripts/canflash/main.py index be275f06f..2d9d81906 100644 --- a/scripts/canflash/main.py +++ b/scripts/canflash/main.py @@ -16,7 +16,7 @@ def build_ui(self): ui.label('Select to flash a new binary file') ui.button('Upload a new binary file', on_click=lambda e: ui.navigate.to('/upload_new_bin')) ui.label('Select to flash an existing binary file or update its notes') - ui.button('Flash an existing binary file', on_click=lambda e: ui.navigate.to('/upload_new_bin')) + ui.button('Flash an existing binary file', on_click=lambda e: ui.navigate.to('/upload_old_bin')) # Upload new binary file and flash class CanFlashApp: From 39d8e5b46ceec77282a1f1ae211beb82f47de190 Mon Sep 17 00:00:00 2001 From: Atul Rao Date: Sat, 15 Nov 2025 20:48:03 -0500 Subject: [PATCH 4/4] Added feature to upload existing bin files --- scripts/canflash/.gitignore | 1 + scripts/canflash/main.py | 53 +++++++++++++++++++++++++++------- scripts/canflash/metadata.json | 2 -- 3 files changed, 43 insertions(+), 13 deletions(-) delete mode 100644 scripts/canflash/metadata.json diff --git a/scripts/canflash/.gitignore b/scripts/canflash/.gitignore index 8fc0d8027..4b8b81a8a 100644 --- a/scripts/canflash/.gitignore +++ b/scripts/canflash/.gitignore @@ -1 +1,2 @@ uploads/ +metadata.json \ No newline at end of file diff --git a/scripts/canflash/main.py b/scripts/canflash/main.py index 2d9d81906..0fcc4431b 100644 --- a/scripts/canflash/main.py +++ b/scripts/canflash/main.py @@ -89,20 +89,52 @@ def save_notes(self, notes: str, file_name: str): except FileNotFoundError: metadata = {} - # Update or add the notes for this file - metadata[file_name]["notes"] = notes - - with open("metadata.json", 'w') as f: - json.dump(metadata, f, indent=2) + if file_name in metadata: + # Update or add the notes for this file + metadata[file_name]["notes"] = notes + + with open("metadata.json", 'w') as f: + json.dump(metadata, f, indent=2) + + ui.notify(f"Notes updated for {file_name}", position="center") + else: + ui.notify(f"File {file_name} not found in metadata", type="negative") def on_file_selection(self): + # Don't do anything if no file is selected + if self.selected_bin_file == self.bin_file: + return + + # Load metadata for the selected file + metadata = load_metadata() + file_metadata = metadata.get(self.selected_bin_file, {}) + + if not file_metadata: + ui.notify("Could not find file metadata", type="negative") + return + + # Construct the full file path + upload_name = file_metadata.get("upload name", "") + file_name = file_metadata.get("file name", "") + self.uploaded_file_path = os.path.join(UPLOAD_DIR, upload_name, file_name) + + # Set the ECU from metadata + stored_ecu = file_metadata.get("ecu", "").lower() + # Match it to the ECU_CONFIG keys (which are title case) + for ecu_key in ECU_CONFIG.keys(): + if ecu_key.lower() == stored_ecu: + self.selected_ecu = ecu_key + break + + # Enable buttons if hasattr(self, "flash_button"): self.flash_button.enable() if hasattr(self, "save_notes_button"): self.save_notes_button.enable() - - path = os.path.join(UPLOAD_DIR, load_metadata().get(self.selected_bin_file, {}).get("upload name", "")) - self.uploaded_file_path = os.path.join(path, load_metadata().get(self.selected_bin_file, {}).get("file name", "")) + + # Load existing notes into textbox + if hasattr(self, "textbox"): + self.textbox.value = file_metadata.get("notes", "") @@ -132,9 +164,8 @@ def build_ui(self): self.textbox = ui.input(label='Enter Metadata Notes') - self.save_notes_button = ui.button('Submit', on_click=lambda: ( - self.save_notes(self.textbox.value, file_dropdown.value), - setattr(self.textbox, 'value', ''))) + self.save_notes_button = ui.button('Submit', on_click=lambda: + self.save_notes(self.textbox.value, self.selected_bin_file)) self.save_notes_button.disable() # Delete Binary File diff --git a/scripts/canflash/metadata.json b/scripts/canflash/metadata.json deleted file mode 100644 index 7a73a41bf..000000000 --- a/scripts/canflash/metadata.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file