Compare commits

..

No commits in common. "a96344f6b9aa901ccd95201fe0baa2c36b6a1a28" and "bb8420553107161ae70b9359271cc13457454678" have entirely different histories.

7 changed files with 75 additions and 252 deletions

View File

@ -166,52 +166,6 @@ Small PCBs with LEDs, resistors, and mounting holes for housing indicators.
# Software/Firmware # Software/Firmware
Python code for operating the extension PCBs (e.g., GPIO pins, shift registers, ADC, and bus communication). This section is a work in progress (WIP), and example scripts will be added as development continues. Python code for operating the extension PCBs (e.g., GPIO pins, shift registers, ADC, and bus communication). This section is a work in progress (WIP), and example scripts will be added as development continues.
# ======== WIP ========
# Python scripting
The pin assignment to the actual terminals are defined / mapped in
`rpi-scripts\interface_board_pins.py` so it should be included in any custom script created to easily reference the terminal numbers printed onto the project housing in the code. See examples in `rpi-scripts\examples`
TODO: make those files link to file but with text still the file path
# Usage Raspberry-PI
## Connectivity
### Establish LAN Connection (ethernet)
configure in command.txt in boot partition?
### Establish WIFI Connection
```bash
s nmcli device wifi connect <SSID> password <password>
# replace <SSID> and <password> with actual wifi
```
### Connect via ssh
```powershell
ssh pi@192.168.1.100
```
### Connect via rdp
Open mstsc from windows start menu, and enter ip, user root and password.
Or use powershell:
```powershell
mstsc /v:192.168.1.100:3389
```
Note: currently only user `root` works (using user pi results in blackscreen)
### Mount filesystem in windows
You can mount /home/pi folder in windows natively using samba
Run in powershell to mount it as network device X (replace ip with the current ip of rpi)
Find out ip with running `ip a` on the raspberry
```powershell
net use X: \\192.168.1.100\pi /user:pi
```
## Scripts
### Autostart GUI
```bash
git/rpi-interface-board/rpi-scripts/gui% s cp gui-start.service /etc/systemd/system/
```
# ======== END WIP ========
--- ---

View File

@ -1,14 +0,0 @@
[Unit]
Description=Start Python GUI on boot
After=graphical.target
[Service]
Environment=DISPLAY=:0
ExecStart=/bin/python ./main.py
WorkingDirectory=/home/pi/git/rpi-interface-board/rpi-scripts/gui
User=pi
Group=pi
Restart=always
[Install]
WantedBy=graphical.target

View File

@ -1,9 +1,7 @@
import os import os
import sys import sys
import time
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from tkinter import messagebox
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
# Add the parent directory to the module search path # Add the parent directory to the module search path
@ -12,12 +10,8 @@ from interface_board_libs.adc_mcp3208 import MCP3208
from interface_board_libs.shift_register import ShiftRegister from interface_board_libs.shift_register import ShiftRegister
from interface_board_pins import * # Import pin assignments from interface_board_pins import * # Import pin assignments
from tab_control import create_control_tab from tab_control import create_control_tab
from tab_control import set_updating_enabled as tab_control__set_updating_enabled
from tab_adc_plot import create_adc_plot_tab from tab_adc_plot import create_adc_plot_tab
from tab_adc_plot import set_updating_enabled as tab_analog_plot__set_updating_enabled
from tab_digital_plot import create_digital_plot_tab from tab_digital_plot import create_digital_plot_tab
from tab_digital_plot import set_updating_enabled as tab_digital_plot__set_updating_enabled
from tab_exit import create_exit_tab
# Initialize ADC & Shift Register # Initialize ADC & Shift Register
adc = MCP3208() adc = MCP3208()
@ -44,33 +38,10 @@ root.configure(bg="black")
notebook = ttk.Notebook(root) notebook = ttk.Notebook(root)
notebook.pack(expand=True, fill="both") notebook.pack(expand=True, fill="both")
# Track active tab
def on_tab_change(event):
active_tab = event.widget.tab(event.widget.index("current"), "text")
print (f"INFO: switched to tab {active_tab}")
match active_tab:
case "ADC Plot":
tab_control__set_updating_enabled(False)
tab_analog_plot__set_updating_enabled(True)
tab_digital_plot__set_updating_enabled(False)
case "Digital Inputs":
tab_control__set_updating_enabled(False)
tab_analog_plot__set_updating_enabled(False)
tab_digital_plot__set_updating_enabled(True)
case "Controls":
tab_control__set_updating_enabled(True)
tab_analog_plot__set_updating_enabled(False)
tab_digital_plot__set_updating_enabled(False)
case _:
print(f"unhandled change to tab {active_tab}")
notebook.bind("<<NotebookTabChanged>>", on_tab_change)
# Add tabs # Add tabs
create_control_tab(notebook, adc, shift_reg, pwm1, pwm2) create_control_tab(notebook, adc, shift_reg, pwm1, pwm2)
create_adc_plot_tab(notebook, adc) create_adc_plot_tab(notebook, adc)
create_digital_plot_tab(notebook) create_digital_plot_tab(notebook)
create_exit_tab(notebook, root, pwm1, pwm2)
# Run GUI # Run GUI
root.mainloop() root.mainloop()

View File

@ -4,14 +4,7 @@ from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import time import time
ADC_PLOT_UPDATE_INTERVAL = 50 # 20 FPS, smooth and efficient ADC_PLOT_UPDATE_INTERVAL = 100
NOT_ACTIVE_CHECK_INTERVAL = 2000
MAX_HISTORY = 100
updating_enabled = False
adc_channels = list(range(8))
data = {ch: [0] * MAX_HISTORY for ch in adc_channels} # Preallocate lists
time_data = list(range(-MAX_HISTORY, 0)) # Simulated time axis
def create_adc_plot_tab(notebook, adc): def create_adc_plot_tab(notebook, adc):
frame = ttk.Frame(notebook) frame = ttk.Frame(notebook)
@ -19,34 +12,41 @@ def create_adc_plot_tab(notebook, adc):
figure = Figure(figsize=(8, 5), dpi=100) figure = Figure(figsize=(8, 5), dpi=100)
ax = figure.add_subplot(1, 1, 1) 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) ax.set_ylim(0, 12)
ax.set_xlim(-MAX_HISTORY, 0) # Keep time axis fixed
canvas = FigureCanvasTkAgg(figure, master=frame) canvas = FigureCanvasTkAgg(figure, master=frame)
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Initialize lines for fast updates adc_channels = list(range(8))
lines = {ch: ax.plot(time_data, data[ch], label=f"ADC {ch+1}")[0] for ch in adc_channels} data = {ch: [] for ch in adc_channels}
time_data = []
def update_plot(): def update_plot():
if not updating_enabled: current_time = time.time()
frame.after(NOT_ACTIVE_CHECK_INTERVAL, update_plot) if len(time_data) > 50:
return for ch in adc_channels:
data[ch].pop(0)
time_data.pop(0)
# Shift existing data left time_data.append(current_time)
for ch in adc_channels: for ch in adc_channels:
data[ch].pop(0) voltage = round(adc.read(ch) * 12 / 4095, 2)
data[ch].append(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)
# Update only the y-data for efficiency
for ch in adc_channels: for ch in adc_channels:
lines[ch].set_ydata(data[ch]) ax.plot(time_data, data[ch], label=f"ADC {ch+1}")
canvas.draw_idle() # Efficient redraw ax.legend(loc="upper right")
canvas.draw()
frame.after(ADC_PLOT_UPDATE_INTERVAL, update_plot) frame.after(ADC_PLOT_UPDATE_INTERVAL, update_plot)
update_plot() update_plot()
def set_updating_enabled(is_active):
global updating_enabled
updating_enabled = is_active
print(f"adc_plot tab: set updating_enabled to {updating_enabled}")

View File

@ -8,12 +8,6 @@ import RPi.GPIO as GPIO
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from interface_board_pins import * # Import pin assignments from interface_board_pins import * # Import pin assignments
updating_enabled = False
ADC_VALUES_UPDATE_INTERVAL = 500
DIGITAL_INPUTS_UPDATE_INTERVAL = 200
NOT_ACTIVE_CHECK_INTERVAL = 2000
def create_control_tab(notebook, adc, shift_reg, pwm1, pwm2): def create_control_tab(notebook, adc, shift_reg, pwm1, pwm2):
frame = ttk.Frame(notebook) frame = ttk.Frame(notebook)
notebook.add(frame, text="Controls") notebook.add(frame, text="Controls")
@ -22,101 +16,47 @@ def create_control_tab(notebook, adc, shift_reg, pwm1, pwm2):
digital_output_states = [tk.BooleanVar(value=False) 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)] adc_values = [tk.StringVar(value="0.00V") for _ in range(8)]
pwm_values = [tk.IntVar(value=0), tk.IntVar(value=0)] pwm_values = [tk.IntVar(value=0), tk.IntVar(value=0)]
output_buttons = {} # Store button references to change colors
def update_inputs(): def update_inputs():
if not updating_enabled:
frame.after(NOT_ACTIVE_CHECK_INTERVAL, update_inputs)
return
for i, pin in enumerate(GPIO_DIGITAL_INPUTS.values()): for i, pin in enumerate(GPIO_DIGITAL_INPUTS.values()):
digital_input_states[i].set("HIGH" if GPIO.input(pin) else "LOW") digital_input_states[i].set("HIGH" if GPIO.input(pin) else "LOW")
frame.after(DIGITAL_INPUTS_UPDATE_INTERVAL, update_inputs) frame.after(500, update_inputs)
def update_adc(): def update_adc():
if not updating_enabled:
frame.after(NOT_ACTIVE_CHECK_INTERVAL, update_adc)
return
for i, adc_channel in enumerate(ADC_CHANNELS.values()): for i, adc_channel in enumerate(ADC_CHANNELS.values()):
value = adc.read(adc_channel) value = adc.read(adc_channel)
adc_values[i].set(f"{round(value * 12 / 4095, 2)}V") adc_values[i].set(f"{round(value * 12 / 4095, 2)}V")
frame.after(ADC_VALUES_UPDATE_INTERVAL, update_adc) frame.after(1000, update_adc)
def toggle_output(index): def toggle_output(index):
""" Toggle shift register output and update button color. """ shift_reg.set_pin(index, digital_output_states[index].get())
new_state = not digital_output_states[index].get()
digital_output_states[index].set(new_state)
shift_reg.set_pin(index, new_state)
if index in output_buttons: def update_pwm(channel, value):
for btn in output_buttons[index]: duty_cycle = int(float(value))
btn.configure(bg="green" if new_state else "red", activebackground="green" if new_state else "red") if channel == 0:
pwm1.ChangeDutyCycle(duty_cycle)
else:
pwm2.ChangeDutyCycle(duty_cycle)
# UI Layout
style = ttk.Style() style = ttk.Style()
style.configure("TScale", thickness=60) style.configure("TScale", thickness=30) # Increases slider thickness
style.configure("TButton", font=("Arial", 18), padding=5)
control_frame = ttk.Frame(frame, padding=40) control_frame = ttk.Frame(frame, padding=30)
control_frame.grid(row=0, column=0, sticky="nsew") control_frame.pack(expand=True, fill="both")
frame.grid_rowconfigure(0, weight=1)
frame.grid_rowconfigure(1, weight=0)
frame.grid_columnconfigure(0, weight=1)
for i in range(8): for i in range(8):
ttk.Label(control_frame, text=f"ADC {i}:", font=("Arial", 18)).grid(row=i, column=0, sticky="e") ttk.Label(control_frame, text=f"ADC {i+1}:", font=("Arial", 14)).grid(row=i, column=0, sticky="e")
ttk.Label(control_frame, textvariable=adc_values[i], width=10, font=("Arial", 18)).grid(row=i, column=1, sticky="w") ttk.Label(control_frame, textvariable=adc_values[i], width=10, font=("Arial", 14)).grid(row=i, column=1, sticky="w")
ttk.Label(control_frame, text=f"IN {i}:", font=("Arial", 18)).grid(row=i, column=2, sticky="e", padx=20) ttk.Label(control_frame, text=f"IN {i+1}:", font=("Arial", 14)).grid(row=i, column=2, sticky="e")
ttk.Label(control_frame, textvariable=digital_input_states[i], width=6, font=("Arial", 18)).grid(row=i, column=3, sticky="w") ttk.Label(control_frame, textvariable=digital_input_states[i], width=6, font=("Arial", 14)).grid(row=i, column=3, sticky="w")
btn = ttk.Checkbutton(control_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")
btn = tk.Button(control_frame, text=f"OUT {i}", font=("Arial", 16), width=10, bg="red", activebackground="red", command=lambda i=i: toggle_output(i)) for i in range(2):
btn.grid(row=i, column=4, sticky="w", padx=20) ttk.Label(control_frame, text=f"PWM{i+1}:", font=("Arial", 14)).grid(row=i, column=5, sticky="e")
slider = ttk.Scale(control_frame, from_=0, to=100, orient="horizontal", length=400, variable=pwm_values[i], command=lambda val, i=i: update_pwm(i, val), style="TScale")
output_buttons.setdefault(i, []).append(btn) slider.grid(row=i, column=6, sticky="w", pady=10) # Added spacing with `pady=10`
if i == 0:
buzzer_btn = tk.Button(control_frame, text="BUZZER", font=("Arial", 16), width=10, bg="red", activebackground="red", command=lambda: toggle_output(0))
buzzer_btn.grid(row=i, column=5, padx=20)
output_buttons[0].append(buzzer_btn)
if i == 1:
relay2_btn = tk.Button(control_frame, text="RELAY2", font=("Arial", 16), width=10, bg="red", activebackground="red", command=lambda: toggle_output(1))
relay2_btn.grid(row=i, column=5, padx=20)
output_buttons[1].append(relay2_btn)
if i == 2:
relay1_btn = tk.Button(control_frame, text="RELAY1", font=("Arial", 16), width=10, bg="red", activebackground="red", command=lambda: toggle_output(2))
relay1_btn.grid(row=i, column=5, padx=20)
output_buttons[2].append(relay1_btn)
pwm_frame = ttk.Frame(frame, padding=20)
pwm_frame.grid(row=1, column=0, sticky="ew")
pwm_frame.columnconfigure(0, weight=0)
pwm_frame.columnconfigure(1, weight=1)
pwm_frame.columnconfigure(2, weight=0)
pwm1_label = ttk.Label(pwm_frame, text="PWM1:", font=("Arial", 18))
pwm1_label.grid(row=0, column=0, padx=20, sticky="e")
pwm1_slider = ttk.Scale(
pwm_frame, from_=0, to=100, orient="horizontal", length=400,
variable=pwm_values[0], command=lambda val: pwm1.ChangeDutyCycle(int(float(val)))
)
pwm1_slider.grid(row=0, column=1, columnspan=2, sticky="we", padx=10)
pwm2_label = ttk.Label(pwm_frame, text="PWM2:", font=("Arial", 18))
pwm2_label.grid(row=1, column=0, padx=20, sticky="e")
pwm2_slider = ttk.Scale(
pwm_frame, from_=0, to=100, orient="horizontal", length=400,
variable=pwm_values[1], command=lambda val: pwm2.ChangeDutyCycle(int(float(val)))
)
pwm2_slider.grid(row=1, column=1, columnspan=2, sticky="we", padx=10)
update_inputs() update_inputs()
update_adc() update_adc()
def set_updating_enabled(is_active):
global updating_enabled
updating_enabled = is_active
print(f"control tab: set updating_enabled to {updating_enabled}")

View File

@ -1,6 +1,5 @@
import sys import sys
import os import os
import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
@ -11,53 +10,47 @@ import time
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from interface_board_pins import * # Import pin assignments from interface_board_pins import * # Import pin assignments
# Adjusted Constants
DIGITAL_PLOT_UPDATE_INTERVAL = 50 # 50ms (20 FPS) is smooth enough
NOT_ACTIVE_CHECK_INTERVAL = 2000 # Check inactive tabs less frequently
MAX_HISTORY = 100 # Keep last 100 values for scrolling
updating_enabled = False # Track if the tab is active
input_channels = list(range(8))
data = {ch: [0] * MAX_HISTORY for ch in input_channels} # Preallocate data
time_data = list(range(-MAX_HISTORY, 0)) # Simulated time axis
def create_digital_plot_tab(notebook): def create_digital_plot_tab(notebook):
frame = ttk.Frame(notebook) frame = ttk.Frame(notebook)
notebook.add(frame, text="Digital Inputs") notebook.add(frame, text="Digital Inputs")
figure = Figure(figsize=(8, 5), dpi=100) figure = Figure(figsize=(8, 5), dpi=100)
ax = figure.add_subplot(1, 1, 1) ax = figure.add_subplot(1, 1, 1)
ax.set_title("Digital Input States Over Time")
ax.set_xlabel("Time (s)")
ax.set_ylabel("State (0=LOW, 1=HIGH)")
ax.set_ylim(-0.2, 1.2) ax.set_ylim(-0.2, 1.2)
ax.set_xlim(-MAX_HISTORY, 0) # Keep time axis fixed
canvas = FigureCanvasTkAgg(figure, master=frame) canvas = FigureCanvasTkAgg(figure, master=frame)
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Initialize lines for fast updates input_channels = list(range(8))
lines = {ch: ax.step(time_data, data[ch], where="post")[0] for ch in input_channels} data = {ch: [] for ch in input_channels}
time_data = []
def update_plot(): def update_plot():
if not updating_enabled: current_time = time.time()
frame.after(NOT_ACTIVE_CHECK_INTERVAL, update_plot) if len(time_data) > 50:
return for ch in input_channels:
data[ch].pop(0)
time_data.pop(0)
# Shift existing data left time_data.append(current_time)
for ch in input_channels: for ch in input_channels:
data[ch].pop(0) state = GPIO.input(GPIO_DIGITAL_INPUTS[ch])
data[ch].append(GPIO.input(GPIO_DIGITAL_INPUTS[ch])) data[ch].append(state)
ax.clear()
ax.set_title("Digital Input States Over Time")
ax.set_xlabel("Time (s)")
ax.set_ylabel("State (0=LOW, 1=HIGH)")
ax.set_ylim(-0.2, 1.2)
# Update only the y-data for efficiency
for ch in input_channels: for ch in input_channels:
lines[ch].set_ydata(data[ch]) ax.step(time_data, data[ch], label=f"IN {ch+1}", where="post")
canvas.draw_idle() # More efficient than draw() ax.legend(loc="upper right")
frame.after(DIGITAL_PLOT_UPDATE_INTERVAL, update_plot) canvas.draw()
frame.after(500, update_plot)
update_plot() update_plot()
def set_updating_enabled(is_active):
global updating_enabled
updating_enabled = is_active
print(f"digital_plot tab: set updating_enabled to {updating_enabled}")

View File

@ -1,21 +0,0 @@
import tkinter as tk
from tkinter import ttk
def create_exit_tab(notebook, root, pwm1, pwm2):
frame = ttk.Frame(notebook)
notebook.add(frame, text="EXIT")
ttk.Label(frame, text="Exit the GUI", font=("Arial", 20)).pack(pady=20)
def exit_program():
pwm1.stop()
pwm2.stop()
root.quit()
root.destroy()
exit_button = ttk.Button(frame, text="Exit Application", command=exit_program, style="TButton")
exit_button.pack(pady=20)
style = ttk.Style()
style.configure("TButton", font=("Arial", 18), padding=10)