Tested your code, it seems to partially work, but would not activate all shelves even if they were all selected and GPIO pin were not triggered.Had some time today and prepared a version which perhaps does something useful.
- added a hardware abstraction class 'Hardware' which removes the RPI.GPIO commands throughout the code
- separated tkinter timers and threading Timers for the Watering timeout
- removed the Sun, Mon, Tue numbering problem
- some cleanup throughout the code. The dictionaries for the variables are replaced by array for simplicity.
For a prefect solution
- lot of comments are missing
- pylint gives poor result 3.02/10
- handling of the timers in case of multiple events should be improved
- logging of major events
...
Anyhow, I have updated the code and the GUI but it is still not activating watering at set days/hours even tho terminal says it was executed successfully at the set time. I even tried to select all days, hours and shelves in the scheduler to see if it would show that maybe there was a problem with apscheduler triggers mismatch, but no watering occurred on the hours...
I have added the section for Scheduler Callback you have on the bottom of your code and this is showing a "KeyError: 4" on terminal at time of schedule. Here is my current code and screenshots of GUI and results:
This is the set GUI screenThis is what I get on terminal when clicking on "Print Schedule" button
Looking for jobs to run
Added job "<lambda>" to job store "default"
Next wakeup is due at 2025-03-28 13:00:00-03:00 (in 1788.707396 seconds)
Added job "<lambda>" to job store "default"
Looking for jobs to run
Total scheduled jobs: 672
Next wakeup is due at 2025-03-28 13:00:00-03:00 (in 1788.704183 seconds)
Looking for jobs to run
Next wakeup is due at 2025-03-28 13:00:00-03:00 (in 1788.702987 seconds)
Scheduled: Shelf 1 (10 seconds), Shelf 2 (20 seconds), Shelf 3 (30 seconds), Shelf 4 (40 seconds)
Scheduled Days: Fri
Scheduled Hours: 13:00
And this is what is on the terminal after the schedule event was triggered at the set time:
Looking for jobs to run
Running job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-03-28 13:00:00 ADT)" (scheduled at 2025-03-28 13:00:00-03:00)
Job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" executed successfully
Running job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-03-28 13:00:00 ADT)" (scheduled at 2025-03-28 13:00:00-03:00)
Job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" executed successfully
Running job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" (scheduled at 2025-03-28 13:00:00-03:00)
Running job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" (scheduled at 2025-03-28 13:00:00-03:00)
Job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" executed successfully
Next wakeup is due at 2025-03-28 14:00:00-03:00 (in 3599.998599 seconds)
Job "<lambda> (trigger: cron[day_of_week='4', hour='13'], next run at: 2025-04-04 13:00:00 ADT)" executed successfully
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.11/tkinter/__init__.py", line 1948, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/tkinter/__init__.py", line 861, in callit
func(*args)
File "/home/microgreen/Scripts/Testmod1.py", line 295, in tk_callback
if day_vars[event["day_of_week"]].get() == 1 and \
~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
KeyError: 4
Here is my current code:
Code:
import tkinter as tkimport RPi.GPIO as GPIOimport threadingimport timefrom datetime import datetimeimport queueimport apscheduler.schedulers.backgroundimport logging# Enable detailed logginglogging.basicConfig(level=logging.DEBUG, format='%(message)s')# GPIO SetupGPIO.setmode(GPIO.BCM)GPIO.setwarnings(False)# Define GPIO pinspins = { "LED Top Shelves": 17, "LED Bottom Shelves": 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}.")def toggle_device(pin, button, label): if GPIO.input(pin): GPIO.output(pin, GPIO.LOW) button.config(text=f"{label} ON", bg="#2ecc71") logging.debug(f"{label} turned ON.") else: GPIO.output(pin, GPIO.HIGH) button.config(text=f"{label} OFF", bg=DEFAULT_COLORS.get(pin, "#ffeb3b")) logging.debug(f"{label} turned OFF.")# 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=30, sticky="n")water_shelf_frame = tk.Frame(frame)water_shelf_frame.grid(row=0, column=1, padx=10, sticky="n")def create_toggle_button(label, pin, row): button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS.get(pin, "#ffeb3b"), width=20, height=2) button.config(command=lambda: toggle_device(pin, button, label)) button.grid(row=row, column=0, padx=10, pady=2, sticky="w") return buttoncontrols = [ ("LED Top Shelves", 17), ("LED Bottom Shelves", 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=20, height=2) button.config(command=lambda: toggle_water_shelf(pin, button, label)) button.grid(row=row, column=0, padx=10, 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=10, 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=10, 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=5, 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 setupscheduler = apscheduler.schedulers.background.BackgroundScheduler()scheduler.start()eventqueue = queue.Queue()# --- Create scheduler jobs ---def update_schedule(job_id, schedule_time, task_function, shelf, day_of_week, hour): # Remove the old job (if exists) and add the new one if scheduler.get_job(job_id): logging.debug(f"Removing old job {job_id}") scheduler.remove_job(job_id) logging.debug(f"Adding new job {job_id} with schedule {schedule_time}") scheduler.add_job(task_function, 'cron', id=job_id, **schedule_time, args=[shelf], misfire_grace_time=10, replace_existing=True)def enqueue_event(shelf): logging.debug(f"Watering {shelf} for the scheduled duration.") print(f"Watering {shelf} for the scheduled duration.")job_id = 0for shelf in range(4): for day_of_week in range(7): for hour in range(24): event = {"shelf": shelf, "day_of_week": day_of_week, "hour": hour} scheduler.add_job(lambda e=event: eventqueue.put(e), 'cron', day_of_week=day_of_week, hour=hour, id=f'job_id_{job_id}') job_id += 1logging.debug(f"Total scheduled jobs: {job_id}")if not scheduler.running: scheduler.start()def process_scheduled_event(event): try: logging.debug(f"Simulating button click for shelf {event['shelf']} on day {event['day_of_week']} at {event['hour']} o'clock") # Get the pin number and button for the corresponding shelf shelf_label = f"Water Shelf {event['shelf'] + 1}" pin = pins.get(shelf_label) button = water_shelf_buttons.get(pin) if pin and button and shelf_enabled_vars[pin].get(): logging.debug(f"Triggering button for {shelf_label}") button.invoke() # Simulates a button click else: logging.debug(f"Skipping watering for {shelf_label} (not enabled or button missing).") except Exception as e: logging.error(f"Error processing scheduled event: {event}, Exception: {e}", exc_info=True) # GUI Scheduling sectionschedule_frame = tk.Frame(win)schedule_frame.pack(pady=8)schedule_label = tk.Label(schedule_frame, text="Watering Schedule", font=("Arial", 14, "bold"))schedule_label.grid(row=0, column=0, columnspan=7, pady=1)# Days of the weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]day_vars = {day: tk.IntVar() for day in days}for i, day in enumerate(days): tk.Checkbutton(schedule_frame, text=day, variable=day_vars[day]).grid(row=1, column=i, padx=30, pady=1)# Time selectiontime_frame = tk.Frame(schedule_frame)time_frame.grid(row=2, column=0, columnspan=7, pady=0)tk.Label(time_frame, text="").grid(row=0, column=0)# Define hour_vars before the Checkbuttonshour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day# Create AM hours checkbuttonsfor i, hour in enumerate([f"{i}:00" for i in range(12)]): tk.Checkbutton(time_frame, text=hour, variable=hour_vars[i]).grid(row=0, column=i) # Create PM hours checkbuttons for i, hour in enumerate([f"{i}:00" for i in range(12, 24)]): tk.Checkbutton(time_frame, text=hour, variable=hour_vars[i+12]).grid(row=1, column=i) # Shelf checkboxes in scheduler shelf_schedule_vars = {f"Water Shelf {i}": tk.IntVar() for i in range(1, 5)}for i, shelf in enumerate(shelf_schedule_vars.keys()): tk.Checkbutton(schedule_frame, text=shelf, variable=shelf_schedule_vars[shelf]).grid(row=3, column=i+1, padx=0, pady=5)def print_schedule(): selected_days = [day for day, var in day_vars.items() if var.get()] selected_hours = [f"{i}:00" for i, var in enumerate(hour_vars) if var.get()] selected_shelves = [] for i in range(1, 5): if shelf_schedule_vars[f"Water Shelf {i}"].get() == 1: duration_seconds = int(timer_values[pins[f"Water Shelf {i}"]].get()) selected_shelves.append(f"Shelf {i} ({duration_seconds} seconds)") if selected_days and selected_hours and selected_shelves: confirmation_text = (f"Scheduled: {', '.join(selected_shelves)}\n" f"Scheduled Days: {', '.join(selected_days)}\n" f"Scheduled Hours: {', '.join(selected_hours)}") logging.debug(confirmation_text) # Popup window for confirmation popup = tk.Toplevel(win) popup.title("Schedule Confirmation") tk.Label(popup, text=confirmation_text, padx=20, pady=20).pack() else: logging.debug("No schedule selected.")# Button press logic without delay for immediate actiondef on_print_press(event): print_schedule() # Trigger the schedule print immediately# Update button bindingprint_button = tk.Button(schedule_frame, text="Print Schedule", bg="#16a085", fg="white", width=20, height=1)print_button.grid(row=4, column=0, columnspan=7, pady=0)print_button.bind("<ButtonPress-1>", on_print_press)# Update schedule function using the provided code snippetdef update_schedule(job_id, schedule_time, task_function, shelf, day_of_week, hour): if scheduler.get_job(job_id): logging.debug(f"Removing old job {job_id}") scheduler.remove_job(job_id) logging.debug(f"Adding new job {job_id} with schedule {schedule_time}") scheduler.add_job(task_function, 'cron', id=job_id, **schedule_time, args=[shelf], misfire_grace_time=10, replace_existing=True)def enqueue_event(shelf): logging.debug(f"Enqueueing event for {shelf}") print(f"Watering {shelf} for the scheduled duration.")def print_schedule(): selected_days = [day for day, var in day_vars.items() if var.get()] selected_hours = [f"{i}:00" for i, var in enumerate(hour_vars) if var.get()] selected_shelves = [] for i in range(1, 5): if shelf_schedule_vars[f"Water Shelf {i}"].get() == 1: duration_seconds = int(timer_values[pins[f"Water Shelf {i}"]].get()) selected_shelves.append(f"Shelf {i} ({duration_seconds} seconds)") if selected_days and selected_hours and selected_shelves: confirmation_text = (f"Scheduled: {', '.join(selected_shelves)}\n" f"Scheduled Days: {', '.join(selected_days)}\n" f"Scheduled Hours: {', '.join(selected_hours)}") logging.debug(confirmation_text) # Popup window for confirmation popup = tk.Toplevel(win) popup.title("Schedule Confirmation") tk.Label(popup, text=confirmation_text, padx=20, pady=20).pack() else: logging.debug("No schedule selected.")def stopProgram(): GPIO.cleanup() scheduler.shutdown() win.quit()tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=2, command=stopProgram).pack(side="bottom", pady=1)# --- Scheduler Callback ---def tk_callback(): while True: try: # Read the event from background scheduler event = eventqueue.get(block=False) if False: print(event) # Evaluate the event, check if checkbox-conditions match shelf_index = event["shelf"] if day_vars[event["day_of_week"]].get() == 1 and \ hour_vars[event["hour"]].get() == 1 and \ shelf_schedule_vars[shelf_index ].get() == 1: if debug: print(f"Water Shelf {shelf_index + 1} scheduled!") toggle_water_shelf(shelf_index, button_shelf[shelf_index], activation_labels[shelf_index]) except queue.Empty: break win.after(500, tk_callback)win.after(500, tk_callback) win.mainloop()Here is the portion of the code that may need fixing:
Code:
def process_scheduled_event(event): try: logging.debug(f"Simulating button click for shelf {event['shelf']} on day {event['day_of_week']} at {event['hour']} o'clock") # Get the pin number and button for the corresponding shelf shelf_label = f"Water Shelf {event['shelf'] + 1}" pin = pins.get(shelf_label) button = water_shelf_buttons.get(pin) if pin and button and shelf_enabled_vars[pin].get(): logging.debug(f"Triggering button for {shelf_label}") button.invoke() # Simulates a button click else: logging.debug(f"Skipping watering for {shelf_label} (not enabled or button missing).") except Exception as e: logging.error(f"Error processing scheduled event: {event}, Exception: {e}", exc_info=True) Cheers,
Statistics: Posted by papagino — Fri Mar 28, 2025 4:34 pm