diff --git a/CONFIG.txt b/CONFIG.txt index 8ba924e..c798b0c 100644 --- a/CONFIG.txt +++ b/CONFIG.txt @@ -1,6 +1,8 @@ -VALUES=example1,example2,NOPE,example3 +VALUES=EX1,EX2,EX3 +AUTO_DETECT=False +AUTO_DETECT_FLAG=### NEWLINE=LF SEPARATOR=, BAUDRATE=9600 UDP_PORT=4444 -SERIAL_PORT=/dev/tty.usbmodem21301 \ No newline at end of file +SERIAL_PORT=/dev/tty.usbmodem11201 \ No newline at end of file diff --git a/README.md b/README.md index cc65bdf..8db1734 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ pip install -r requirements.txt To configure the parameters of the program you have to create a `CONFIG.txt` file with the following parameters: ```txt VALUES=PARAM1,PARAM2,NULL,PARAM3 +AUTO_DETECT=True +AUTO_DETECT_FLAG=### NEWLINE=; SEPARATOR=, BAUDRATE=9600 @@ -39,10 +41,22 @@ With the configuration shown above you can send the following JSON (assuming CSV ### Command-line run option It is possible to run the program directly from the command line (for example) with the following command: ```bash -./SerialToUdpTranslator-v2.8.2-Win-x64.exe --config ./CONFIG.txt --nogui -#./NameOfTheExecutable --config ./CONFIG.txt --nogui +./SerialToUdpTranslator-v2.8.2-Win-x64.exe --config ./CONFIG.txt --autodetect --nogui +#./NameOfTheExecutable --config ./CONFIG.txt --autodetect --nogui +``` +Where you can specify the path of the configuration file `--config` -> `CONFIG.txt` file (explained above) and the `--nogui` flag to run the program without the GUI. +You can also specify the `--autodetect` flag to start the program with the autodetection of the `` in order to automatically discover the parameters that will be passed to the program as first message (note that if you want to use special characters like ### you have to pass them via CONFIG file above), for example: +```txt +starting flag -> ### +first message -> ###,PARAM1,PARAM2,NULL,PARAM3 +second message -> 1,2,3,4 +parsing result: +{ + "PARAM1": 1, + "PARAM2": 2, + "PARAM3": 4 +} ``` -Where you can specify the path of the configuration file `--config` -> `CONFIG.txt` file (explained above) and the `--nogui` flag to run the program without the GUI. ## How to pair with Plotjuggler graphing tool 1. Open Plotjuggler diff --git a/backend/functions.py b/backend/functions.py index 5c99139..df2c9f5 100644 --- a/backend/functions.py +++ b/backend/functions.py @@ -11,17 +11,19 @@ udp_socket = None ser_socket = None +values = [] # Controller -def start_connection_controller(UDP_PORT, SERIAL_PORT, VALUES, NEWLINE, SEPARATOR, BAUDRATE, label_connected, connect_button, disconnect_button): - global udp_socket, ser_socket +def start_connection_controller(UDP_PORT, SERIAL_PORT, VALUES, NEWLINE, SEPARATOR, BAUDRATE, AUTO_DETECT, AUTO_DETECT_FLAG, label_connected, connect_button, disconnect_button): + global udp_socket, ser_socket, values udp_socket = open_stream_udp(int(UDP_PORT)) ser_socket = open_stream_serial(SERIAL_PORT, BAUDRATE) + values = VALUES time.sleep(1) if udp_socket and ser_socket: print("Connection established") - read_serial_data(ser_socket, udp_socket, VALUES, NEWLINE, SEPARATOR, UDP_PORT) + read_serial_data(ser_socket, udp_socket, NEWLINE, SEPARATOR, UDP_PORT, AUTO_DETECT, AUTO_DETECT_FLAG) if label_connected: label_connected.grid(row=18, column=1, columnspan=10) if connect_button: @@ -57,7 +59,7 @@ def open_stream_udp(UDP_PORT): print(f"Error opening JSON streaming server: {e}") # Read data from the serial port -def read_serial_data(ser_socket, udp_socket, VALUES, NEWLINE, SEPARATOR, UDP_PORT): +def read_serial_data(ser_socket, udp_socket, NEWLINE, SEPARATOR, UDP_PORT, AUTO_DETECT, AUTO_DETECT_FLAG): data_buffer = "" # NEWLINE translation of special characters @@ -90,7 +92,7 @@ def read_data_handler(data_buffer): #print(f"Received line: {line}") #DEBUG # Process the line (convert to json and send it to the UDP server) - csv_to_json(udp_socket, line, UDP_PORT, VALUES, SEPARATOR) + csv_to_json(udp_socket, line, UDP_PORT, SEPARATOR, AUTO_DETECT, AUTO_DETECT_FLAG) except serial.SerialException as e: break @@ -103,24 +105,35 @@ def read_data_handler(data_buffer): read_serial_thread.start() # Converter Serial CSV stream to JSON converter -def csv_to_json(udp_socket, line_csv, UDP_PORT, VALUES, SEPARATOR): +def csv_to_json(udp_socket, line_csv, UDP_PORT, SEPARATOR, AUTO_DETECT, AUTO_DETECT_FLAG): + global values try: # Split the CSV data by comma csv_parts = line_csv.strip().split(SEPARATOR) + if AUTO_DETECT: + if csv_parts[0] == AUTO_DETECT_FLAG: + values = [csv_parts[i] for i in range(1, len(csv_parts))] + print(f"Auto-detected values: {values}") + return + else: + # Check if VALUES are default and replace them with param1, param2, param3, ... + if not values or values == ['']: + values = [f"param{i+1}" for i in range(len(csv_parts))] + # Initialize a dictionary to hold the JSON data json_data = {} # Loop through each value and its corresponding csv part - for value, csv_part in zip(VALUES, csv_parts): + for value, csv_part in zip(values, csv_parts): # Check if the csv part is not NULL if value != "NOPE": # Convert value to int or float if possible try: - csv_part = int(csv_part) # Try converting to integer + csv_part = int(csv_part) except ValueError: try: - csv_part = float(csv_part) # Try converting to float + csv_part = float(csv_part) except ValueError: pass # If not convertible, keep the value as string diff --git a/main.py b/main.py index 5ac0605..69106cf 100644 --- a/main.py +++ b/main.py @@ -6,15 +6,17 @@ import argparse class GUI: - def __init__(self, config_path=None, direct_connect=False): + def __init__(self, config_path=None, autodetect=None,direct_connect=False): # Config parameters self.PATH_CONFIG_MODEL = "Path" - self.VALUES=['example1,example2,example3'] + self.VALUES='' self.NEWLINE=';' self.SEPARATOR=',' self.BAUDRATE=9600 self.UDP_PORT=5000 self.SERIAL_PORT='/dev/ttyUSB0' + self.AUTO_DETECT=False + self.AUTO_DETECT_FLAG="###" global controller_thread controller_thread = None @@ -28,6 +30,10 @@ def __init__(self, config_path=None, direct_connect=False): self.PATH_CONFIG_MODEL = config_path self.startup() + if autodetect: + self.AUTO_DETECT = True + self.AUTO_DETECT_FLAG = autodetect + if direct_connect: self.connect_thread(direct=True) return @@ -42,7 +48,7 @@ def __init__(self, config_path=None, direct_connect=False): self.screen_width = self.app.winfo_screenwidth() self.screen_height = self.app.winfo_screenheight() self.window_width = 500 - self.window_height = 600 + self.window_height = 650 self.x_coordinate = (self.screen_width - self.window_width) // 2 self.y_coordinate = (self.screen_height - self.window_height) // 2 @@ -64,55 +70,67 @@ def __init__(self, config_path=None, direct_connect=False): 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) + # Autodetect values + self.label_autodetect_values = tk.Label(self.frame, text="Check the box if you want to autodetect the values, \nspecify the special line starter value:") + self.label_autodetect_values.grid(row=4, column=1, columnspan=10) + self.auto_detect_var = tk.BooleanVar() + if self.AUTO_DETECT == True: + self.auto_detect_var.set(True) + self.checkbox_autodetect = tk.Checkbutton(self.frame, text="Autodetect", variable=self.auto_detect_var) + self.checkbox_autodetect.grid(row=5, column=0, columnspan=10) + self.textbox_autodetect_values = tk.Text(self.frame, height=1, width=10) + self.textbox_autodetect_values.insert("1.0", self.AUTO_DETECT_FLAG) + self.textbox_autodetect_values.grid(row=5, column=6, columnspan=10) + # Values - self.label_values = tk.Label(self.frame, text="Insert data label values, separated by spaces:") - self.label_values.grid(row=4, column=1, columnspan=10) + self.label_values = tk.Label(self.frame, text="OR insert data label values, separated by the value you specify below:") + self.label_values.grid(row=6, column=1, columnspan=10) self.textbox_values = tk.Text(self.frame, height=1, width=50) self.textbox_values.insert("1.0", self.VALUES) - self.textbox_values.grid(row=5, column=1, columnspan=10) + self.textbox_values.grid(row=7, column=1, columnspan=10) # Newline self.label_newline = tk.Label(self.frame, text="Insert newline separator for the upcoming CSV stream. \nInsert LF->'\\n' and CRLF->'\\r\\n':") - self.label_newline.grid(row=6, column=1, columnspan=10) + self.label_newline.grid(row=8, column=1, columnspan=10) self.textbox_newline = tk.Text(self.frame, height=1, width=30) self.textbox_newline.insert("1.0", self.NEWLINE) - self.textbox_newline.grid(row=7, column=1, columnspan=10) + self.textbox_newline.grid(row=9, column=1, columnspan=10) # Separator self.label_separator = tk.Label(self.frame, text="Insert single data separator for the upcoming CSV stream:") - self.label_separator.grid(row=8, column=1, columnspan=10) + self.label_separator.grid(row=10, column=1, columnspan=10) self.textbox_separator = tk.Text(self.frame, height=1, width=30) self.textbox_separator.insert("1.0", self.SEPARATOR) - self.textbox_separator.grid(row=9, column=1, columnspan=10) + self.textbox_separator.grid(row=11, column=1, columnspan=10) # Baudrate self.label_baudrate = tk.Label(self.frame, text="Insert baudrate of the serial signal:") - self.label_baudrate.grid(row=10, column=1, columnspan=10) + self.label_baudrate.grid(row=12, column=1, columnspan=10) self.textbox_baudrate = tk.Text(self.frame, height=1, width=30) self.textbox_baudrate.insert("1.0", self.BAUDRATE) - self.textbox_baudrate.grid(row=11, column=1, columnspan=10) + self.textbox_baudrate.grid(row=13, column=1, columnspan=10) # Local UDP Port self.label_udp_port = tk.Label(self.frame, text="Local UDP port:") - self.label_udp_port.grid(row=12, column=1, columnspan=10) + self.label_udp_port.grid(row=14, 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.grid(row=13, column=1, columnspan=10) + self.textbox_udp_port.grid(row=15, column=1, columnspan=10) # Serial Port self.label_serial_port = tk.Label(self.frame, text="Serial port:") - self.label_serial_port.grid(row=14, column=1, columnspan=10) + self.label_serial_port.grid(row=16, column=1, columnspan=10) self.textbox_serial_port = tk.Text(self.frame, height=1, width=30) self.textbox_serial_port.insert("1.0", self.SERIAL_PORT) - self.textbox_serial_port.grid(row=15, column=1, columnspan=10) + self.textbox_serial_port.grid(row=17, column=1, columnspan=10) # Connect button 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=16, column=0, columnspan=5, pady=15) + 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=16, column=7, columnspan=5, pady=15) + self.disconnect_button.grid(row=18, column=7, columnspan=5, pady=15) # Connected status self.label_connected = tk.Label(self.frame, text="CONNECTED!", font=("Arial", 14, "bold")) @@ -165,10 +183,15 @@ def connect(self, direct=False): self.SEPARATOR = self.textbox_separator.get("1.0", "end-1c") if hasattr(self, 'textbox_baudrate'): self.BAUDRATE = self.textbox_baudrate.get("1.0", "end-1c") + if hasattr(self, 'auto_detect_var'): + self.AUTO_DETECT = self.auto_detect_var.get() + if hasattr(self, 'textbox_autodetect_values'): + self.AUTO_DETECT_FLAG = self.textbox_autodetect_values.get("1.0", "end-1c") self.save_to_config_file() # Save the data to the CONFIG file for future reuse try: - start_connection_controller(self.UDP_PORT, self.SERIAL_PORT, self.VALUES, self.NEWLINE, self.SEPARATOR, self.BAUDRATE, self.label_connected, self.connect_button, self.disconnect_button) + start_connection_controller(self.UDP_PORT, self.SERIAL_PORT, self.VALUES, self.NEWLINE, self.SEPARATOR, self.BAUDRATE, + self.AUTO_DETECT, self.AUTO_DETECT_FLAG, self.label_connected, self.connect_button, self.disconnect_button) except Exception as e: print(f"Connection failed: {e}") @@ -204,6 +227,10 @@ def startup(self): self.SERIAL_PORT = value elif name == 'VALUES': self.VALUES = value.replace(' ', '_').strip() + elif name == 'AUTO_DETECT': + self.AUTO_DETECT = value + elif name == 'AUTO_DETECT_FLAG': + self.AUTO_DETECT_FLAG = value def update_interface(self): self.textbox_path_config_model.delete("1.0", tk.END) @@ -220,6 +247,8 @@ def update_interface(self): self.textbox_separator.insert("1.0", str(self.SEPARATOR)) self.textbox_baudrate.delete("1.0", tk.END) self.textbox_baudrate.insert("1.0", str(self.BAUDRATE)) + self.textbox_autodetect_values.delete("1.0", tk.END) + self.textbox_autodetect_values.insert("1.0", str(self.AUTO_DETECT_FLAG)) def save_to_config_file(self): values_string = ",".join(self.VALUES) @@ -227,6 +256,8 @@ def save_to_config_file(self): if self.PATH_CONFIG_MODEL != "Path": with open(self.PATH_CONFIG_MODEL, 'w') as f: f.write(f'VALUES={values_string}\n') + f.write(f'AUTO_DETECT={self.AUTO_DETECT}\n') + f.write(f'AUTO_DETECT_FLAG={self.AUTO_DETECT_FLAG}\n') f.write(f'NEWLINE={self.NEWLINE}\n') f.write(f'SEPARATOR={self.SEPARATOR}\n') f.write(f'BAUDRATE={self.BAUDRATE}\n') @@ -236,7 +267,8 @@ def save_to_config_file(self): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Start the Serial CSV to UDP JSON Translator GUI.") parser.add_argument('--config', type=str, help='Path to the configuration file') + parser.add_argument('--autodetect', type=str, help='Autodetect values from the serial stream, specify the special line starter value') parser.add_argument('--nogui', action='store_true', default=False, help='Connect directly without showing the GUI') args = parser.parse_args() - gui = GUI(config_path=args.config, direct_connect=args.nogui) \ No newline at end of file + gui = GUI(config_path=args.config, autodetect=args.autodetect,direct_connect=args.nogui) \ No newline at end of file