From ba7b7fbd77a28d644697b800bbe41759ee5633be Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Mon, 11 Sep 2023 00:46:06 -0400 Subject: [PATCH 1/8] python script to test the feeders --- Tester/requirements.txt | 2 + Tester/tester.py | 105 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 Tester/requirements.txt create mode 100644 Tester/tester.py diff --git a/Tester/requirements.txt b/Tester/requirements.txt new file mode 100644 index 0000000..e9e5da8 --- /dev/null +++ b/Tester/requirements.txt @@ -0,0 +1,2 @@ +tk +pyserial diff --git a/Tester/tester.py b/Tester/tester.py new file mode 100644 index 0000000..4d2cb38 --- /dev/null +++ b/Tester/tester.py @@ -0,0 +1,105 @@ +import tkinter as tk +import tkinter.messagebox +import serial +from tkinter.scrolledtext import ScrolledText +import threading + +class SerialCommunicationApp: + def __init__(self, root): + self.root = root + self.root.title("Serial Communication Application") + self.serial_ports = self.list_serial_ports() + + # Create a frame for the serial port connection widgets + serial_frame = tk.Frame(root) + serial_frame.pack() + + tk.Label(serial_frame, text="Serial Port:").grid(row=0, column=0) + self.serial_port_var = tk.StringVar(root) + self.serial_port_var.set(self.serial_ports[0] if self.serial_ports else "") + tk.OptionMenu(serial_frame, self.serial_port_var, *self.serial_ports).grid(row=0, column=1) + + tk.Label(serial_frame, text="Baud Rate (bps):").grid(row=0, column=2) + self.baud_rate_var = tk.StringVar(root) + self.baud_rate_var.set("115200") + tk.Entry(serial_frame, textvariable=self.baud_rate_var).grid(row=0, column=3) + + tk.Button(serial_frame, text="Connect", command=self.connect_serial).grid(row=0, column=4) + + # Add space between sections + tk.Label(root, text="").pack() + + self.create_buttons() + + # Create a frame for N0 to N31 buttons + button_frame = tk.Frame(root) + button_frame.pack() + + for i in range(32): + button_text = f"N{i}" + tk.Button(button_frame, text=button_text, command=lambda text=button_text: self.send_to_serial(text)).pack(side=tk.LEFT) + + # Add space between buttons and the text field + tk.Label(root, text="").pack() + + # Create a scrolled text field to display serial messages + self.message_text = ScrolledText(root) + self.message_text.pack(fill=tk.BOTH, expand=True) + self.message_text.config(state=tk.DISABLED) # Disable editing + + self.serial_connection = None # Serial connection will be established after connecting + self.serial_thread = None # Thread for serial reading + + def list_serial_ports(self): + try: + import serial.tools.list_ports + return [port.device for port in serial.tools.list_ports.comports()] + except ImportError: + return [] + + def connect_serial(self): + port = self.serial_port_var.get() + baud_rate = int(self.baud_rate_var.get()) + + try: + self.serial_connection = serial.Serial(port, baud_rate) + self.start_serial_thread() # Start the serial reading thread + except serial.SerialException as e: + tk.messagebox.showerror("Connection Error", str(e)) + + def create_buttons(self): + pass + + def send_to_serial(self, text): + if self.serial_connection is not None: + message = f"M600 {text} F4\n" + self.serial_connection.write(message.encode()) + + # Display the sent message in the text field + self.message_text.config(state=tk.NORMAL) # Enable editing + self.message_text.insert(tk.END, message) + self.message_text.config(state=tk.DISABLED) # Disable editing + + def start_serial_thread(self): + if self.serial_thread is None or not self.serial_thread.is_alive(): + self.serial_thread = threading.Thread(target=self.read_serial) + self.serial_thread.daemon = True # Thread will terminate when the application closes + self.serial_thread.start() + + def read_serial(self): + while True: + try: + data = self.serial_connection.readline().decode("utf-8", errors='replace') + self.message_text.config(state=tk.NORMAL) # Enable editing + self.message_text.insert(tk.END, data) + self.message_text.see(tk.END) + self.message_text.config(state=tk.DISABLED) # Disable editing + except UnicodeDecodeError as e: + self.message_text.config(state=tk.NORMAL) # Enable editing + self.message_text.insert(tk.END, "Invalid Character: " + repr(e.object[e.start:e.end]) + "\n") + self.message_text.config(state=tk.DISABLED) # Disable editing + +if __name__ == "__main__": + root = tk.Tk() + app = SerialCommunicationApp(root) + root.mainloop() From 76fa767f90aabe92fe3bff4e85172e85b2eada43 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Mon, 11 Sep 2023 00:48:53 -0400 Subject: [PATCH 2/8] fix the window title --- Tester/tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tester/tester.py b/Tester/tester.py index 4d2cb38..49ebd14 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -7,7 +7,7 @@ class SerialCommunicationApp: def __init__(self, root): self.root = root - self.root.title("Serial Communication Application") + self.root.title("MAXFeeder Tester") self.serial_ports = self.list_serial_ports() # Create a frame for the serial port connection widgets From 181e29dc4a4258d23ee38784441bf5b3287e67a1 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Mon, 11 Sep 2023 00:51:13 -0400 Subject: [PATCH 3/8] fix the script to support 34 feeders --- Tester/tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tester/tester.py b/Tester/tester.py index 49ebd14..6174ca5 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -35,7 +35,7 @@ def __init__(self, root): button_frame = tk.Frame(root) button_frame.pack() - for i in range(32): + for i in range(34): button_text = f"N{i}" tk.Button(button_frame, text=button_text, command=lambda text=button_text: self.send_to_serial(text)).pack(side=tk.LEFT) From c6f443bbb60b464d854b9d156e8c722520cc01c9 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Mon, 11 Sep 2023 23:42:24 -0400 Subject: [PATCH 4/8] add keyboard shortkut function and F value for each feeder controlled --- Tester/requirements.txt | 1 + Tester/tester.py | 136 +++++++++++++++++++++++++++++++--------- 2 files changed, 107 insertions(+), 30 deletions(-) diff --git a/Tester/requirements.txt b/Tester/requirements.txt index e9e5da8..c176693 100644 --- a/Tester/requirements.txt +++ b/Tester/requirements.txt @@ -1,2 +1,3 @@ tk pyserial +keyboard \ No newline at end of file diff --git a/Tester/tester.py b/Tester/tester.py index 6174ca5..ebd0bba 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -1,8 +1,8 @@ import tkinter as tk -import tkinter.messagebox import serial from tkinter.scrolledtext import ScrolledText import threading +import keyboard class SerialCommunicationApp: def __init__(self, root): @@ -12,7 +12,7 @@ def __init__(self, root): # Create a frame for the serial port connection widgets serial_frame = tk.Frame(root) - serial_frame.pack() + serial_frame.grid(row=0, column=0, sticky="ew") tk.Label(serial_frame, text="Serial Port:").grid(row=0, column=0) self.serial_port_var = tk.StringVar(root) @@ -24,32 +24,74 @@ def __init__(self, root): self.baud_rate_var.set("115200") tk.Entry(serial_frame, textvariable=self.baud_rate_var).grid(row=0, column=3) - tk.Button(serial_frame, text="Connect", command=self.connect_serial).grid(row=0, column=4) + self.connect_button = tk.Button(serial_frame, text="Connect", command=self.connect_serial) + self.connect_button.grid(row=0, column=4) - # Add space between sections - tk.Label(root, text="").pack() + self.disconnect_button = tk.Button(serial_frame, text="Disconnect", command=self.disconnect_serial, state=tk.DISABLED) + self.disconnect_button.grid(row=0, column=5) + + self.serial_status_label = tk.Label(serial_frame, text="Serial Status: Not Connected", fg="red") + self.serial_status_label.grid(row=0, column=6) - self.create_buttons() + # Add space between sections + tk.Label(root, text="").grid(row=1) - # Create a frame for N0 to N31 buttons + # Create a frame for N0 to N33 buttons with space between the buttons button_frame = tk.Frame(root) - button_frame.pack() + button_frame.grid(row=2, column=0, sticky="ew") + self.fx_menus = {} # Dictionary to store Fx value menus for each button + self.buttons = {} # Dictionary to store buttons + self.key_assign_buttons = {} # Dictionary to store "Assign Key" buttons + + self.assigned_keys = {} # Dictionary to store assigned keys for i in range(34): button_text = f"N{i}" - tk.Button(button_frame, text=button_text, command=lambda text=button_text: self.send_to_serial(text)).pack(side=tk.LEFT) + row, col = divmod(i, 8) + + # Create a frame for each button and menu + button_menu_frame = tk.Frame(button_frame, padx=10, pady=10) + button_menu_frame.grid(row=row, column=col, sticky="nsew") + + # Create an outline around each frame + button_menu_frame.config(borderwidth=2, relief=tk.RAISED) + + # Create a button with a callback to assign a key + self.buttons[button_text] = tk.Button(button_menu_frame, text=button_text, command=lambda text=button_text: self.send_to_serial(text)) + self.buttons[button_text].pack(fill=tk.BOTH) + self.buttons[button_text].pack(fill=tk.BOTH) + + # Create a menu for Fx values + fx_var = tk.StringVar(root) + fx_var.set("F4") # Default value is F4 + fx_menu = tk.OptionMenu(button_menu_frame, fx_var, "F4", "F8", "F12", "F16", "F20", "F24") + fx_menu.pack(fill=tk.BOTH) + + # Create a button to assign a key + self.key_assign_buttons[button_text] = tk.Button(button_menu_frame, text="Assign Key", command=lambda text=button_text: self.assign_key(text)) + self.key_assign_buttons[button_text].pack(fill=tk.BOTH) + + # Store the menu associated with the button in the dictionary + self.fx_menus[button_text] = fx_var # Add space between buttons and the text field - tk.Label(root, text="").pack() + tk.Label(root, text="").grid(row=3) # Create a scrolled text field to display serial messages self.message_text = ScrolledText(root) - self.message_text.pack(fill=tk.BOTH, expand=True) - self.message_text.config(state=tk.DISABLED) # Disable editing + self.message_text.grid(row=4, column=0, sticky="nsew") + self.message_text.config(state=tk.DISABLED) + self.message_text.config(height=10) + + self.serial_connection = None + self.serial_thread = None - self.serial_connection = None # Serial connection will be established after connecting - self.serial_thread = None # Thread for serial reading + # Configure grid weights for column resizing + root.grid_rowconfigure(4, weight=1) + root.grid_columnconfigure(0, weight=1) + + def list_serial_ports(self): try: import serial.tools.list_ports @@ -63,43 +105,77 @@ def connect_serial(self): try: self.serial_connection = serial.Serial(port, baud_rate) - self.start_serial_thread() # Start the serial reading thread + self.serial_status_label.config(text="Serial Status: Connected", fg="green") + self.connect_button.config(state=tk.DISABLED) + self.disconnect_button.config(state=tk.NORMAL) + self.start_serial_thread() except serial.SerialException as e: tk.messagebox.showerror("Connection Error", str(e)) - def create_buttons(self): - pass + def disconnect_serial(self): + if self.serial_connection is not None: + self.serial_connection.close() + self.serial_status_label.config(text="Serial Status: Disconnected", fg="red") + self.connect_button.config(state=tk.NORMAL) + self.disconnect_button.config(state=tk.DISABLED) def send_to_serial(self, text): if self.serial_connection is not None: - message = f"M600 {text} F4\n" + fx_value = self.fx_menus[text].get() + message = f"M600 {text} {fx_value}\n" self.serial_connection.write(message.encode()) - # Display the sent message in the text field - self.message_text.config(state=tk.NORMAL) # Enable editing - self.message_text.insert(tk.END, message) - self.message_text.config(state=tk.DISABLED) # Disable editing + #self.message_text.config(state=tk.NORMAL) + #self.message_text.insert(tk.END, message) + #self.message_text.see(tk.END) + #self.message_text.config(state=tk.DISABLED) + + + def assign_key(self, text): + # Remplacez le texte du bouton par "Press a Key..." pour l'indication + self.key_assign_buttons[text].config(text="Press a Key...") + self.root.update() # Mettre à jour l'interface pour afficher le nouveau texte + + # Attendre que l'utilisateur appuie sur une touche du clavier + selected_key = keyboard.read_event(suppress=True).name + + # Mettre à jour le texte du bouton avec la touche assignée + self.key_assign_buttons[text].config(text=selected_key) + + # Créer une fonction de rappel pour le bouton qui enverra les données série associées à la touche + def send_on_key_press(): + self.send_to_serial(text) + + # Associer la fonction de rappel au bouton et à la touche du clavier + keyboard.add_hotkey(selected_key, send_on_key_press) + + + def reset_assigned_keys(self): + for text in self.buttons.keys(): + self.assigned_keys[text] = "" + def start_serial_thread(self): if self.serial_thread is None or not self.serial_thread.is_alive(): self.serial_thread = threading.Thread(target=self.read_serial) - self.serial_thread.daemon = True # Thread will terminate when the application closes + self.serial_thread.daemon = True self.serial_thread.start() - + def read_serial(self): while True: try: data = self.serial_connection.readline().decode("utf-8", errors='replace') - self.message_text.config(state=tk.NORMAL) # Enable editing + self.message_text.config(state=tk.NORMAL) self.message_text.insert(tk.END, data) self.message_text.see(tk.END) - self.message_text.config(state=tk.DISABLED) # Disable editing + self.message_text.config(state=tk.DISABLED) except UnicodeDecodeError as e: - self.message_text.config(state=tk.NORMAL) # Enable editing + self.message_text.config(state=tk.NORMAL) self.message_text.insert(tk.END, "Invalid Character: " + repr(e.object[e.start:e.end]) + "\n") - self.message_text.config(state=tk.DISABLED) # Disable editing - + self.message_text.config(state=tk.DISABLED) + + if __name__ == "__main__": root = tk.Tk() app = SerialCommunicationApp(root) - root.mainloop() + root.mainloop() \ No newline at end of file From 2b8066ba75de66650490f689425196a1639e5ee2 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Mon, 11 Sep 2023 23:53:15 -0400 Subject: [PATCH 5/8] deleting keyboar assignation on second click --- Tester/tester.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Tester/tester.py b/Tester/tester.py index ebd0bba..1819360 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -131,24 +131,35 @@ def send_to_serial(self, text): #self.message_text.config(state=tk.DISABLED) + + def assign_key(self, text): - # Remplacez le texte du bouton par "Press a Key..." pour l'indication - self.key_assign_buttons[text].config(text="Press a Key...") - self.root.update() # Mettre à jour l'interface pour afficher le nouveau texte + current_assigned_key = self.assigned_keys.get(text, "") - # Attendre que l'utilisateur appuie sur une touche du clavier - selected_key = keyboard.read_event(suppress=True).name + if current_assigned_key: + # Key is already assigned, unassign it + self.assigned_keys[text] = "" + self.key_assign_buttons[text].config(text="Assign Key") + keyboard.remove_hotkey(current_assigned_key) + else: + # Assign a new key + # Replace the button text with "Press a Key..." for indication + self.key_assign_buttons[text].config(text="Press a Key...") + self.root.update() # Update the interface to display the new text - # Mettre à jour le texte du bouton avec la touche assignée - self.key_assign_buttons[text].config(text=selected_key) + # Wait for the user to press a keyboard key + selected_key = keyboard.read_event(suppress=True).name - # Créer une fonction de rappel pour le bouton qui enverra les données série associées à la touche - def send_on_key_press(): - self.send_to_serial(text) - - # Associer la fonction de rappel au bouton et à la touche du clavier - keyboard.add_hotkey(selected_key, send_on_key_press) + # Update the button text with the assigned key + self.key_assign_buttons[text].config(text=selected_key) + + # Create a callback function for the button to send serial data associated with the key + def send_on_key_press(): + self.send_to_serial(text) + # Assign the callback function to the button and the keyboard key + keyboard.add_hotkey(selected_key, send_on_key_press) + self.assigned_keys[text] = selected_key def reset_assigned_keys(self): for text in self.buttons.keys(): From 95e632dab256c27b072f6339861539810d62008e Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Tue, 12 Sep 2023 00:12:49 -0400 Subject: [PATCH 6/8] key not monitored when the app is not in focus --- Tester/tester.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tester/tester.py b/Tester/tester.py index 1819360..37bb057 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -90,6 +90,7 @@ def __init__(self, root): root.grid_rowconfigure(4, weight=1) root.grid_columnconfigure(0, weight=1) + def list_serial_ports(self): @@ -148,6 +149,8 @@ def assign_key(self, text): self.root.update() # Update the interface to display the new text # Wait for the user to press a keyboard key + + selected_key = keyboard.read_event(suppress=True).name # Update the button text with the assigned key @@ -155,7 +158,8 @@ def assign_key(self, text): # Create a callback function for the button to send serial data associated with the key def send_on_key_press(): - self.send_to_serial(text) + if root.focus_displayof(): + self.send_to_serial(text) # Assign the callback function to the button and the keyboard key keyboard.add_hotkey(selected_key, send_on_key_press) @@ -189,4 +193,7 @@ def read_serial(self): if __name__ == "__main__": root = tk.Tk() app = SerialCommunicationApp(root) + focus_check = tk.BooleanVar() + root.bind('', lambda _: focus_check.set(True)) + root.bind('', lambda _: focus_check.set(False)) root.mainloop() \ No newline at end of file From ae1cf6285c37866e4a7f842fbd7ba0eb0aed5f45 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Tue, 12 Sep 2023 00:30:16 -0400 Subject: [PATCH 7/8] add readme --- Tester/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Tester/README.md diff --git a/Tester/README.md b/Tester/README.md new file mode 100644 index 0000000..ac7ac78 --- /dev/null +++ b/Tester/README.md @@ -0,0 +1,16 @@ +# MAX Feeder Tester App + +## Installation +Nota: not tested under linux or mac. + +To get started you must install the python submodules + + pip install -r requirement.txt + +To launch the application + + python rester.py + +## Using the app + +... \ No newline at end of file From b33691d7b13b455363b8ac9db566625677a05349 Mon Sep 17 00:00:00 2001 From: Dany Doiron Date: Tue, 12 Sep 2023 01:01:40 -0400 Subject: [PATCH 8/8] add the port name in the serial status label add serial open condition for writing and listening --- Tester/tester.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Tester/tester.py b/Tester/tester.py index 37bb057..77abcea 100644 --- a/Tester/tester.py +++ b/Tester/tester.py @@ -1,6 +1,7 @@ import tkinter as tk import serial from tkinter.scrolledtext import ScrolledText +import tkinter.messagebox # Importez tkinter.messagebox séparément import threading import keyboard @@ -106,7 +107,7 @@ def connect_serial(self): try: self.serial_connection = serial.Serial(port, baud_rate) - self.serial_status_label.config(text="Serial Status: Connected", fg="green") + self.serial_status_label.config(text=f"Serial Status: Connected to {port}", fg="green") self.connect_button.config(state=tk.DISABLED) self.disconnect_button.config(state=tk.NORMAL) self.start_serial_thread() @@ -114,14 +115,14 @@ def connect_serial(self): tk.messagebox.showerror("Connection Error", str(e)) def disconnect_serial(self): - if self.serial_connection is not None: + if self.serial_connection and self.serial_connection.is_open: self.serial_connection.close() self.serial_status_label.config(text="Serial Status: Disconnected", fg="red") self.connect_button.config(state=tk.NORMAL) self.disconnect_button.config(state=tk.DISABLED) def send_to_serial(self, text): - if self.serial_connection is not None: + if self.serial_connection and self.serial_connection.is_open: fx_value = self.fx_menus[text].get() message = f"M600 {text} {fx_value}\n" self.serial_connection.write(message.encode()) @@ -179,11 +180,12 @@ def start_serial_thread(self): def read_serial(self): while True: try: - data = self.serial_connection.readline().decode("utf-8", errors='replace') - self.message_text.config(state=tk.NORMAL) - self.message_text.insert(tk.END, data) - self.message_text.see(tk.END) - self.message_text.config(state=tk.DISABLED) + if self.serial_connection and self.serial_connection.is_open: + data = self.serial_connection.readline().decode("utf-8", errors='replace') + self.message_text.config(state=tk.NORMAL) + self.message_text.insert(tk.END, data) + self.message_text.see(tk.END) + self.message_text.config(state=tk.DISABLED) except UnicodeDecodeError as e: self.message_text.config(state=tk.NORMAL) self.message_text.insert(tk.END, "Invalid Character: " + repr(e.object[e.start:e.end]) + "\n")