Compare commits
No commits in common. "a96344f6b9aa901ccd95201fe0baa2c36b6a1a28" and "bb8420553107161ae70b9359271cc13457454678" have entirely different histories.
a96344f6b9
...
bb84205531
46
README.md
46
README.md
@ -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 ========
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -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
|
|
@ -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()
|
||||||
|
@ -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}")
|
|
||||||
|
@ -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}")
|
|
||||||
|
@ -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}")
|
|
||||||
|
|
||||||
|
@ -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)
|
|
Loading…
x
Reference in New Issue
Block a user