55
66from copy import deepcopy
77
8+ from dataclasses import dataclass
89import tkinter as tk
910from tkinter import messagebox , filedialog , ttk
1011import json
5455}
5556
5657
58+ @dataclass
59+ class SerialPort :
60+ """Data class representing the info for a serial port."""
61+
62+ com_port : str | None
63+ """The COM port to connect to."""
64+ baud_rate : int
65+ """The baud rate for the port."""
66+ timeout : int
67+ """The timeout for the port."""
68+ connection : serial .Serial | None
69+ """The serial connection object."""
70+
71+
5772class Menus :
5873 """
5974 Menus class for creating a custom menu bar in a Tkinter application.
@@ -98,40 +113,31 @@ def __init__(
98113 self .menu_bar = tk .Frame (parent , bg = "#333333" )
99114 """The frame containing the menu bar buttons."""
100115
116+ self .serial_port = SerialPort (None , 115200 , 1 , None )
117+ """The serial port configuration."""
118+
101119 # Define menu items and their corresponding dropdown options
102120 menus = {
103- "File" : ["New" , "Open" , "Save" , "Exit" ],
104- "Controllers" : [
105- "Arduino Mega" ,
106- "Arduino Uno" ,
107- "Arduino Micro" ,
108- "Arduino Mini" ,
109- "STM32" ,
110- "NodeMCU ESP8266" ,
111- "NodeMCU ESP32"
121+ "Fichier" : ["Nouveau" , "Ouvrir" , "Enregistrer" , "Quitter" ],
122+ "Microcontrôleur" : ["Choisir un microcontrôleur" , "Table de correspondance" , "Configurer le port série" ],
123+ "Exporter" : [
124+ "Vérifier" ,
125+ "Téléverser" ,
112126 ],
113- "Ports" : ["Configure Ports" ],
114- "Export" : ["Show Correspondence Table" ],
115- "Help" : ["Documentation" , "About" ],
127+ "Aide" : ["Documentation" , "À propos" ],
116128 }
117129
118130 # Mapping menu labels to their handler functions
119131 menu_commands = {
120- "New" : self .new_file ,
121- "Open" : self .open_file ,
122- "Save" : self .save_file ,
123- "Exit" : self .parent .quit ,
124- "Arduino Mega" : lambda : self .select_microcontroller ("Arduino Mega" ),
125- "Arduino Uno" : lambda : self .select_microcontroller ("Arduino Uno" ),
126- "Arduino Micro" : lambda : self .select_microcontroller ("Arduino Micro" ),
127- "Arduino Mini" : lambda : self .select_microcontroller ("Arduino Mini" ),
128- "STM32" : lambda : self .select_microcontroller ("STM32" ),
129- "NodeMCU ESP8266" : lambda : self .select_microcontroller ("NodeMCU ESP8266" ),
130- "NodeMCU ESP32" : lambda : self .select_microcontroller ("NodeMCU ESP32" ),
131- "Configure Ports" : self .configure_ports ,
132- "Show Correspondence Table" : self .show_correspondence_table ,
132+ "Nouveau" : self .new_file ,
133+ "Ouvrir" : self .open_file ,
134+ "Enregistrer" : self .save_file ,
135+ "Quitter" : self .parent .quit ,
136+ "Configurer le port série" : self .configure_ports ,
137+ "Table de correspondance" : self .show_correspondence_table ,
138+ "Choisir un microcontrôleur" : self .select_microcontroller ,
133139 "Documentation" : self .open_documentation ,
134- "About " : self .about ,
140+ "À propos " : self .about ,
135141 }
136142
137143 # Create each menu button and its dropdown
@@ -142,11 +148,33 @@ def __init__(
142148 self .parent .bind ("<Button-1>" , self .close_dropdown , add = "+" )
143149 self .canvas .bind ("<Button-1>" , self .close_dropdown , add = "+" )
144150
145- def select_microcontroller (self , microcontroller_name ):
151+ def select_microcontroller (self ):
146152 """Handler for microcontroller selection."""
147- print (f"{ microcontroller_name } selected." )
148- self .selected_microcontroller = microcontroller_name
149- messagebox .showinfo ("Microcontroller Selected" , f"{ microcontroller_name } has been selected." )
153+ # Create a new top-level window for the dialog
154+ dialog = tk .Toplevel (self .parent )
155+ dialog .title ("Choisir un microcontrôleur" )
156+
157+ # Set the size and position of the dialog
158+ dialog .geometry ("300x150" )
159+
160+ # Create a label for the combobox
161+ label = tk .Label (dialog , text = "Choisir:" )
162+ label .pack (pady = 10 )
163+ available_microcontrollers = list (MICROCONTROLLER_PINS .keys ())
164+ # Create a combobox with the options
165+ combobox = ttk .Combobox (dialog , values = available_microcontrollers )
166+ combobox .pack (pady = 10 )
167+
168+ # Create a button to confirm the selection
169+ def confirm_selection ():
170+ selected_option = combobox .get ()
171+ print (f"Selected option: { selected_option } " )
172+ self .selected_microcontroller = selected_option
173+ print (f"{ selected_option } selected." )
174+ dialog .destroy ()
175+
176+ confirm_button = tk .Button (dialog , text = "Confirm" , command = confirm_selection )
177+ confirm_button .pack (pady = 10 )
150178
151179 def show_correspondence_table (self ):
152180 """Displays the correspondence table between pin_io objects and microcontroller pins in a table format."""
@@ -173,13 +201,15 @@ def show_correspondence_table(self):
173201 if len (input_pin_ios ) > len (input_pins ):
174202 messagebox .showerror (
175203 "Too Many Inputs" ,
176- f"You have { len (input_pin_ios )} input pin_ios but only { len (input_pins )} available input pins on the microcontroller." ,
204+ f"You have { len (input_pin_ios )} input pin_ios but only "
205+ f"{ len (input_pins )} available input pins on the microcontroller." ,
177206 )
178207 return
179208 if len (output_pin_ios ) > len (output_pins ):
180209 messagebox .showerror (
181210 "Too Many Outputs" ,
182- f"You have { len (output_pin_ios )} output pin_ios but only { len (output_pins )} available output pins on the microcontroller." ,
211+ f"You have { len (output_pin_ios )} output pin_ios but only "
212+ f"{ len (output_pins )} available output pins on the microcontroller." ,
183213 )
184214 return
185215
@@ -246,14 +276,19 @@ def create_menu(self, menu_name, options, menu_commands):
246276 btn .pack (side = "left" )
247277
248278 # Create the dropdown frame
249- dropdown = tk .Frame (self .parent , bg = "#333333" , bd = 1 , relief = "solid" , width = 200 )
279+ dropdown = tk .Frame (self .parent , bg = "#333333" , bd = 1 , relief = "solid" , width = 250 )
250280
251281 # Calculate dropdown height based on number of options
252282 button_height = 30 # Approximate height of each dropdown button
253283 dropdown_height = button_height * len (options )
254- dropdown .place (x = 0 , y = 0 , width = 200 , height = dropdown_height ) # Initial size based on options
284+ dropdown .place (x = 0 , y = 0 , width = 250 , height = dropdown_height ) # Initial size based on options
255285 dropdown .place_forget () # Hide initially
256286
287+ def select_menu_item (option ):
288+ """Wrapper function to close dropdown and execute the menu command."""
289+ self .close_dropdown (None )
290+ menu_commands .get (option , lambda : print (f"{ option } selected" ))()
291+
257292 # Populate the dropdown with menu options
258293 for option in options :
259294 option_btn = tk .Button (
@@ -265,11 +300,11 @@ def create_menu(self, menu_name, options, menu_commands):
265300 activeforeground = "white" ,
266301 bd = 0 ,
267302 anchor = "w" ,
268- width = 200 ,
303+ width = 250 ,
269304 padx = 20 ,
270305 pady = 5 ,
271306 font = ("FiraCode-Bold" , 12 ),
272- command = menu_commands . get ( option , lambda opt = option : print ( f" { opt } selected" ) ),
307+ command = lambda o = option : select_menu_item ( o ),
273308 )
274309 option_btn .pack (fill = "both" )
275310
@@ -293,7 +328,7 @@ def toggle_dropdown(self, menu_name):
293328 # Position the dropdown below the button
294329 btn_x = child .winfo_rootx () - self .parent .winfo_rootx ()
295330 btn_y = child .winfo_rooty () - self .parent .winfo_rooty () + child .winfo_height ()
296- child .dropdown .place (x = btn_x , y = btn_y , width = 200 )
331+ child .dropdown .place (x = btn_x , y = btn_y , width = 250 )
297332 print (f"Opened dropdown for { menu_name } " )
298333 child .dropdown .lift () # Ensure dropdown is on top
299334 else :
@@ -323,10 +358,14 @@ def close_dropdown(self, event):
323358 Parameters:
324359 - event (tk.Event): The event object.
325360 """
326- if not self .is_descendant (event .widget , self .menu_bar ) and not any (
327- self .is_descendant (event .widget , child .dropdown )
328- for child in self .menu_bar .winfo_children ()
329- if isinstance (child , tk .Button ) and hasattr (child , "dropdown" )
361+ if (
362+ event is None
363+ or not self .is_descendant (event .widget , self .menu_bar )
364+ and not any (
365+ self .is_descendant (event .widget , child .dropdown )
366+ for child in self .menu_bar .winfo_children ()
367+ if isinstance (child , tk .Button ) and hasattr (child , "dropdown" )
368+ )
330369 ):
331370 for child in self .menu_bar .winfo_children ():
332371 if isinstance (child , tk .Button ) and hasattr (child , "dropdown" ):
@@ -396,7 +435,6 @@ def open_file(self):
396435 ]
397436 self .board .sketcher .circuit (x_o , y_o , model = model_io )
398437 else :
399- # TODO add IO
400438 print (f"Unspecified component: { key } " )
401439 messagebox .showinfo ("Open File" , f"Circuit loaded from { file_path } " )
402440 except Exception as e :
@@ -425,32 +463,21 @@ def save_file(self):
425463 if "label" in comp_data :
426464 comp_data ["label" ] = comp_data ["type" ]
427465 if "wire" in key :
428- comp_data .pop ("XY" , None ) # Remove XY, will be recalculated anyway
466+ comp_data .pop ("XY" , None ) # Remove XY, will be recalculated anyway
429467 # Save the data to a JSON file
430468 with open (file_path , "w" , encoding = "utf-8" ) as file :
431469 json .dump (circuit_data , file , indent = 4 )
432470 print (f"Circuit saved to { file_path } " )
433471 messagebox .showinfo ("Save Successful" , f"Circuit saved to { file_path } " )
434- except Exception as e :
472+ except ( TypeError , KeyError ) as e :
435473 print (f"Error saving file: { e } " )
436474 messagebox .showerror ("Save Error" , f"An error occurred while saving the file:\n { e } " )
437475 else :
438476 print ("Save file cancelled." )
439477
440- def Arduino (self ):
441- """Handler for the 'Arduino' menu item."""
442- print ("Arduino" )
443- messagebox .showinfo ("Arduino" , "Arduino choice functionality not implemented yet." )
444-
445- def ESP32 (self ):
446- """Handler for the 'ESP32' menu item."""
447- print ("ESP32" )
448- messagebox .showinfo ("ESP32" , "ESP32 choice functionality not implemented yet." )
449-
450478 def configure_ports (self ):
451479 """Handler for the 'Configure Ports' menu item."""
452480 print ("Configure Ports" )
453-
454481 options = [comport .device for comport in serial .tools .list_ports .comports ()]
455482 if len (options ) == 0 :
456483 message = "No COM ports available. Please connect a device and try again."
@@ -475,7 +502,7 @@ def configure_ports(self):
475502 def confirm_selection ():
476503 selected_option = combobox .get ()
477504 print (f"Selected option: { selected_option } " )
478- self .com_port = selected_option
505+ self .serial_port . com_port = selected_option
479506 dialog .destroy ()
480507
481508 confirm_button = tk .Button (dialog , text = "Confirm" , command = confirm_selection )
@@ -490,3 +517,41 @@ def about(self):
490517 """Handler for the 'About' menu item."""
491518 print ("About this software" )
492519 messagebox .showinfo ("About" , "ArduinoLogique v1.0\n Simulateur de circuits logiques" )
520+
521+ def open_port (self ):
522+ """Handler for the 'Open Port' menu item."""
523+ try :
524+ self .serial_port .connection = serial .Serial (
525+ port = self .serial_port .com_port , baudrate = self .serial_port .baud_rate , timeout = self .serial_port .timeout
526+ )
527+ print (f"Port série { self .serial_port .com_port } ouvert avec succès." )
528+ except serial .SerialException as e :
529+ print (f"Erreur lors de l'ouverture du port { self .serial_port .com_port } : { e } " )
530+
531+ def send_data (self , data ):
532+ """
533+ Send a string of data to the microcontroller through the serial port.
534+ """
535+ if self .serial_port .connection and self .serial_port .connection .is_open :
536+ try :
537+ # Convertir la chaîne en bytes et l'envoyer
538+ self .serial_port .connection .write (data .encode ("utf-8" ))
539+ print (f"Données envoyées: { data } " )
540+ except serial .SerialException as e :
541+ print (f"Erreur lors de l'envoi des données: { e } " )
542+ else :
543+ print ("Le port série n'est pas ouvert. Impossible d'envoyer les données." )
544+
545+ def close_port (self ):
546+ """Close the serial port."""
547+ if self .serial_port .connection and self .serial_port .connection .is_open :
548+ self .serial_port .connection .close ()
549+ print (f"Port série { self .serial_port .com_port } fermé." )
550+ else :
551+ print ("Le port série est déjà fermé." )
552+
553+ def download_script (self , script ):
554+ """Upload the script to the microcontroller through the serial port."""
555+ self .open_port ()
556+ self .send_data (script )
557+ self .close_port ()
0 commit comments