import tkinter as tk import serial import threading import csv import serial.tools.list_ports from datetime import datetime import pytz import os # Set timezone for Amsterdam AMSTERDAM_TZ = pytz.timezone("Europe/Amsterdam") # Global variables recording = False csv_file = None csv_writer = None ser = None def get_amsterdam_time(): """Returns the current time in Amsterdam with microseconds.""" return datetime.now(AMSTERDAM_TZ).strftime("%Y-%m-%d %H:%M:%S.%f") def get_timestamped_filename(): """Generates a timestamped filename in Amsterdam time.""" timestamp = datetime.now(AMSTERDAM_TZ).strftime("%Y-%m-%d_%H-%M-%S") return f"sensor_data_{timestamp}.csv" def list_serial_ports(): """Returns a list of available serial ports.""" return [port.device for port in serial.tools.list_ports.comports()] def connect_serial(): """Connects to the selected serial port.""" global ser selected_port = port_var.get() if selected_port: try: ser = serial.Serial(selected_port, 115200, timeout=1) lbl_status.config(text=f"Connected to {selected_port}", fg="green") threading.Thread(target=read_serial, daemon=True).start() except Exception as e: lbl_status.config(text=f"Error: {e}", fg="red") def read_serial(): """Reads serial data and updates the GUI while optionally saving to CSV.""" global csv_writer, csv_file while ser and ser.is_open: try: line = ser.readline().decode('utf-8').strip() values = line.split(",") if len(values) == 4: timestamp = get_amsterdam_time() # Format numbers to keep width fixed formatted_values = [f"{int(v):>6}" for v in values] # Update GUI labels lbl_value1.config(text=formatted_values[0]) lbl_value2.config(text=formatted_values[1]) lbl_value3.config(text=formatted_values[2]) lbl_value4.config(text=formatted_values[3]) # Save to CSV if recording if recording and csv_writer: csv_writer.writerow([timestamp] + values) csv_file.flush() # Ensure data is written immediately except: pass def toggle_recording(): """Starts or stops data recording with a timestamped filename.""" global recording, csv_file, csv_writer if not recording: btn_record.config(text="Stop Recording", fg="red") # Create a timestamped filename filename = get_timestamped_filename() csv_file = open(filename, "w", newline="") csv_writer = csv.writer(csv_file) csv_writer.writerow(["Timestamp", "Value 1", "Value 2", "Value 3", "Value 4"]) lbl_status.config(text=f"Recording to {filename}", fg="blue") recording = True else: btn_record.config(text="Start Recording", fg="black") recording = False if csv_file: csv_file.close() csv_file = None lbl_status.config(text="Recording Stopped", fg="black") # Create main GUI window root = tk.Tk() root.title("BLE Sensor Data Logger") root.geometry("400x300") # Set window size root.resizable(False, False) # Create a frame for better layout frame = tk.Frame(root, padx=20, pady=10) frame.pack(pady=5) # Dropdown for selecting serial port tk.Label(frame, text="Select Serial Port:", font=("Arial", 12)).grid(row=0, column=0, sticky="w", padx=5) port_var = tk.StringVar(root) ports = list_serial_ports() port_dropdown = tk.OptionMenu(frame, port_var, *ports) port_dropdown.grid(row=0, column=1, padx=5) port_var.set(ports[0] if ports else "No ports found") # Connect button btn_connect = tk.Button(frame, text="Connect", font=("Arial", 12), command=connect_serial) btn_connect.grid(row=0, column=2, padx=5) # Connection status label lbl_status = tk.Label(root, text="Not connected", font=("Arial", 10), fg="red") lbl_status.pack(pady=5) # Sensor values section monospace_font = ("Courier", 14, "bold") tk.Label(frame, text="Value 1:", font=("Arial", 12)).grid(row=1, column=0, sticky="w", padx=10) lbl_value1 = tk.Label(frame, text="------", font=monospace_font, fg="blue", width=6, anchor="e") lbl_value1.grid(row=1, column=1, sticky="w") tk.Label(frame, text="Value 2:", font=("Arial", 12)).grid(row=2, column=0, sticky="w", padx=10) lbl_value2 = tk.Label(frame, text="------", font=monospace_font, fg="blue", width=6, anchor="e") lbl_value2.grid(row=2, column=1, sticky="w") tk.Label(frame, text="Value 3:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", padx=10) lbl_value3 = tk.Label(frame, text="------", font=monospace_font, fg="blue", width=6, anchor="e") lbl_value3.grid(row=3, column=1, sticky="w") tk.Label(frame, text="Value 4:", font=("Arial", 12)).grid(row=4, column=0, sticky="w", padx=10) lbl_value4 = tk.Label(frame, text="------", font=monospace_font, fg="blue", width=6, anchor="e") lbl_value4.grid(row=4, column=1, sticky="w") # Start/Stop Recording Button btn_record = tk.Button(root, text="Start Recording", font=("Arial", 12), command=toggle_recording) btn_record.pack(pady=10) # Run the GUI root.mainloop()