Skip to content

Commit 5481c97

Browse files
Refactor ESP32 manager; improve issue handling and user feedback for flashing process
1 parent a1da224 commit 5481c97

File tree

2 files changed

+159
-81
lines changed

2 files changed

+159
-81
lines changed

.idea/workspace.xml

Lines changed: 30 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flasher.py

Lines changed: 129 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from utils import tprint, handler, separator
1212

1313

14-
class ESP32UltraManager:
14+
class ESP32:
1515
def __init__(self):
1616
self.esp32_folder = 'esp32'
1717
self.menu_items = []
@@ -83,10 +83,12 @@ def __update_menu(self) -> None:
8383
if update_status not in ["Up-to-date", "Uncommitted Changes", "Ahead of Main", "Update Available"]:
8484
tprint.warning(f"{update_status}. Please resolve the issues before updating.")
8585
return
86-
if update_status in ["Uncommitted Changes", "Ahead of Main"]:
87-
tprint.warning("You have uncommitted changes or are ahead of the main branch.")
88-
tprint.warning("Please commit or stash your changes before updating.")
86+
if update_status == "Ahead of Main":
87+
tprint.warning("You are ahead of the main branch.")
88+
tprint.warning("Please commit or stash your changes to allow updating.")
8989
return
90+
if update_status == "Uncommitted Changes":
91+
tprint.warning("You have uncommitted changes!! Will still update though.")
9092

9193
# Fetch remote and check for updates
9294
subprocess.run(["git", "fetch"], stdout=subprocess.DEVNULL)
@@ -174,7 +176,7 @@ def __flasher_menu(self) -> None:
174176
tprint.debug(f"Selection of user (index): {selection}")
175177
if 0 <= selection < len(self.menu_items):
176178
self.current_item = self.menu_items[selection]
177-
self.__handle_selection(self.current_item)
179+
self.__handle_issues(self.current_item)
178180
break
179181
else:
180182
tprint.warning("Invalid selection, try again.")
@@ -186,51 +188,7 @@ def __flasher_menu(self) -> None:
186188
except Exception as e:
187189
handler.exception(msg=e)
188190

189-
# Function to handle the flashing and configuration of the selected project #
190-
191-
def __handle_selection(self, item: tuple[str, str, list[str], list[str]]) -> None:
192-
try:
193-
folder_name, error, issues, warn = item
194-
folder_path = os.path.join(self.esp32_folder, folder_name)
195-
196-
print()
197-
tprint.info(f"Project: {folder_name}")
198-
if warn:
199-
for w in warn:
200-
tprint.warning(w)
201-
202-
if error:
203-
tprint.warning("Issues detected:")
204-
for issue in issues:
205-
print(f"\033[91m - {issue}\033[0m")
206-
207-
print()
208-
tprint.info("Options:")
209-
print(" [1] Open folder to fix manually")
210-
print(" [2] Autogenerate config.ini")
211-
print(" [3] Recheck this project")
212-
print(" [4] Return to menu")
213-
choice = tprint.input(" > ").strip()
214-
215-
if choice == '1':
216-
os.system(f'explorer {folder_path}')
217-
tprint.input("Press enter to recheck the project: > ")
218-
self.check.project(self.menu_items, folder_name, folder_path)
219-
elif choice == '2':
220-
if self.__generate_config(folder_path):
221-
self.check.project(self.menu_items, folder_name, folder_path)
222-
else:
223-
tprint.error("Failed to autogenerate config.ini.")
224-
elif choice == '3':
225-
self.check.project(self.menu_items, folder_name, folder_path)
226-
else:
227-
if choice.lower() != 'exit':
228-
tprint.warning("Invalid choice, returning to menu.")
229-
self.__flasher_menu()
230-
else:
231-
self.__flash_esp32(folder_name)
232-
except Exception as e:
233-
handler.exception(msg=e)
191+
# ---------------------------- Menu methods ----------------------------- #
234192

235193
def __flash_esp32(self, folder_name: str) -> None:
236194
"""Flash the ESP32 using the config.ini instructions."""
@@ -337,27 +295,103 @@ def flash(flash_port: str, flash_folder_path: str, flash_bin_files: dict[str, st
337295
except Exception as e:
338296
handler.exception(msg=e)
339297

340-
def __generate_config(self, folder_path: str) -> bool:
298+
def __handle_issues(self, item: tuple[str, str, list[str], list[str]]) -> None:
299+
try:
300+
folder_name, error, issues, warn = item
301+
folder_path = os.path.join(self.esp32_folder, folder_name)
302+
303+
print()
304+
tprint.info(f"Project: {folder_name}")
305+
if warn:
306+
for w in warn:
307+
tprint.warning(w)
308+
309+
if error:
310+
tprint.warning("Issues detected:")
311+
for idx, issue in enumerate(issues, start=1):
312+
print(f"\033[91m [{idx}] {issue}\033[0m")
313+
314+
self.__suggest_fixes(issues)
315+
self.__show_issues(folder_path)
316+
317+
self.check.project(self.menu_items, folder_name, folder_path)
318+
self.__flasher_menu()
319+
else:
320+
self.__flash_esp32(folder_name)
321+
except Exception as e:
322+
handler.exception(msg=e)
323+
324+
def __show_issues(self, folder_path):
325+
def delete_subdirectories(path):
326+
try:
327+
if not os.path.isdir(path):
328+
raise ValueError(f"Provided path '{path}' is not a directory.")
329+
330+
for entry in os.scandir(path):
331+
if entry.is_dir(follow_symlinks=False):
332+
try:
333+
shutil.rmtree(entry.path)
334+
tprint.debug(f"Deleted directory: {entry.path}")
335+
except Exception as e:
336+
handler.exception(msg=f"Failed {entry.path} -> {e}")
337+
tprint.success("Subdirectories removed.")
338+
except Exception as e:
339+
handler.exception(msg=e)
340+
341+
tprint.info("Options:")
342+
print(" [1] Open folder to fix manually")
343+
print(" [2] Autogenerate config.ini (or regenerate)")
344+
print(" [3] Remove subdirectories (if any)")
345+
choice = tprint.input(" > ").strip()
346+
347+
if choice == '1':
348+
os.system(f'explorer {folder_path}')
349+
tprint.input("Press enter to recheck the project: > ")
350+
elif choice == '2':
351+
self.__generate_config(folder_path)
352+
elif choice == '3':
353+
delete_subdirectories(folder_path)
354+
else:
355+
if choice.lower() != 'exit':
356+
tprint.warning("Invalid choice, returning to menu.")
357+
358+
def __generate_config(self, folder_path: str) -> None:
341359
try:
360+
if not os.path.exists(folder_path):
361+
tprint.error(f"'{folder_path}' folder not found.")
362+
if not os.path.isdir(folder_path):
363+
tprint.error(f"'{folder_path}' is not a directory.")
364+
365+
config_path = os.path.join(folder_path, 'config.ini')
366+
if os.path.exists(config_path):
367+
tprint.warning("'config.ini' already exists. Regenerating will overwrite it.")
368+
os.remove(config_path)
369+
342370
bin_files = [f for f in os.listdir(folder_path) if f.endswith('.bin')]
343371
if not bin_files:
344372
tprint.error("No .bin files found in the folder to generate config.ini.")
345-
return False
346373

347374
print()
348375
tprint.info(f"Found BIN files: {', '.join(bin_files)}")
349376

350377
baud = self.get.valid_baud_rate()
351378
if baud == 'exit':
352-
return False
379+
return
353380

354381
config = configparser.ConfigParser()
355382
config['Settings'] = {'Baud_Rate': baud}
356383

384+
used_addresses = set()
357385
for bin_file in bin_files:
358-
address = self.get.valid_address(bin_file)
359-
if address == 'exit':
360-
return False
386+
while True:
387+
address = self.get.valid_address(bin_file)
388+
if address == 'exit':
389+
return
390+
if address in used_addresses:
391+
tprint.warning(f"Address {address} is already in use. Please enter a unique address.")
392+
else:
393+
used_addresses.add(address)
394+
break
361395
config['Settings'][bin_file] = address
362396

363397
config_path = os.path.join(folder_path, 'config.ini')
@@ -366,10 +400,37 @@ def __generate_config(self, folder_path: str) -> bool:
366400

367401
print()
368402
tprint.success("'config.ini' generated successfully!")
369-
return True
370403
except Exception as e:
371404
handler.exception(msg=e)
372-
return False
405+
406+
@staticmethod
407+
def __suggest_fixes(issues: list[str]) -> None:
408+
"""Provide suggestive fixes for detected issues."""
409+
try:
410+
print()
411+
tprint.info("Suggestive Fixes:")
412+
for idx, issue in enumerate(issues, start=1):
413+
if "Missing config.ini" in issue:
414+
print(" Suggestion: Use the 'Autogenerate config.ini' option to create a new config.ini file.")
415+
elif "Invalid memory address" in issue:
416+
print(
417+
" Suggestion: Ensure all memory addresses in config.ini are in hex format (e.g., 0x10000).")
418+
elif "Bin file" in issue and "not referenced" in issue:
419+
print(" Suggestion: Add the missing bin file to the [Settings] section of config.ini.")
420+
elif "Bin file" in issue and "referenced in config.ini but not found" in issue:
421+
print(" Suggestion: Ensure the referenced bin file exists in the project folder.")
422+
elif "Subfolders detected" in issue:
423+
print(" Suggestion: Remove subfolders from the project folder to avoid conflicts.")
424+
elif "Invalid or missing Baud_Rate" in issue:
425+
print(
426+
" Suggestion: Add a valid Baud_Rate (e.g. 115200) to the [Settings] section of config.ini.")
427+
elif "Memory address conflict" in issue:
428+
print(" Suggestion: Ensure all memory addresses in config.ini are unique.")
429+
else:
430+
print(" Suggestion: Review the issue manually and resolve it.")
431+
print()
432+
except Exception as e:
433+
handler.exception(msg=e)
373434

374435

375436
class Check:
@@ -415,10 +476,10 @@ def __init__(self, config):
415476
def project(self, menu_items, folder_name: str, folder_path: str) -> None:
416477
try:
417478
refreshed_issues, _ = self.for_issues(folder_path)
418-
for idx, (name, _, _) in enumerate(menu_items):
479+
for idx, (name, error, issues, warn) in enumerate(menu_items):
419480
if name == folder_name:
420481
error = True if refreshed_issues else False
421-
menu_items[idx] = (folder_name, error, refreshed_issues if error else None)
482+
menu_items[idx] = (folder_name, error, refreshed_issues if error else None, warn)
422483
break
423484
except Exception as e:
424485
handler.exception(msg=e)
@@ -484,6 +545,11 @@ def validate_memory_addresses() -> list[str]:
484545
if ref_file not in bin_files:
485546
issues.append(f"Bin file '{ref_file}' is referenced in config.ini but not found in the folder")
486547

548+
# Check for subfolders inside the project folder
549+
subfolders = [f for f in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))]
550+
if subfolders:
551+
issues.append(f"Subfolders detected in project folder: {', '.join(subfolders)}")
552+
487553
issues += validate_memory_addresses()
488554

489555
baud_rate = self.config.get('Settings', 'Baud_Rate', fallback=None)
@@ -526,7 +592,8 @@ def update_status() -> tuple[str, str]:
526592
return "Unknown Branch", "\033[91m" # Red
527593

528594
# Check GitHub connectivity (Windows only)
529-
ping_result = subprocess.run(["ping", "-n", "1", "github.com"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
595+
ping_result = subprocess.run(["ping", "-n", "1", "github.com"], stdout=subprocess.DEVNULL,
596+
stderr=subprocess.DEVNULL).returncode
530597
if ping_result != 0:
531598
return "Offline", "\033[91m" # Red
532599

@@ -600,7 +667,7 @@ def main():
600667
print()
601668
tprint.warning("The 'esp32' folder did not exist. It has been created. Add your projects and try again.")
602669
exit()
603-
flasher = ESP32UltraManager()
670+
flasher = ESP32()
604671
flasher.main_menu()
605672
except KeyboardInterrupt:
606673
print()

0 commit comments

Comments
 (0)