11import configparser
2+ import msvcrt
23import os
34import shutil
45import subprocess
@@ -41,43 +42,54 @@ def __load_menu_items(self) -> None:
4142 # ---------------------------- Menu methods ----------------------------- #
4243
4344 def main_menu (self ) -> None :
44- while True :
45- print ()
46- separator ("ESP32 Ultra Manager - Main Menu" )
47- print ()
45+ try :
46+ while True :
47+ print ()
48+ separator ("ESP32 Ultra Manager - Main Menu" )
49+ print ()
4850
49- # Check update status
50- update_status , update_color = self .check .update_status ()
51+ # Check update status
52+ update_status , update_color = self .check .update_status ()
5153
52- # Check COM port availability
53- ports = list (serial .tools .list_ports .comports ())
54- com_status_color = "\033 [91m" if not ports else "\033 [97m" # Red if no ports, white otherwise
55-
56- print (" [1] Flash an ESP32 Project" )
57- print (f" [2] Communicate { com_status_color } (COM Ports Available: { len (ports )} )\033 [0m" )
58- print (f" [3] Updates { update_color } ({ update_status } )\033 [0m" )
59- print (" [4] Help" )
60- print (" [5] Exit\n " )
61- choice = tprint .input ("Select an option: > " ).strip ()
62-
63- if choice == '1' :
64- self .__flasher_menu ()
65- elif choice == '2' :
66- self .__communication_menu ()
67- elif choice == '3' :
68- self .__update_menu ()
69- elif choice == '4' :
70- print ()
71- tprint .info ("ESP32 Ultra Manager - Help" )
72- print ("A tool to flash ESP32 devices from organized folders under ./esp32" )
73- print ("Each folder must contain .bin files and a config.ini" )
74- print ("The tool supports auto-generation of config.ini and update via Git." )
75- elif choice == '5' or choice .lower () == 'exit' :
76- break
77- else :
78- tprint .warning ("Invalid selection. Try again." )
54+ # Check COM port availability
55+ ports = list (serial .tools .list_ports .comports ())
56+ com_status_color = "\033 [91m" if not ports else "\033 [97m" # Red if no ports, white otherwise
57+
58+ print (" [1] Flash an ESP32 Project" )
59+ print (f" [2] Communicate { com_status_color } (COM Ports Available: { len (ports )} )\033 [0m" )
60+ print (f" [3] Updates { update_color } ({ update_status } )\033 [0m" )
61+ print (" [4] Help" )
62+ print (" [5] Exit\n " )
7963
80- def __update_menu (self ) -> None :
64+ while msvcrt .kbhit ():
65+ msvcrt .getch ()
66+
67+ try :
68+ choice = tprint .input ("Select an option: > " ).strip ()
69+ except Exception :
70+ tprint .warning ("If you see this after serial communication automatically, its a known bug as the stdin isn't successfully cleared for some reason." )
71+ choice = tprint .input ("Select an option: > " ).strip ()
72+
73+ if choice == '1' :
74+ self ._flasher_menu ()
75+ elif choice == '2' :
76+ self ._communication_menu ()
77+ elif choice == '3' :
78+ self ._update_menu ()
79+ elif choice == '4' :
80+ print ()
81+ tprint .info ("ESP32 Ultra Manager - Help" )
82+ print ("A tool to flash ESP32 devices from organized folders under ./esp32" )
83+ print ("Each folder must contain .bin files and a config.ini" )
84+ print ("The tool supports auto-generation of config.ini and update via Git." )
85+ elif choice == '5' or choice .lower () == 'exit' :
86+ break
87+ else :
88+ tprint .warning ("Invalid selection. Try again." )
89+ except Exception as e :
90+ handler .exception (msg = e )
91+
92+ def _update_menu (self ) -> None :
8193 print ()
8294 separator ("ESP32 Ultra Manager - Update" )
8395 print ()
@@ -140,7 +152,7 @@ def __update_menu(self) -> None:
140152 except Exception as e :
141153 handler .exception (msg = e )
142154
143- def __communication_menu (self ) -> None :
155+ def _communication_menu (self ) -> None :
144156 try :
145157 print ()
146158 separator ("ESP32 Ultra Manager - Serial Communication" )
@@ -188,7 +200,7 @@ def __communication_menu(self) -> None:
188200 except Exception as e :
189201 handler .exception (msg = e )
190202
191- def __flasher_menu (self ) -> None :
203+ def _flasher_menu (self ) -> None :
192204 try :
193205 print ()
194206 separator ("ESP32 Ultra Manager - Flashing" )
@@ -308,14 +320,14 @@ def flash(flash_port: str, flash_folder_path: str, flash_bin_files: dict[str, st
308320 ports = list (serial .tools .list_ports .comports ())
309321 if not ports :
310322 tprint .error ("No COM ports found." )
311- self .__flasher_menu ()
323+ self ._flasher_menu ()
312324 return
313325
314326 selected_port = self .__com_port_menu (ports )
315327
316328 if not selected_port :
317329 tprint .warning ("No COM port selected, returning to menu." )
318- self .__flasher_menu ()
330+ self ._flasher_menu ()
319331 return
320332
321333 # Flash the ESP32
@@ -343,7 +355,7 @@ def __handle_issues(self, item: tuple[str, str, list[str], list[str]]) -> None:
343355 self .__show_issues (folder_path )
344356
345357 self .check .project (self .menu_items , folder_name , folder_path )
346- self .__flasher_menu ()
358+ self ._flasher_menu ()
347359 else :
348360 self .__flash_esp32 (folder_name )
349361 except Exception as e :
@@ -490,14 +502,16 @@ def __start_communication(self, project_name: str, baud_rate: str) -> None:
490502 tprint .warning ("No COM port selected, returning to menu." )
491503 return
492504
505+ print ()
493506 tprint .info (f"Connecting to { selected_port } at { baud_rate } baud..." )
494507 try :
495508 def __test_connection (port_test , baud_test ):
496509 try :
497510 with serial .Serial (port_test , int (baud_test ), timeout = 1 ) as ser_test :
498511 ser_test .write (b'\x55 ' ) # Send a recognizable byte pattern (e.g., 0x55)
499- response = ser_test .read (1 ) # Read a single byte response
500- if response == b'\x55 ' : # Check if the response matches the sent byte
512+ response = ser_test .read (100 ).decode (errors = 'ignore' )
513+ if "ESP" in response or "rst:" in response :
514+ tprint .debug (f"Received response at baud rate { baud_test } : \n { response } " )
501515 return True
502516 except serial .SerialException as err :
503517 tprint .warning (f"Serial Exception at baud rate { baud_test } : { err } " )
@@ -510,18 +524,22 @@ def __test_connection(port_test, baud_test):
510524 choice = tprint .input (
511525 "Do you want to auto-fix and find the correct baud rate? (y/n): > " ).strip ().lower ()
512526 if choice == 'y' :
513- for rate in Check .esp32_supported_baudrates :
514- tprint .info (f"Trying baud rate: { rate } " )
527+ tprint .info ("Attempting to find a working baud rate..." )
528+ for rate in sorted (Check .esp32_supported_baudrates , reverse = True ):
529+ tprint .debug (f"Trying baud rate: { rate } " )
515530 if __test_connection (selected_port , rate ):
516531 baud_rate = rate
517532 tprint .success (f"Connection successful at { baud_rate } baud." )
533+ self .config .set ('Settings' , 'Baud_Rate' , str (baud_rate ))
534+ with open (os .path .join (self .esp32_folder , project_name , 'config.ini' ), 'w' ) as configfile :
535+ self .config .write (configfile )
536+ tprint .info (f"Baud rate { baud_rate } saved to config.ini." )
518537 break
519538 else :
520- tprint .error ("Unable to find a working baud rate. Returning to menu." )
539+ tprint .warning ("Unable to find a working baud rate. Returning to menu." )
521540 return
522541 else :
523- tprint .warning ("Auto-fix skipped. Returning to menu." )
524- return
542+ tprint .info ("Auto-fix skipped. Connecting..." )
525543
526544 with serial .Serial (selected_port , int (baud_rate ), timeout = 1 ) as ser :
527545 print ()
@@ -532,16 +550,24 @@ def __test_connection(port_test, baud_test):
532550 if ser .in_waiting > 0 :
533551 print (ser .read (ser .in_waiting ).decode (errors = 'ignore' ), end = '' , flush = True )
534552 except KeyboardInterrupt :
553+ if ser .is_open :
554+ tprint .debug ("Closing serial port and resetting input buffer." )
555+ ser .reset_input_buffer () # Clear any existing data in the serial buffer
556+ ser .close ()
535557 break
536558 except Exception as e :
537559 handler .exception (msg = e )
560+ if ser .is_open :
561+ tprint .debug ("Closing serial port and resetting input buffer." )
562+ ser .reset_input_buffer () # Clear any existing data in the serial buffer
563+ ser .close ()
538564 break
539565 except Exception as e :
540566 handler .exception (msg = e )
541567 return
542568 finally :
543569 print ()
544- separator (f"Session { project_name or 'Has ' } Ended" )
570+ separator (f"Session { project_name or 'Temporary ' } Ended" )
545571 except Exception as e :
546572 handler .exception (msg = e )
547573
@@ -578,10 +604,6 @@ class Check:
578604 1152000 ,
579605 1500000 ,
580606 2000000 , # Often flaky unless high-end bridge + short cable
581- 2500000 ,
582- 3000000 ,
583- 3500000 ,
584- 4000000 # ESP32 supports it, but most bridges can't
585607 ]
586608
587609 def __init__ (self , config ):
0 commit comments