@@ -49,8 +49,12 @@ def main_menu(self) -> None:
4949 # Check update status
5050 update_status , update_color = self .check .update_status ()
5151
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+
5256 print (" [1] Flash an ESP32 Project" )
53- print (" [2] Communicate (WIP) " )
57+ print (f " [2] Communicate { com_status_color } (COM Ports Available: { len ( ports ) } ) \033 [0m " )
5458 print (f" [3] Updates { update_color } ({ update_status } )\033 [0m" )
5559 print (" [4] Help" )
5660 print (" [5] Exit\n " )
@@ -136,14 +140,51 @@ def __update_menu(self) -> None:
136140 except Exception as e :
137141 handler .exception (msg = e )
138142
139- @staticmethod # WIP
140- def __communication_menu () -> None :
143+ def __communication_menu (self ) -> None :
141144 try :
142145 print ()
143146 separator ("ESP32 Ultra Manager - Serial Communication" )
144147 print ()
145148
146- tprint .warning ("Communication feature is currently a WIP." )
149+ # Check available COM ports
150+ ports = list (serial .tools .list_ports .comports ())
151+ if not ports :
152+ tprint .error ("No COM ports found." )
153+ return
154+
155+ tprint .info ("Select a project for communication:" )
156+ tprint .info (" [1] Temporary (Custom Baud Rate)" )
157+ for idx , (item , _ , _ , _ ) in enumerate (self .menu_items ):
158+ tprint .info (f" [{ idx + 2 } ] { item } " )
159+
160+ print ()
161+ choice = tprint .input ("Enter a number to select, or 'exit' to quit: > " ).strip ()
162+ if choice .lower () == 'exit' :
163+ return
164+
165+ try :
166+ choice = int (choice )
167+ if choice == 1 :
168+ baud_rate = self .get .valid_baud_rate ()
169+ if baud_rate == 'exit' :
170+ return
171+ self .__start_communication (None , baud_rate )
172+ elif 2 <= choice <= len (self .menu_items ) + 1 :
173+ project = self .menu_items [choice - 2 ]
174+ folder_name , _ , _ , _ = project
175+ folder_path = os .path .join (self .esp32_folder , folder_name )
176+ config_path = os .path .join (folder_path , 'config.ini' )
177+ self .config .read (config_path )
178+
179+ baud_rate = self .config .get ('Settings' , 'Baud_Rate' , fallback = None )
180+ if not baud_rate or not baud_rate .isdigit ():
181+ tprint .error ("Invalid or missing Baud Rate in config.ini." )
182+ return
183+ self .__start_communication (folder_name , baud_rate )
184+ else :
185+ tprint .warning ("Invalid selection. Returning to menu." )
186+ except ValueError :
187+ tprint .error ("Invalid input. Please enter a valid number." )
147188 except Exception as e :
148189 handler .exception (msg = e )
149190
@@ -161,9 +202,9 @@ def __flasher_menu(self) -> None:
161202 tprint .info ("Select a project to flash:" )
162203 for idx , (item , error , issues , warn ) in enumerate (self .menu_items ):
163204 if error :
164- tprint .warning (f" < { idx + 1 } > { item } " )
205+ tprint .warning (f" [ { idx + 1 } ] { item } " )
165206 else :
166- tprint .info (f" < { idx + 1 } > { item } " )
207+ tprint .info (f" [ { idx + 1 } ] { item } " )
167208
168209 selection = tprint .input ("Enter a number to select, or 'exit' to quit: > " ).strip ()
169210
@@ -270,21 +311,8 @@ def flash(flash_port: str, flash_folder_path: str, flash_bin_files: dict[str, st
270311 self .__flasher_menu ()
271312 return
272313
273- print ()
274- tprint .info ("Available COM ports:" )
275- likely_port = None
276- for idx , port in enumerate (ports ):
277- is_likely = 'esp' in port .description .lower () or 'usb' in port .description .lower ()
278- marker = ' <-- likely ESP32' if is_likely else ''
279- if is_likely and not likely_port :
280- likely_port = port .device
281- tprint .info (f"<{ idx + 1 } > { port .device } - { port .description } { marker } " )
282-
283- choice = tprint .input ("Select a COM port (or press enter to use suggested): > " ).strip ()
284- if choice .lower () == 'exit' :
285- return
314+ selected_port = self .__com_port_menu (ports )
286315
287- selected_port = self .get .selected_com_port (choice , likely_port , ports )
288316 if not selected_port :
289317 tprint .warning ("No COM port selected, returning to menu." )
290318 self .__flasher_menu ()
@@ -432,46 +460,132 @@ def __suggest_fixes(issues: list[str]) -> None:
432460 except Exception as e :
433461 handler .exception (msg = e )
434462
463+ def __com_port_menu (self , ports ):
464+ print ()
465+ tprint .info ("Available COM ports:" )
466+ likely_port = None
467+ for idx , port in enumerate (ports ):
468+ is_likely = 'esp' in port .description .lower () or 'usb' in port .description .lower ()
469+ marker = '\033 [92m <-- likely ESP32\033 [0m' if is_likely else ''
470+ if is_likely and not likely_port :
471+ likely_port = port .device
472+ tprint .info (f"[{ idx + 1 } ] { port .device } - { port .description } { marker } " )
473+
474+ choice = tprint .input ("Select a COM port (or press enter to use suggested): > " ).strip ()
475+ if choice .lower () == 'exit' :
476+ return
477+
478+ return self .get .selected_com_port (choice , likely_port , ports )
479+
480+ def __start_communication (self , project_name : str , baud_rate : str ) -> None :
481+ try :
482+ ports = list (serial .tools .list_ports .comports ())
483+ if not ports :
484+ tprint .error ("No COM ports found." )
485+ return
486+
487+ selected_port = self .__com_port_menu (ports )
488+
489+ if not selected_port :
490+ tprint .warning ("No COM port selected, returning to menu." )
491+ return
492+
493+ tprint .info (f"Connecting to { selected_port } at { baud_rate } baud..." )
494+ try :
495+ def __test_connection (port_test , baud_test ):
496+ try :
497+ with serial .Serial (port_test , int (baud_test ), timeout = 1 ) as ser_test :
498+ 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
501+ return True
502+ except serial .SerialException as err :
503+ tprint .warning (f"Serial Exception at baud rate { baud_test } : { err } " )
504+ except OSError as err :
505+ tprint .warning (f"OSError at baud rate { baud_test } : { err } " )
506+ return False
507+
508+ if not __test_connection (selected_port , baud_rate ):
509+ tprint .warning (f"Failed to connect at { baud_rate } baud." )
510+ choice = tprint .input (
511+ "Do you want to auto-fix and find the correct baud rate? (y/n): > " ).strip ().lower ()
512+ if choice == 'y' :
513+ for rate in Check .esp32_supported_baudrates :
514+ tprint .info (f"Trying baud rate: { rate } " )
515+ if __test_connection (selected_port , rate ):
516+ baud_rate = rate
517+ tprint .success (f"Connection successful at { baud_rate } baud." )
518+ break
519+ else :
520+ tprint .error ("Unable to find a working baud rate. Returning to menu." )
521+ return
522+ else :
523+ tprint .warning ("Auto-fix skipped. Returning to menu." )
524+ return
525+
526+ with serial .Serial (selected_port , int (baud_rate ), timeout = 1 ) as ser :
527+ print ()
528+ separator (f"Session { project_name or 'Temporary' } Started" )
529+ print ("Press Ctrl+C to exit the session.\n " )
530+ while True :
531+ try :
532+ if ser .in_waiting > 0 :
533+ print (ser .read (ser .in_waiting ).decode (errors = 'ignore' ), end = '' , flush = True )
534+ except KeyboardInterrupt :
535+ break
536+ except Exception as e :
537+ handler .exception (msg = e )
538+ break
539+ except Exception as e :
540+ handler .exception (msg = e )
541+ return
542+ finally :
543+ print ()
544+ separator (f"Session { project_name or 'Has' } Ended" )
545+ except Exception as e :
546+ handler .exception (msg = e )
547+
435548
436549class Check :
550+ esp32_supported_baudrates = [
551+ 50 ,
552+ 75 ,
553+ 110 ,
554+ 134 ,
555+ 150 ,
556+ 200 ,
557+ 300 ,
558+ 600 ,
559+ 1200 ,
560+ 1800 ,
561+ 2400 ,
562+ 4800 ,
563+ 9600 , # Default baudrate for many USB-UART bridges
564+ 14400 ,
565+ 19200 ,
566+ 28800 ,
567+ 38400 ,
568+ 57600 ,
569+ 74880 , # Default bootloader baudrate
570+ 115200 , # Most common and default flashing speed
571+ 128000 ,
572+ 230400 ,
573+ 256000 ,
574+ 460800 , # Fast and reliable
575+ 512000 ,
576+ 921600 , # Maximum reliable speed for many USB-UART bridges (CP210x, CH340)
577+ 1000000 ,
578+ 1152000 ,
579+ 1500000 ,
580+ 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
585+ ]
586+
437587 def __init__ (self , config ):
438588 self .config = config
439- self .esp32_supported_baudrates = [
440- 50 ,
441- 75 ,
442- 110 ,
443- 134 ,
444- 150 ,
445- 200 ,
446- 300 ,
447- 600 ,
448- 1200 ,
449- 1800 ,
450- 2400 ,
451- 4800 ,
452- 9600 , # Default baudrate for many USB-UART bridges
453- 14400 ,
454- 19200 ,
455- 28800 ,
456- 38400 ,
457- 57600 ,
458- 74880 , # Default bootloader baudrate
459- 115200 , # Most common and default flashing speed
460- 128000 ,
461- 230400 ,
462- 256000 ,
463- 460800 , # Fast and reliable
464- 512000 ,
465- 921600 , # Maximum reliable speed for many USB-UART bridges (CP210x, CH340)
466- 1000000 ,
467- 1152000 ,
468- 1500000 ,
469- 2000000 , # Often flaky unless high-end bridge + short cable
470- 2500000 ,
471- 3000000 ,
472- 3500000 ,
473- 4000000 # ESP32 supports it, but most bridges can't
474- ]
475589
476590 def project (self , menu_items , folder_name : str , folder_path : str ) -> None :
477591 try :
0 commit comments