From f0d691ff8eedd439e39d39733e72b1ddde06739d Mon Sep 17 00:00:00 2001 From: Ismail Hassaballa Date: Tue, 2 Dec 2025 19:04:11 +0100 Subject: [PATCH] Added calibration data for sensors --- Serial_decoder.py | 2 +- mainplot.py | 149 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 111 insertions(+), 40 deletions(-) diff --git a/Serial_decoder.py b/Serial_decoder.py index e825228..f92794f 100644 --- a/Serial_decoder.py +++ b/Serial_decoder.py @@ -7,7 +7,7 @@ from datetime import datetime import queue import re -PORT = "COM9" +PORT = "COM7" SerialPort = serial.Serial(port=PORT,baudrate=250000) input_queue = queue.SimpleQueue() diff --git a/mainplot.py b/mainplot.py index 6c18de5..8189ee8 100644 --- a/mainplot.py +++ b/mainplot.py @@ -1,4 +1,3 @@ - import Serial_decoder as SD import tkinter as tk from tkinter import ttk @@ -16,14 +15,38 @@ FILENAME = f"SaveData/{datetime.today().strftime('%Y_%m_%d_%H_%M')}" # === Plot history === MAX_POINTS = 100 # rolling window length -history_x = deque(maxlen=MAX_POINTS) # timestamps -history_y = [deque(maxlen=MAX_POINTS) for _ in range(16)] # per-channel values + +# Derived channels (what we actually plot): +# 0: Thrust (avg of load cells 1 & 2) +# 1-6: Temp 1..6 +# 7-14: Pressure 1..8 +DERIVED_CHANNEL_LABELS = [ + "Thrust", # 0 + "Temp 1", # 1 + "Temp 2", # 2 + "Temp 3", # 3 + "Temp 4", # 4 + "Temp 5", # 5 + "Temp 6", # 6 + "Pressure 1", # 7 + "Pressure 2", # 8 + "Pressure 3", # 9 + "Pressure 4", # 10 + "Pressure 5", # 11 + "Pressure 6", # 12 + "Pressure 7", # 13 + "Pressure 8", # 14 +] +NUM_DERIVED_CHANNELS = len(DERIVED_CHANNEL_LABELS) # 15 + +history_x = deque(maxlen=MAX_POINTS) # timestamps +history_y = [deque(maxlen=MAX_POINTS) for _ in range(NUM_DERIVED_CHANNELS)] # per-channel values # === GUI setup === root = tk.Tk() root.title("Live 4x4 Grid + Selectable Real-Time Plot") -# Left: 4x4 grid +# Left: 4x4 grid (raw ADC values from 16 channels) grid_frame = ttk.Frame(root) grid_frame.grid(row=0, column=0, padx=10, pady=10, sticky="n") @@ -39,27 +62,29 @@ for row in range(4): lbl.grid(row=row, column=col, padx=5, pady=5) labels.append(lbl) -# Last tick label (as you had) -last_tick_lbl = tk.Label( - grid_frame, text="Last Tick: -", width=20, height=3, - borderwidth=2, relief="groove", font=("Arial", 14) -) +# Last / delta tick labels lbl = tk.Label(root, text="Last Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14)) -lbl.grid(row = 4, column=0, columnspan=2) -labels.append(lbl) -lbl = tk.Label(root, text="Delta Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14)) -lbl.grid(row=4, column=2, columnspan= 2) -labels.append(lbl) +lbl.grid(row=4, column=0, columnspan=2) +labels.append(lbl) # index 16 -# Checkboxes to select plotted channels +lbl = tk.Label(root, text="Delta Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14)) +lbl.grid(row=4, column=2, columnspan=2) +labels.append(lbl) # index 17 + +# Checkboxes to select plotted (derived) channels check_frame = ttk.LabelFrame(root, text="Plot Selection") check_frame.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nw") check_vars = [] -for i in range(16): +for i, ch_label in enumerate(DERIVED_CHANNEL_LABELS): var = tk.IntVar(value=0) - chk = ttk.Checkbutton(check_frame, text=f"Ch {i+1}", variable=var, command=lambda: update_plot()) - chk.grid(row=i//4, column=i%4, sticky="w", padx=4, pady=2) + chk = ttk.Checkbutton( + check_frame, + text=ch_label, + variable=var, + command=update_plot if 'update_plot' in globals() else None # placeholder; will be updated below + ) + chk.grid(row=i // 4, column=i % 4, sticky="w", padx=4, pady=2) check_vars.append(var) # Right: Matplotlib plot @@ -68,7 +93,7 @@ plot_frame.grid(row=0, column=1, rowspan=2, padx=10, pady=10, sticky="n") fig = Figure(figsize=(6, 4), dpi=100) ax = fig.add_subplot(111) -ax.set_title("Selected Channels (rolling)") +ax.set_title("Selected Channels") ax.set_xlabel("Timestamp") ax.set_ylabel("Value") canvas = FigureCanvasTkAgg(fig, master=plot_frame) @@ -77,70 +102,116 @@ canvas.get_tk_widget().pack(fill="both", expand=True) # Optional controls controls_frame = ttk.Frame(plot_frame) controls_frame.pack(fill="x", pady=5) -# Clear selection button + def clear_selection(): for v in check_vars: v.set(0) update_plot() + +def clear_history(): + """Clear stored history for all plotted channels.""" + history_x.clear() + for dq in history_y: + dq.clear() + update_plot() # refresh plot so it shows 'No data' message + ttk.Button(controls_frame, text="Clear Selection", command=clear_selection).pack(side="left") +ttk.Button(controls_frame, text="Clear History", command=clear_history).pack(side="left") # === Update functions === Last_tick: int = 0 -def update_grid(values: list,tick: int): + +def update_grid(values: list, tick: int): + """Update the 4x4 grid with raw ADC values and write ADC csv.""" global Last_tick - with open(f"{FILENAME}_ADC.csv","+a") as file: + # NOTE: mode should be "a" or "a+", not "+a" + with open(f"{FILENAME}_ADC.csv", "a") as file: file.write(f"{tick}") - for i in range(16): + for i in range(16): # 16 raw ADC channels file.write(f",{values[i]}") labels[i].config(text=f"{values[i]:n}") - labels[16].config(text=f"Last Tick:{tick:n}") - labels[17].config(text=f"Delta Tick:{(tick-Last_tick):n}") + ms_total = tick + hours, rem = divmod(ms_total, 3_600_000) # 1000*60*60 + minutes, rem = divmod(rem, 60_000) # 1000*60 + seconds, milliseconds = divmod(rem, 1000) + labels[16].config( text=f"UP Time:{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}") + labels[17].config(text=f"Delta Tick:{(tick - Last_tick):n}") Last_tick = tick file.write("\n") -def update_SI(value:SD.ReturnDecoder): - with open(f"{FILENAME}_SI.csv","+a") as file: +def update_SI(value: SD.ReturnDecoder): + # NOTE: mode should be "a" or "a+", not "+a" + with open(f"{FILENAME}_SI.csv", "a") as file: file.write(f"{value.timestamp},{value.log_message}\n") - print(f"\033[F\033[2Ktime:{value.timestamp}, SI:{value.log_message}",end="\n") + print(f"\033[F\033[2Ktime:{value.timestamp}, SI:{value.log_message}", end="\n") + def append_history(adc_values: list, timestamp: int): - """Store the new sample in the rolling history.""" + """Store the new sample in the rolling history (derived units).""" + if len(history_x)> 0 and timestamp < history_x[-1]: + return # discard out-of-order samples history_x.append(timestamp) - for i in range(16): - history_y[i].append(adc_values[i]) + + # 0: Thrust (average of load cells 0 and 1) + lc_avg = (adc_values[0] + adc_values[1]) / 2 + weight = -1.166759308000000e-04 * lc_avg + 4.971416323340051e+02 + history_y[0].append(weight) + + # 1-6: Temperatures from channels 2..7 + for i in range(2, 8): # six thermocouples + temp = 0.000111 * adc_values[i] + 2.31991 + history_y[i - 1].append(temp) # 2→1, 7→6 + + # 7-14: Pressures from channels 8..15 + for i in range(8, 16): # eight pressure sensors + pres = 0.0000153522 * adc_values[i] - 6.5652036917 + history_y[i - 1].append(pres) # 8→7, 15→14 + def update_plot(): - """Redraw plot based on selected channels and available history.""" + """Redraw plot based on selected derived channels and available history.""" ax.clear() - ax.set_title("Selected Channels (rolling)") + ax.set_title("Selected Channels") ax.set_xlabel("Timestamp") ax.set_ylabel("Value") selected = [i for i, var in enumerate(check_vars) if var.get() == 1] if len(history_x) == 0 or len(selected) == 0: - ax.text(0.5, 0.5, "No data / No channels selected", ha="center", va="center", transform=ax.transAxes) + ax.text( + 0.5, 0.5, + "No data / No channels selected", + ha="center", va="center", + transform=ax.transAxes + ) canvas.draw() return x = list(history_x) for idx in selected: - y = list(history_y[idx]) - ax.plot(x, y, label=f"Ch {idx+1}") + # safety: guard against any mismatch, just in case + if idx < len(history_y): + y = list(history_y[idx]) + ax.plot(x, y, label=DERIVED_CHANNEL_LABELS[idx]) + ax.legend(loc="upper left", fontsize=8) ax.grid(True, linestyle="--", alpha=0.3) canvas.draw() -def update_panel(): - """Main polling loop that consumes Serial_decoder outputs (unchanged structure).""" +# Now that update_plot exists, fix the checkbox command to point to it +for chk_var, child in zip(check_vars, check_frame.winfo_children()): + if isinstance(child, ttk.Checkbutton): + child.config(command=update_plot) +def update_panel(): + """Main polling loop that consumes Serial_decoder outputs.""" values: SD.ReturnDecoder = SD.GetReturn(0.1) # non-blocking read with timeout if values is None: root.after(10, update_panel) return # Stop if input thread died - if tasks[1].is_alive() == False: + if tasks[1].is_alive() is False: root.quit() match values.name: