1111from 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
375436class 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