From e5e9af82995cb344da6480014ecbd91b9a9069cd Mon Sep 17 00:00:00 2001 From: Paolo Date: Thu, 9 May 2024 14:19:47 +0200 Subject: [PATCH] v0.2 --- backend/functions.py | 68 ++++++++++++++++------- cannellonipy | 2 +- main.py | 125 ++++++++++++++++++++++++++----------------- 3 files changed, 127 insertions(+), 68 deletions(-) diff --git a/backend/functions.py b/backend/functions.py index b7273e5..c258aaa 100644 --- a/backend/functions.py +++ b/backend/functions.py @@ -12,22 +12,37 @@ LOCALHOST_IP = "127.0.0.1" BUFFER_SIZE = 1024 +# Global variables +udp_socket = None +cannelloni_thread0 = None +cannelloni_thread1 = None +cannellonipy_handle0 = None +cannellonipy_handle1 = None +data_thread = None +is_running = None + # Controller -def start_connection_controller(IP_SCANNER, CAN0_PORT, CAN1_PORT, UDP_PORT, PATH_DBC_CAN0, PATH_DBC_CAN1, label_connected, connect_button): +def start_connection_controller(IP_SCANNER, CAN0_PORT, CAN1_PORT, UDP_PORT, PATH_DBC_CAN0, PATH_DBC_CAN1, label_connected, connect_button, disconnect_button): + global udp_socket, cannelloni_sockets, is_running, data_thread udp_socket = open_stream_udp(int(UDP_PORT)) cannelloni_sockets = open_stream_cannelloni(IP_SCANNER, CAN0_PORT, CAN1_PORT) time.sleep(1) if udp_socket and cannelloni_sockets[0].udp_pcb and cannelloni_sockets[1].udp_pcb: print("Connection established") - read_data_cannelloni(udp_socket, cannelloni_sockets, PATH_DBC_CAN0, PATH_DBC_CAN1, UDP_PORT) - label_connected.pack() + is_running = True + label_connected.grid(row=19, column=1, columnspan=10) connect_button.config(state="disabled") + disconnect_button.config(state="active") + data_thread = threading.Thread(target=read_data_cannelloni, args=(udp_socket, cannelloni_sockets, PATH_DBC_CAN0, PATH_DBC_CAN1, UDP_PORT), daemon=True) + data_thread.start() else: - print("Connection failed") + disconnect() + print("Failed to establish connection, disconnecting...") # Opens a stream from the specified scanner IP and ports def open_stream_cannelloni(IP_SCANNER, CAN0_PORT, CAN1_PORT): + global cannelloni_thread0, cannelloni_thread1, cannellonipy_handle0, cannellonipy_handle1 try: # Create a cannellonipy handle cannellonipy_handle0 = CannelloniHandle() @@ -60,38 +75,34 @@ def open_stream_udp(UDP_PORT): # Stream JSON data to PlotJuggler via UDP def send_stream_to_plotjuggler(udp_socket, json_data, UDP_PORT): try: - print(f"Sending JSON data: {json_data}") # Serialize the JSON data to a string - json_string = json.dumps(json_data) - # Encode the JSON string to bytes - json_bytes = json_string.encode('utf-8') + json_bytes = json.dumps(json_data).encode('utf-8') # Send JSON data to the server udp_socket.sendto(json_bytes, (LOCALHOST_IP, int(UDP_PORT))) # Check with cmd: nc -ul + print(f"Sending JSON data: {json_data}") #DEBUG except Exception as e: print(f"Error sending data via UDP: {e}") # Read data from the SCanner via cannelloni def read_data_cannelloni(udp_socket, cannelloni_sockets, PATH_DBC_CAN0, PATH_DBC_CAN1, UDP_PORT): + global is_running try: - while True: + while is_running: # Get the received frames form cannelloni received_frames_can0 = cannelloni_sockets[0].get_received_can_frames() received_frames_can1 = cannelloni_sockets[1].get_received_can_frames() - # Convert Cannelloni data to JSON - json_data0 = cannelloni_to_json(received_frames_can0, PATH_DBC_CAN0) - json_data1 = cannelloni_to_json(received_frames_can1, PATH_DBC_CAN1) - # Merge the two JSON objects streams - if json_data0 and json_data1: + if received_frames_can0 and received_frames_can1: + # Convert Cannelloni data to JSON + json_data0 = cannelloni_to_json(received_frames_can0, PATH_DBC_CAN0) + json_data1 = cannelloni_to_json(received_frames_can1, PATH_DBC_CAN1) + json_data = {**json_data0, **json_data1} print(json_data) # Send the JSON data to PlotJuggler via UDP send_stream_to_plotjuggler(udp_socket, json_data, UDP_PORT) - else: - print("No data received") - return except Exception as e: print(f"Error reading data from SCanner: {e}") @@ -134,9 +145,30 @@ def cannelloni_to_json(frame_cannelloni, dbc_path): except Exception as e: print(f"Error converting Cannelloni data to JSON: {e}") +# Disconnect from the serial and UDP servers +def disconnect(): + global udp_socket, cannelloni_thread0, cannelloni_thread1, data_thread, is_running, cannellonipy_handle0, cannellonipy_handle1 + try: + print("Disconnecting...") + if udp_socket: + # Close the app UDP socket + udp_socket.close() + if cannelloni_thread0 and cannelloni_thread1: + # Close the cannellonipy UDP sockets + cannellonipy_handle0.udp_pcb.close() + cannellonipy_handle1.udp_pcb.close() + # Join the threads + cannelloni_thread0.join() + cannelloni_thread1.join() + if data_thread: + # Join the read data thread + is_running = False + data_thread.join() + except Exception as e: + print(f"Error disconnecting: {e}") -# CANNELLONI DATA FORMAT +# ------------- CANNELLONI DATA FORMAT ------------- # ## Data Frames # Each data frame can contain several CAN frames. # The header of a data frame contains the following diff --git a/cannellonipy b/cannellonipy index 98d3d51..d620df9 160000 --- a/cannellonipy +++ b/cannellonipy @@ -1 +1 @@ -Subproject commit 98d3d519ca49d27ee146e5dc96960e6fa7bcf99e +Subproject commit d620df97f0468d2bcdfe7391119852b26150f350 diff --git a/main.py b/main.py index 1ebcfc6..aca1f8f 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,8 @@ import tkinter as tk from tkinter import filedialog -from backend.functions import start_connection_controller +from backend.functions import start_connection_controller, disconnect import threading +import time class GUI: def __init__(self): @@ -17,8 +18,8 @@ def __init__(self): self.CAN1_PORT = 5000 self.UDP_PORT = 9870 - # Flag - connected = False + global controller_thread + controller_thread = None # ---------- WINDOW SETTINGS --------------------------------------- self.app.title("SCanner Adapter") @@ -27,83 +28,89 @@ def __init__(self): self.screen_width = self.app.winfo_screenwidth() self.screen_height = self.app.winfo_screenheight() self.window_width = 500 - self.window_height = 620 + self.window_height = 640 self.x_coordinate = (self.screen_width - self.window_width) // 2 self.y_coordinate = (self.screen_height - self.window_height) // 2 # Set the geometry of the window to position it on the center of the screen self.app.geometry(f"{self.window_width}x{self.window_height}+{self.x_coordinate}+{self.y_coordinate}") + self.frame = tk.Frame(self.app) + self.frame.pack(pady=20) # ---------- GUI ELEMENTS -------------------------------------------- - self.label = tk.Label(self.app, text="Hello, this is the SCanner Adapter! \nYou have to enter all the required values on the form below \n and than press connect!") - self.label.pack(pady=10) + self.label = tk.Label(self.frame, text="Hello, this is the SCanner Adapter! \nYou have to enter all the required values on the form below \n and than press connect!") + self.label.grid(row=0, column=1, columnspan=10, pady=10) # Path to CONFIG - self.label_path_config_model = tk.Label(self.app, text="Select path to CONFIG model to use:") - self.label_path_config_model.pack() - self.textbox_path_config_model = tk.Text(self.app, height=1, width=50) - self.textbox_path_config_model.pack() + self.label_path_config_model = tk.Label(self.frame, text="Select path to CONFIG model to use:") + self.label_path_config_model.grid(row=1, column=1, columnspan=10) + self.textbox_path_config_model = tk.Text(self.frame, height=1, width=50) + self.textbox_path_config_model.grid(row=2, column=1, columnspan=10) self.textbox_path_config_model.insert("1.0", self.PATH_CONFIG_MODEL) - self.browse_button_config_model = tk.Button(self.app, text="Browse", command=self.browse_file_config_model) - self.browse_button_config_model.pack(pady=10) + self.browse_button_config_model = tk.Button(self.frame, text="Browse", command=self.browse_file_config_model) + self.browse_button_config_model.grid(row=3, column=1, columnspan=10, pady=10) # IP Scanner - self.label_ip_scanner = tk.Label(self.app, text="IP SCanner:") - self.label_ip_scanner.pack() - self.textbox_ip_scanner = tk.Text(self.app, height=1, width=30) + self.label_ip_scanner = tk.Label(self.frame, text="IP SCanner:") + self.label_ip_scanner.grid(row=4, column=1, columnspan=10) + self.textbox_ip_scanner = tk.Text(self.frame, height=1, width=30) self.textbox_ip_scanner.insert("1.0", self.IP_SCANNER) - self.textbox_ip_scanner.pack() + self.textbox_ip_scanner.grid(row=5, column=1, columnspan=10) # CAN0 Port - self.label_can0_port = tk.Label(self.app, text="CAN0 port:") - self.label_can0_port.pack() - self.textbox_can0_port = tk.Text(self.app, height=1, width=30) + self.label_can0_port = tk.Label(self.frame, text="CAN0 port:") + self.label_can0_port.grid(row=6, column=1, columnspan=10) + self.textbox_can0_port = tk.Text(self.frame, height=1, width=30) self.textbox_can0_port.insert("1.0", self.CAN0_PORT) - self.textbox_can0_port.pack() + self.textbox_can0_port.grid(row=7, column=1, columnspan=10) # CAN1 Port - self.label_can1_port = tk.Label(self.app, text="CAN1 port:") - self.label_can1_port.pack() - self.textbox_can1_port = tk.Text(self.app, height=1, width=30) + self.label_can1_port = tk.Label(self.frame, text="CAN1 port:") + self.label_can1_port.grid(row=8, column=1, columnspan=10) + self.textbox_can1_port = tk.Text(self.frame, height=1, width=30) self.textbox_can1_port.insert("1.0", self.CAN1_PORT) - self.textbox_can1_port.pack() + self.textbox_can1_port.grid(row=9, column=1, columnspan=10) # Local port JSON relay - self.label_udp_port = tk.Label(self.app, text="Local port JSON UDP server (for PlotJuggler):") - self.label_udp_port.pack() - self.textbox_udp_port = tk.Text(self.app, height=1, width=30) + self.label_udp_port = tk.Label(self.frame, text="Local port JSON UDP server (for PlotJuggler):") + self.label_udp_port.grid(row=10, column=1, columnspan=10) + self.textbox_udp_port = tk.Text(self.frame, height=1, width=30) self.textbox_udp_port.insert("1.0", self.UDP_PORT) - self.textbox_udp_port.pack() + self.textbox_udp_port.grid(row=11, column=1, columnspan=10) # Path dbc CAN0 - self.label_path_dbc_can0 = tk.Label(self.app, text="Select path to CAN0 DBC:") - self.label_path_dbc_can0.pack() - self.textbox_path_dbc_can0 = tk.Text(self.app, height=1, width=50) - self.textbox_path_dbc_can0.pack() + self.label_path_dbc_can0 = tk.Label(self.frame, text="Select path to CAN0 DBC:") + self.label_path_dbc_can0.grid(row=12, column=1, columnspan=10) + self.textbox_path_dbc_can0 = tk.Text(self.frame, height=1, width=50) + self.textbox_path_dbc_can0.grid(row=13, column=1, columnspan=10) self.textbox_path_dbc_can0.insert("1.0", self.PATH_DBC_CAN0) - self.browse_button_can0 = tk.Button(self.app, text="Browse", command=self.browse_file_can0) - self.browse_button_can0.pack() + self.browse_button_can0 = tk.Button(self.frame, text="Browse", command=self.browse_file_can0) + self.browse_button_can0.grid(row=14, column=1, columnspan=10) # Path dbc CAN1 - self.label_path_dbc_can1 = tk.Label(self.app, text="Select path to CAN1 DBC:") - self.label_path_dbc_can1.pack() - self.textbox_path_dbc_can1 = tk.Text(self.app, height=1, width=50) - self.textbox_path_dbc_can1.pack() + self.label_path_dbc_can1 = tk.Label(self.frame, text="Select path to CAN1 DBC:") + self.label_path_dbc_can1.grid(row=15, column=1, columnspan=10) + self.textbox_path_dbc_can1 = tk.Text(self.frame, height=1, width=50) + self.textbox_path_dbc_can1.grid(row=16, column=1, columnspan=10) self.textbox_path_dbc_can1.insert("1.0", self.PATH_DBC_CAN1) - self.browse_button_can1 = tk.Button(self.app, text="Browse", command=self.browse_file_can1) - self.browse_button_can1.pack() + self.browse_button_can1 = tk.Button(self.frame, text="Browse", command=self.browse_file_can1) + self.browse_button_can1.grid(row=17, column=1, columnspan=10) # Connect button - self.connect_button = tk.Button(self.app, text="Connect", command=self.connect_thread, font=("Arial", 14, "bold"), width=15, height=2) - self.connect_button.pack(pady=20) + self.connect_button = tk.Button(self.frame, text="Connect", command=self.connect_thread, font=("Arial", 14, "bold"), width=8, height=2) + self.connect_button.grid(row=18, column=0, columnspan=5, pady=15) + + # Disconnect button + self.disconnect_button = tk.Button(self.frame, text="Disconnect", command=self.disconnect, font=("Arial", 14, "bold"), width=8, height=2, state="disabled") + self.disconnect_button.grid(row=18, column=7, columnspan=5, pady=15) # Connected status - self.label_connected = tk.Label(self.app, text="CONNECTED!", font=("Arial", 14, "bold")) - self.label_connected.forget() + self.label_connected = tk.Label(self.frame, text="CONNECTED!", font=("Arial", 14, "bold")) + self.label_connected.grid_forget() # Connected status - self.label_config_loaded = tk.Label(self.app, text="CONFIG loaded!", font=("Arial", 14, "bold")) - self.label_config_loaded.forget() + self.label_config_loaded = tk.Label(self.frame, text="CONFIG loaded!", font=("Arial", 14, "bold")) + self.label_config_loaded.grid_forget() # Start the Tkinter event loop self.app.mainloop() @@ -116,7 +123,7 @@ def browse_file_config_model(self): self.textbox_path_config_model.delete("1.0", tk.END) self.textbox_path_config_model.insert("1.0", self.PATH_CONFIG_MODEL) - self.label_config_loaded.pack() + self.label_config_loaded.grid(row=20, column=1, columnspan=10) self.startup() def browse_file_can0(self): @@ -130,8 +137,10 @@ def browse_file_can1(self): self.textbox_path_dbc_can1.insert("1.0", self.PATH_DBC_CAN1) def connect_thread(self): + global controller_thread # Start a new thread for the connect function - threading.Thread(target=self.connect, daemon=True).start() + controller_thread = threading.Thread(target=self.connect, daemon=True) + controller_thread.start() def connect(self): # Get the content of each textbox widget @@ -143,7 +152,25 @@ def connect(self): self.PATH_DBC_CAN1 = self.textbox_path_dbc_can1.get("1.0", "end-1c") self.save_to_config_file() # Save the paths to the CONFIG file for future reuse - start_connection_controller(self.IP_SCANNER, self.CAN0_PORT, self.CAN1_PORT, self.UDP_PORT, self.PATH_DBC_CAN0, self.PATH_DBC_CAN1, self.label_connected, self.connect_button) + start_connection_controller(self.IP_SCANNER, self.CAN0_PORT, self.CAN1_PORT, self.UDP_PORT, + self.PATH_DBC_CAN0, self.PATH_DBC_CAN1, self.label_connected, + self.connect_button, self.disconnect_button) + + def disconnect(self): + global controller_thread + # Stop the controller thread if it's running + if controller_thread: + disconnect() + time.sleep(1) + controller_thread.join() + self.connect_button.config(state="active") + self.disconnect_button.config(state="disabled") + self.label_connected.grid_forget() + self.label_config_loaded.grid_forget() + controller_thread = None + print("Disconnected") + else: + return def startup(self): with open(self.PATH_CONFIG_MODEL, 'r') as f: