Thanks for the tips, I am a newbie in python, but figured out how to make the GUI aligned properly by creating frames for the different sections of the scheduler. The program seems to be working great and will test full functionality over the next few days.The column width is determined by the longest value, in this case "Shelf 1", "Shelf 2" and so on. The rest of the columns is than distributed over the space that's left over. You can put the Shelves in another frame (will not be aligned anymore with the hour section), or use shorter names like S_1, S_2, or use fixed widths for your grid but then longer text may be chopped.I still have problem figuring out why the checkboxes in the scheduler are not spaced evenly.
I need to figure out how to have the hours on the schedule to show hours in AM and PM instead of 0-12 values...
Here is how the new GUI looks:And here is the code for it:
Code:
import tkinter as tkimport RPi.GPIO as GPIOimport threadingimport timefrom datetime import datetimeimport queuefrom apscheduler.schedulers.background import BackgroundSchedulerimport logging# Enable detailed logginglogging.basicConfig(level=logging.DEBUG, format='%(message)s')# GPIO SetupGPIO.setmode(GPIO.BCM)GPIO.setwarnings(False)# Define GPIO pinspins = { "LED Shelf #1 & 2": 17, "LED Shelf #3 & 4": 18, "Water Sprayer": 25, "Fans": 5, "Water Shelf 1": 27, "Water Shelf 2": 22, "Water Shelf 3": 23, "Water Shelf 4": 24}# Set up GPIO as outputfor pin in pins.values(): GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.HIGH) # OFF state for relays# Initialize GUIwin = tk.Tk()win.title("Microgreens Farm Controller")win.geometry("800x480")# Default colors for OFF stateDEFAULT_COLORS = { 17: "#ffeb3b", 18: "#ffeb3b", 5: "#FFA500", 25: "#87CEEB",}# Checkbox control variablesshelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}# Timer valuestimer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}def start_timer(pin, button, duration, label): time.sleep(duration) GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg="#87CEFA")def toggle_water_shelf(pin, button, label): print(f"Toggling water shelf {label} with pin {pin}") if shelf_enabled_vars[pin].get(): if GPIO.input(pin): GPIO.output(pin, GPIO.LOW) button.config(text=f"{label} ON", bg="#2ecc71") print(f"Water shelf {label} turned ON") try: duration = int(timer_values[pin].get()) except ValueError: duration = 10 threading.Thread(target=start_timer, args=(pin, button, duration, label), daemon=True).start() logging.debug(f"Water Shelf {label} turned ON for {duration} seconds.") else: GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg="#87CEFA") logging.debug(f"Water Shelf {label} turned OFF.") else: logging.debug(f"Watering not enabled for {label}.")# Scheduler variablesday_vars = {day: tk.IntVar() for day in ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]}hour_vars = {hour: tk.IntVar() for hour in range(24)}schedule_shelf_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}# Light cycle settingsled_on_durations = {pin: tk.StringVar(value="16") for pin in [17, 18]}led_off_durations = {pin: tk.StringVar(value="8") for pin in [17, 18]}# Scheduler setupscheduler = BackgroundScheduler()scheduler.start()def apply_led_schedule(pin, button, label): """Applies the light schedule for the given LED.""" try: on_duration = int(led_on_durations[pin].get()) off_duration = int(led_off_durations[pin].get()) except ValueError: logging.error("Invalid duration input. Using default 12 ON / 6 OFF.") on_duration, off_duration = 12, 6 def turn_on(): if GPIO.input(pin) == 0: # Only turn ON if it was previously ON GPIO.output(pin, GPIO.LOW) button.config(text=f"{label} ON", bg="#2ecc71") logging.info(f"{label} turned ON") scheduler.add_job(turn_off, 'interval', hours=on_duration, id=f"led_off_{pin}") def turn_off(): GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg=DEFAULT_COLORS[pin]) logging.info(f"{label} turned OFF") scheduler.add_job(turn_on, 'interval', hours=off_duration, id=f"led_on_{pin}") turn_on()def toggle_device(pin, button, label): """Manually toggles LED state and cancels the schedule if turned OFF manually.""" if GPIO.input(pin): GPIO.output(pin, GPIO.LOW) button.config(text=f"{label} ON", bg="#2ecc71") else: GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg=DEFAULT_COLORS.get(pin, "#ffeb3b")) scheduler.remove_job(f"led_off_{pin}", jobstore=None) scheduler.remove_job(f"led_on_{pin}", jobstore=None)# Main layoutframe = tk.Frame(win)frame.pack(expand=True, fill="both", pady=5)left_frame = tk.Frame(frame)left_frame.grid(row=0, column=0, padx=18, sticky="n")water_shelf_frame = tk.Frame(frame)water_shelf_frame.grid(row=0, column=1, padx=5, sticky="n")def create_toggle_button(label, pin, row): button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS.get(pin, "#ffeb3b"), width=16, height=2) button.config(command=lambda: toggle_device(pin, button, label)) button.grid(row=row, column=0, padx=2, pady=2, sticky="w") return buttoncontrols = [ ("LED Shelf #1 & 2", 17), ("LED Shelf #3 & 4", 18), ("Water Sprayer", 25), ("Fans", 5),]for i, (label, pin) in enumerate(controls): create_toggle_button(label, pin, i)def create_water_shelf_button(label, pin, row): button = tk.Button(water_shelf_frame, text=label + " OFF", bg="#87CEFA", width=16, height=2) button.config(command=lambda: toggle_water_shelf(pin, button, label)) button.grid(row=row, column=0, padx=2, pady=2, sticky="w") shelf_checkbox = tk.Checkbutton(water_shelf_frame, text="Enable Watering", variable=shelf_enabled_vars[pin]) shelf_checkbox.grid(row=row, column=1, padx=2, pady=5, sticky="w") timer_entry = tk.Entry(water_shelf_frame, textvariable=timer_values[pin], width=5) timer_entry.grid(row=row, column=2, padx=2, pady=5, sticky="e") timer_values[pin].set("10") timer_label = tk.Label(water_shelf_frame, text="Seconds") timer_label.grid(row=row, column=3, padx=2, pady=2, sticky="e") return buttonwater_shelf_buttons = {}for i in range(1, 5): water_shelf_buttons[pins[f"Water Shelf {i}"]] = create_water_shelf_button(f"Water Shelf {i}", pins[f"Water Shelf {i}"], i)# Scheduler Framescheduler_frame = tk.Frame(win)scheduler_frame.pack(side="top", pady=2)# Scheduler (Shelves Section)shelves_frame = tk.Frame(win)shelves_frame.pack(side="top", pady=1)# Scheduler (Days Section)days_frame = tk.Frame(win)days_frame.pack(side="top", pady=1)# Scheduler (Hours Section)hours_frame = tk.Frame(win)hours_frame.pack(side="top", pady=1)# Scheduler (Set Schedule Button)setButton_frame = tk.Frame(win)setButton_frame.pack(side="top", pady=1)# Function to toggle devicesdef toggle_device(pin, button, label): if GPIO.input(pin): GPIO.output(pin, GPIO.LOW) button.config(text=f"{label} ON", bg="#2ecc71") else: GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg=DEFAULT_COLORS.get(pin, "#ffeb3b"))def schedule_watering(): selected_days = [day for day, var in day_vars.items() if var.get() == 1] selected_hours = [hour for hour, var in hour_vars.items() if var.get() == 1] selected_shelves = [shelf for shelf, var in schedule_shelf_vars.items() if var.get() == 1] # Create popup window to confirm schedule schedule_popup = tk.Toplevel(win, padx=20, pady=10) schedule_popup.title("Schedule Confirmation") tk.Label(schedule_popup, text="Scheduled Confirmation", font=("Arial", 14), padx=30, pady=5).pack() tk.Label( schedule_popup, text=f"Scheduled: {', '.join([f'Water Shelf {list(pins.keys())[list(pins.values()).index(s)].split()[-1]} ({timer_values[s].get()}s)' for s in selected_shelves])}").pack() tk.Label(schedule_popup, text=f"Scheduled Days: {', '.join(selected_days)}").pack() tk.Label(schedule_popup, text=f"Scheduled Hours: {', '.join(map(str, selected_hours))}").pack() for day in selected_days: for hour in selected_hours: for shelf in selected_shelves: shelf_label = f"Water Shelf {list(pins.values()).index(shelf)}" # Get correct label scheduler.add_job( lambda s=shelf, l=shelf_label: toggle_water_shelf(s, water_shelf_buttons[s], l), 'cron', day_of_week=day.lower(), hour=hour, minute=0 )tk.Label(scheduler_frame, text="Watering Schedule", font=("Arial", 12, "bold")).grid(row=0, column=2, columnspan=8)# Days selectiontk.Label(days_frame, text="Days:").grid(row=1, column=0, sticky="n")for i, (day, var) in enumerate(day_vars.items()): tk.Checkbutton(days_frame, text=day, variable=var).grid(row=1, column=i+1, sticky="w")# Hours selectiontk.Label(hours_frame, text="Hours:").grid(row=2, column=0, sticky="w")for i, (hour, var) in enumerate(hour_vars.items()): tk.Checkbutton(hours_frame, text=str(hour), variable=var).grid(row=2 + (i // 12), column=(i % 12) + 1, sticky="w")# Shelves selectiontk.Label(shelves_frame, text="Shelves:").grid(row=3, column=0, sticky="w")for i, (shelf, var) in enumerate(schedule_shelf_vars.items()): tk.Checkbutton(shelves_frame, text=f"Shelf {i+1}", variable=var).grid(row=3, column=i+1, sticky="w")# Schedule Buttontk.Button(setButton_frame, text="Set Schedule", bg="green", fg="white", command=schedule_watering).grid(row=1, column=2, columnspan=8, pady=5)def enqueue_event(shelf): logging.debug(f"Enqueueing event for {shelf}") print(f"Watering {shelf} for the scheduled duration.") # LED Controlsfor i, (label, pin) in enumerate([("LED Shelf #1 & 2", 17), ("LED Shelf #3 & 4", 18)]): button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS[pin], width=16, height=2) button.config(command=lambda p=pin, b=button, l=label: toggle_device(p, b, l)) button.grid(row=i, column=0, padx=2, pady=2, sticky="w") tk.Label(left_frame, text="ON").grid(row=i, column=1) tk.Entry(left_frame, textvariable=led_on_durations[pin], width=2).grid(row=i, column=2) tk.Label(left_frame, text="OFF").grid(row=i, column=3) tk.Entry(left_frame, textvariable=led_off_durations[pin], width=2).grid(row=i, column=4) tk.Label(left_frame, text="Hrs").grid(row=i, column=7) tk.Button(left_frame, text="Set", command=lambda p=pin, b=button, l=label: apply_led_schedule(p, b, l)).grid(row=i, column=5, padx=2)# Exit program functiondef stopProgram(): scheduler.shutdown() GPIO.cleanup() win.quit()tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=15, height=2, command=stopProgram).pack(side="bottom", pady=1)win.mainloop()Statistics: Posted by papagino — Tue Apr 01, 2025 3:33 pm