From 48bfbb98ef02417d04254cab18914408d15511ae Mon Sep 17 00:00:00 2001 From: jonny Date: Thu, 30 Jan 2025 11:57:05 +0100 Subject: [PATCH] Add gui for control and visualizing inputs (basic functionality works) --- rpi-scripts/gui/gui_helpers.py | 49 +++++++++++++++ rpi-scripts/gui/main.py | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100755 rpi-scripts/gui/gui_helpers.py create mode 100755 rpi-scripts/gui/main.py diff --git a/rpi-scripts/gui/gui_helpers.py b/rpi-scripts/gui/gui_helpers.py new file mode 100755 index 0000000..5f63315 --- /dev/null +++ b/rpi-scripts/gui/gui_helpers.py @@ -0,0 +1,49 @@ +import tkinter as tk +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import time + +def create_adc_plot(frame, adc): + """ + Creates a live-updating ADC plot in the given Tkinter frame. + """ + figure = Figure(figsize=(6, 4), dpi=100) + ax = figure.add_subplot(1, 1, 1) + ax.set_title("ADC Readings Over Time") + ax.set_xlabel("Time (s)") + ax.set_ylabel("Voltage (V)") + ax.set_ylim(0, 12) + + canvas = FigureCanvasTkAgg(figure, master=frame) + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + adc_channels = list(range(8)) + data = {ch: [] for ch in adc_channels} + time_data = [] + + def update_plot(): + current_time = time.time() + if len(time_data) > 50: + for ch in adc_channels: + data[ch].pop(0) + time_data.pop(0) + + time_data.append(current_time) + for ch in adc_channels: + voltage = round(adc.read(ch) * 12 / 4095, 2) + data[ch].append(voltage) + + ax.clear() + ax.set_title("ADC Readings Over Time") + ax.set_xlabel("Time (s)") + ax.set_ylabel("Voltage (V)") + ax.set_ylim(0, 12) + + for ch in adc_channels: + ax.plot(time_data, data[ch], label=f"ADC {ch+1}") + + ax.legend(loc="upper right") + canvas.draw() + frame.after(1000, update_plot) + + update_plot() diff --git a/rpi-scripts/gui/main.py b/rpi-scripts/gui/main.py new file mode 100755 index 0000000..7ee30f3 --- /dev/null +++ b/rpi-scripts/gui/main.py @@ -0,0 +1,105 @@ +import os +import sys +import time +import tkinter as tk +from tkinter import ttk +from tkinter import messagebox +import RPi.GPIO as GPIO +# Add the parent directory to the module search path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +from interface_board_libs.adc_mcp3208 import MCP3208 +from interface_board_libs.shift_register import ShiftRegister +from interface_board_pins import * # Import pin assignments +from gui_helpers import create_adc_plot + +# Initialize ADC & Shift Register +adc = MCP3208() +shift_reg = ShiftRegister(GPIO_SHIFT_REG_DATA, GPIO_SHIFT_REG_LATCH, GPIO_SHIFT_REG_CLOCK) + +# GPIO Setup +GPIO.setmode(GPIO.BCM) +for pin in GPIO_DIGITAL_INPUTS.values(): + GPIO.setup(pin, GPIO.IN) +GPIO.setup(GPIO_PWM1, GPIO.OUT) +GPIO.setup(GPIO_PWM2, GPIO.OUT) +pwm1 = GPIO.PWM(GPIO_PWM1, 1000) +pwm2 = GPIO.PWM(GPIO_PWM2, 1000) +pwm1.start(0) +pwm2.start(0) + +# Tkinter GUI +root = tk.Tk() +root.title("Raspberry Pi Interface Board") +root.attributes('-fullscreen', True) +root.configure(bg="black") + +# Tabbed Interface +notebook = ttk.Notebook(root) +main_frame = ttk.Frame(notebook) +plot_frame = ttk.Frame(notebook) +notebook.add(main_frame, text="Controls") +notebook.add(plot_frame, text="ADC Plot") +notebook.pack(expand=True, fill="both") + +# Layout Variables +digital_input_states = [tk.StringVar(value="LOW") for _ in range(8)] +digital_output_states = [tk.BooleanVar(value=False) for _ in range(8)] +adc_values = [tk.StringVar(value="0.00V") for _ in range(8)] +pwm_values = [tk.IntVar(value=0), tk.IntVar(value=0)] + +def update_inputs(): + for i, pin in enumerate(GPIO_DIGITAL_INPUTS.values()): + digital_input_states[i].set("HIGH" if GPIO.input(pin) else "LOW") + root.after(500, update_inputs) + +def update_adc(): + for i, adc_channel in enumerate(ADC_CHANNELS.values()): + value = adc.read(adc_channel) + adc_values[i].set(f"{round(value * 12 / 4095, 2)}V") + root.after(1000, update_adc) + +def toggle_output(index): + shift_reg.set_pin(index, digital_output_states[index].get()) + +def update_pwm(channel, value): + duty_cycle = int(float(value)) + if channel == 0: + pwm1.ChangeDutyCycle(duty_cycle) + else: + pwm2.ChangeDutyCycle(duty_cycle) + +def exit_program(): + pwm1.stop() + pwm2.stop() + GPIO.cleanup() + root.quit() + +# Create UI Elements in Main Frame +frame = ttk.Frame(main_frame, padding=20) +frame.pack(expand=True, fill="both") + +for i in range(8): + ttk.Label(frame, text=f"ADC {i+1}:").grid(row=i, column=0, sticky="e") + ttk.Label(frame, textvariable=adc_values[i], width=10).grid(row=i, column=1, sticky="w") + ttk.Label(frame, text=f"IN {i+1}:").grid(row=i, column=2, sticky="e") + ttk.Label(frame, textvariable=digital_input_states[i], width=6).grid(row=i, column=3, sticky="w") + btn = ttk.Checkbutton(frame, text=f"OUT {i+1}", variable=digital_output_states[i], command=lambda i=i: toggle_output(i)) + btn.grid(row=i, column=4, sticky="w") + +for i in range(2): + ttk.Label(frame, text=f"PWM{i+1}").grid(row=i, column=5, sticky="e") + slider = ttk.Scale(frame, from_=0, to=100, orient="horizontal", length=300, variable=pwm_values[i], command=lambda val, i=i: update_pwm(i, val)) + slider.grid(row=i, column=6, sticky="w") + +exit_button = ttk.Button(root, text="Exit", command=exit_program) +exit_button.place(relx=0.9, rely=0.9, anchor="center") + +# Create ADC Plot in Second Tab +create_adc_plot(plot_frame, adc) + +# Start Updates +update_inputs() +update_adc() + +# Run GUI +root.mainloop()