Need help with using a scheduler to activate buttons on a GUI.
Hello people, I am a bit new to Pi and not so good in coding.
I made a GUI using a touchscreen and a Raspberry Pi 3 to control a small microgreen farm.
The GUI is used to control lighting, irrigation system and a water sprayer to do the watering by hand.
There are 4 shelves in the Microgreen Cabinet with individual water solenoids. I can set a timer in seconds that will activate the watering on a shelf for the set duration. Each shelf has a Checkbox that will only allow watering on that shelf if checked to prevent activating watering when no container is present on the shelf to prevent flooding.
I want the scheduler to simulate a water Shelf button pressed for set days and times to activate automatic watering on the microgreens on that shelf.
In order for the Watering Schedule to activate watering, both the checkbox beside the water Shelf # and the one below the Watering Scheduler need to be checked.
Here is the GUI screen:
And here is my current code generated via chatGPT with the Scheduler not working:
Can anyone help me getting the scheduler to work simulation buttons press for the selected watering shelves at set days/times.
Cheers,
I made a GUI using a touchscreen and a Raspberry Pi 3 to control a small microgreen farm.
The GUI is used to control lighting, irrigation system and a water sprayer to do the watering by hand.
There are 4 shelves in the Microgreen Cabinet with individual water solenoids. I can set a timer in seconds that will activate the watering on a shelf for the set duration. Each shelf has a Checkbox that will only allow watering on that shelf if checked to prevent activating watering when no container is present on the shelf to prevent flooding.
I want the scheduler to simulate a water Shelf button pressed for set days and times to activate automatic watering on the microgreens on that shelf.
In order for the Watering Schedule to activate watering, both the checkbox beside the water Shelf # and the one below the Watering Scheduler need to be checked.
Here is the GUI screen:
- GUI.png
- GUI.png (152.36 KiB) Viewed 6552 times
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
# GPIO Setup
GPIO.setmode(GPIO.BCM)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b", # LED Top Shelves (Yellow)
18: "#ffeb3b", # LED Bottom Shelves (Yellow)
5: "#FFA500", # Fans (Orange)
25: "#87CEEB", # Water Sprayer (Blue)
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}
# Function to turn off water shelf after timer expires
def start_timer(pin, button, duration, label):
time.sleep(duration)
GPIO.output(pin, GPIO.HIGH) # Turn OFF
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle water shelf with timer
def toggle_water_shelf(pin, button, label):
if shelf_enabled_vars[pin].get(): # Check if enabled
if GPIO.input(pin): # If OFF, turn ON
GPIO.output(pin, GPIO.LOW)
button.config(text=f"{label} ON", bg="#2ecc71")
try:
duration = int(timer_values[pin].get())
except ValueError:
duration = 10
threading.Thread(target=start_timer, args=(pin, button, duration, label), daemon=True).start()
else:
GPIO.output(pin, GPIO.HIGH)
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle general GPIO devices
def toggle_device(pin, button, label):
if GPIO.input(pin): # If OFF, turn ON
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"))
# Main layout
frame = 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")
# General control buttons
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 button
controls = [
("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)
# Water shelf buttons
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="w")
timer_values[pin].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
water_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)
# Scheduling section
schedule_frame = tk.Frame(win)
schedule_frame.pack(pady=10)
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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
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 selection
time_frame = tk.Frame(schedule_frame)
time_frame.grid(row=2, column=0, columnspan=7, pady=0)
tk.Label(time_frame, text="AM").grid(row=0, column=0)
for i, hour in enumerate([f"{i} AM" for i in range(12)]):
tk.Checkbutton(time_frame, text=hour, variable=tk.IntVar()).grid(row=0, column=i+1)
tk.Label(time_frame, text="PM").grid(row=1, column=0)
for i, hour in enumerate([f"{i} PM" for i in range(1, 12)] + ["12 PM"]):
tk.Checkbutton(time_frame, text=hour, variable=tk.IntVar()).grid(row=1, column=i+1)
# Shelf checkboxes in scheduler
shelf_schedule_vars = {f"Water Shelf {i}": tk.IntVar() for i in range(1, 5)}
for i in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
# Exit button
def stopProgram():
GPIO.cleanup()
win.quit()
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=2, command=stopProgram).pack(pady=5)
win.mainloop()
Can anyone help me getting the scheduler to work simulation buttons press for the selected watering shelves at set days/times.
Cheers,
Re: Need help with using a scheduler to activate buttons on a GUI.
Not a simple thing.
The easy part is the architecture: whenever background activities need to be done, then it is needed to isolate these tasks from the tkinter event queue 'mainloop'. The win.after(...) method allows to set up a periodical call which runs in tkinter context. A queue.Queue is used to isolate the background task from the tkinter context. The background runs in a thread and just sends events to the queue.Queue which are then evaluated in the tkinter context.
The background for the problem can be a scheduler. I usually use apscheduler package which allows cron type triggers which almost perfect match the day, hour pattern from your GUI. apscheduler creates threads on its own. I'd setup plenty of jobs, for each shelf, day, hour. This keeps processing in the tkinter context simple.
The data structures of your existing program do not perfectly match the needs of such a structure. For the scheduler checkboxes, there should be IntVar associated, see the sample. See also the numbering of the day, cron style is 0,1,2... for mon,tue, wed, thu ... ; you have sun first.
The easy part is the architecture: whenever background activities need to be done, then it is needed to isolate these tasks from the tkinter event queue 'mainloop'. The win.after(...) method allows to set up a periodical call which runs in tkinter context. A queue.Queue is used to isolate the background task from the tkinter context. The background runs in a thread and just sends events to the queue.Queue which are then evaluated in the tkinter context.
The background for the problem can be a scheduler. I usually use apscheduler package which allows cron type triggers which almost perfect match the day, hour pattern from your GUI. apscheduler creates threads on its own. I'd setup plenty of jobs, for each shelf, day, hour. This keeps processing in the tkinter context simple.
The data structures of your existing program do not perfectly match the needs of such a structure. For the scheduler checkboxes, there should be IntVar associated, see the sample. See also the numbering of the day, cron style is 0,1,2... for mon,tue, wed, thu ... ; you have sun first.
Code: Select all
import queue
import tkinter as tk
# install apscheduler package
import apscheduler.schedulers.background
scheduler = apscheduler.schedulers.background.BackgroundScheduler()
eventqueue = queue.Queue()
activation_labels = [
"Water Shelf 1",
"Water Shelf 2",
"Water Shelf 3",
"Water Shelf 4",
]
activation_pins = [
27,
22,
23,
24
]
# --- create the scheduler jobs
job_id = 0
for 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 += 1
print(job_id)
scheduler.start()
win = tk.Tk()
win.title("Microgreens Farm Controller with Scheduler")
win.geometry("800x480")
# --- in your code, create intvar for each of the checkboxes.
# watering schedule day checkbox variables. These are sorted mon,tue,...sun
day = [ tk.IntVar(win, 0) for i in range(7)]
# watering schedule hour checkbox variables
hour = [ tk.IntVar(win, 0) for i in range(24)]
# watering schedule shelf checkbox variables
schedule_shelf = [tk.IntVar(win, 0) for i in range(4)]
# watering enable shelf checkbox variables
activation_shelf = [tk.IntVar(win, 0) for i in range(4)]
# the buttons for the shelf (needed to set the label text)
button_shelf = [tk.Button(win, text=activation_labels[i]).pack(pady=5) for i in range(4)]
# --- the existing callback for the shelf button.
def toggle_water_shelf(pin, button, label):
# use current source code
pass
# --- a time called tk callback
def tk_callback():
while True:
try:
# read the event from background scheduler
event = eventqueue.get(block=False)
print(event)
# evaluate the event, check if checkbox-conditions match
shelf_index = event["shelf"]
if day[event["day_of_week"]].get()== 1 and \
hour[event["hour"]].get()== 1 and \
schedule_shelf[shelf_index].get()== 1 and \
activation_shelf[shelf_index].get()== 1:
# call the button callback
toggle_water_shelf(activation_pins[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()
Re: Need help with using a scheduler to activate buttons on a GUI.
OK but I would like the scheduler to only simulate button press for the watering shelves, and not actually controlling GPIO pins. The reason for this is that there are watering duration timers (ex: 10s) that will change on a regular basis depending on what type of microgreens needs watering, some take more water than others.ghp wrote: ↑Mon Mar 24, 2025 5:23 pmNot a simple thing.
The easy part is the architecture: whenever background activities need to be done, then it is needed to isolate these tasks from the tkinter event queue 'mainloop'. The win.after(...) method allows to set up a periodical call which runs in tkinter context. A queue.Queue is used to isolate the background task from the tkinter context. The background runs in a thread and just sends events to the queue.Queue which are then evaluated in the tkinter context.
The background for the problem can be a scheduler. I usually use apscheduler package which allows cron type triggers which almost perfect match the day, hour pattern from your GUI. apscheduler creates threads on its own. I'd setup plenty of jobs, for each shelf, day, hour. This keeps processing in the tkinter context simple.
The data structures of your existing program do not perfectly match the needs of such a structure. For the scheduler checkboxes, there should be IntVar associated, see the sample. See also the numbering of the day, cron style is 0,1,2... for mon,tue, wed, thu ... ; you have sun first.
Code: Select all
import queue import tkinter as tk # install apscheduler package import apscheduler.schedulers.background scheduler = apscheduler.schedulers.background.BackgroundScheduler() eventqueue = queue.Queue() activation_labels = [ "Water Shelf 1", "Water Shelf 2", "Water Shelf 3", "Water Shelf 4", ] activation_pins = [ 27, 22, 23, 24 ] # --- create the scheduler jobs job_id = 0 for 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 += 1 print(job_id) scheduler.start() win = tk.Tk() win.title("Microgreens Farm Controller with Scheduler") win.geometry("800x480") # --- in your code, create intvar for each of the checkboxes. # watering schedule day checkbox variables. These are sorted mon,tue,...sun day = [ tk.IntVar(win, 0) for i in range(7)] # watering schedule hour checkbox variables hour = [ tk.IntVar(win, 0) for i in range(24)] # watering schedule shelf checkbox variables schedule_shelf = [tk.IntVar(win, 0) for i in range(4)] # watering enable shelf checkbox variables activation_shelf = [tk.IntVar(win, 0) for i in range(4)] # the buttons for the shelf (needed to set the label text) button_shelf = [tk.Button(win, text=activation_labels[i]).pack(pady=5) for i in range(4)] # --- the existing callback for the shelf button. def toggle_water_shelf(pin, button, label): # use current source code pass # --- a time called tk callback def tk_callback(): while True: try: # read the event from background scheduler event = eventqueue.get(block=False) print(event) # evaluate the event, check if checkbox-conditions match shelf_index = event["shelf"] if day[event["day_of_week"]].get()== 1 and \ hour[event["hour"]].get()== 1 and \ schedule_shelf[shelf_index].get()== 1 and \ activation_shelf[shelf_index].get()== 1: # call the button callback toggle_water_shelf(activation_pins[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()
I am new to all this coding, but I am good in designing GUI functionality in terms of what I want the GUI to perform for the project.
So if I want the scheduler to simulate button press for the watering shelves at set days and times, do you have any idea on how I could incorporate this to my existing code?
ChatGPT was very good at creating codes for my project from a precise descriptive text, but I am stuck with the scheduler not working...
Any help would be appreciated.
Cheers,
Re: Need help with using a scheduler to activate buttons on a GUI.
The idea of the code provided is that the scheduler calls the button's (existing) callback method. Which should exactly do what happens when button is pressed manually.would like the scheduler to only simulate button press for the watering shelves, and not actually controlling GPIO pins.
Merge the code with your existing GUI code along the hints provided in the code.
Re: Need help with using a scheduler to activate buttons on a GUI.
I'm afraid this is beyond my capability, I will really screw the code if I even try to do this.ghp wrote: ↑Mon Mar 24, 2025 7:39 pmThe idea of the code provided is that the scheduler calls the button's (existing) callback method. Which should exactly do what happens when button is pressed manually.would like the scheduler to only simulate button press for the watering shelves, and not actually controlling GPIO pins.
Merge the code with your existing GUI code along the hints provided in the code.
I am more familiar with arduino stuff, python is not in my brain and I can't learn it since I will probably only use it once of twice for a very long time.
Time to hire a coder that can do this stuff for me...
Cheers
Re: Need help with using a scheduler to activate buttons on a GUI.
Does this look like it could work?
I am getting this from the print:
Total scheduled jobs: 672
/usr/lib/python3/dist-packages/apscheduler/util.py:436: PytzUsageWarning: The localize method is no longer necessary, as this time zone supports the fold attribute (PEP 495). For more details on migrating to a PEP 495-compliant implementation, see https://pytz-deprecation-shim.readthedo ... ation.html
return tzinfo.localize(dt)
Any ideas!!!
I am getting this from the print:
Total scheduled jobs: 672
/usr/lib/python3/dist-packages/apscheduler/util.py:436: PytzUsageWarning: The localize method is no longer necessary, as this time zone supports the fold attribute (PEP 495). For more details on migrating to a PEP 495-compliant implementation, see https://pytz-deprecation-shim.readthedo ... ation.html
return tzinfo.localize(dt)
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
# Scheduler setup
scheduler = BackgroundScheduler()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for 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 += 1
print(f"Total scheduled jobs: {job_id}")
scheduler.start()
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b", # LED Top Shelves (Yellow)
18: "#ffeb3b", # LED Bottom Shelves (Yellow)
5: "#FFA500", # Fans (Orange)
25: "#87CEEB", # Water Sprayer (Blue)
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}
# Function to turn off water shelf after timer expires
def start_timer(pin, button, duration, label):
time.sleep(duration)
GPIO.output(pin, GPIO.HIGH) # Turn OFF
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle water shelf with timer
def toggle_water_shelf(pin, button, label):
if shelf_enabled_vars[pin].get(): # Check if enabled
if GPIO.input(pin): # If OFF, turn ON
GPIO.output(pin, GPIO.LOW)
button.config(text=f"{label} ON", bg="#2ecc71")
try:
duration = int(timer_values[pin].get())
except ValueError:
duration = 10
threading.Thread(target=start_timer, args=(pin, button, duration, label), daemon=True).start()
else:
GPIO.output(pin, GPIO.HIGH)
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle general GPIO devices
def toggle_device(pin, button, label):
if GPIO.input(pin): # If OFF, turn ON
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"))
# Main layout
frame = 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")
# General control buttons
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 button
controls = [
("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)
# Water shelf buttons
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="w")
timer_values[pin].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
water_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)
# Scheduling section
schedule_frame = tk.Frame(win)
schedule_frame.pack(pady=10)
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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
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 selection
time_frame = tk.Frame(schedule_frame)
time_frame.grid(row=2, column=0, columnspan=7, pady=0)
tk.Label(time_frame, text="AM").grid(row=0, column=0)
for i, hour in enumerate([f"{i} AM" for i in range(12)]):
tk.Checkbutton(time_frame, text=hour, variable=tk.IntVar()).grid(row=0, column=i+1)
tk.Label(time_frame, text="PM").grid(row=1, column=0)
for i, hour in enumerate([f"{i} PM" for i in range(1, 12)] + ["12 PM"]):
tk.Checkbutton(time_frame, text=hour, variable=tk.IntVar()).grid(row=1, column=i+1)
# Shelf checkboxes in scheduler
shelf_schedule_vars = {f"Water Shelf {i}": tk.IntVar() for i in range(1, 5)}
for i in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
# Exit button
def stopProgram():
GPIO.cleanup()
win.quit()
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=2, command=stopProgram).pack(pady=5)
# Checkbox variables
day = [tk.IntVar(win, 0) for _ in range(7)]
hour = [tk.IntVar(win, 0) for _ in range(24)]
schedule_shelf = [tk.IntVar(win, 0) for _ in range(4)]
activation_shelf = [tk.IntVar(win, 0) for _ in range(4)]
# --- Scheduler Callback ---
def tk_callback():
while not eventqueue.empty():
try:
event = eventqueue.get(block=False)
shelf_index = event["shelf"]
if (day[event["day_of_week"]].get() == 1 and
hour[event["hour"]].get() == 1 and
schedule_shelf[shelf_index].get() == 1 and
activation_shelf[shelf_index].get() == 1):
toggle_water_shelf(activation_pins[shelf_index],
button_shelf[shelf_index],
activation_labels[shelf_index])
except queue.Empty:
pass
win.after(500, tk_callback) # Check every 500ms
win.after(500, tk_callback)
win.mainloop()
Re: Need help with using a scheduler to activate buttons on a GUI.
Got this warning PytzUsageWarning also. Just ignore this.
Re: Need help with using a scheduler to activate buttons on a GUI.
Hmm, your code creates checkboxes like this into a dictionary (why this? )
and later creates IntVar in the array, but these are not related to the checkboxes.
This means that the scheduler actions look into "schedule_shelf" which never change.
You should instead create the variables in the array
which first creates the tk intvar in the array, and then uses these for the checkboxes.
Same for the other three array "day", "hour", "activation_shelf".
Code: Select all
# Shelf checkboxes in scheduler
shelf_schedule_vars = {f"Water Shelf {i}": tk.IntVar() for i in range(1, 5)}
for i in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
Code: Select all
# Checkbox variables
schedule_shelf = [tk.IntVar(win, 0) for _ in range(4)]
You should instead create the variables in the array
Code: Select all
schedule_shelf = [tk.IntVar(win, 0) for _ in range(4)]
for i in range(4):
tk.Checkbutton(schedule_frame,
text=f"Water Shelf {i+1}",
variable=schedule_shelf[i]).grid(row=3, column=i+1, pady=5)
Same for the other three array "day", "hour", "activation_shelf".
Re: Need help with using a scheduler to activate buttons on a GUI.
Ok this is my updated code, I now have a button to print the schedules and it is showing the schedules accurately:
ex:
Total scheduled jobs: 672
/usr/lib/python3/dist-packages/apscheduler/util.py:436: PytzUsageWarning: The localize method is no longer necessary, as this time zone supports the fold attribute (PEP 495). For more details on migrating to a PEP 495-compliant implementation, see https://pytz-deprecation-shim.readthedo ... ation.html
return tzinfo.localize(dt)
Scheduled Days: Tue
Scheduled Hours: 7 PM
Testing to see if it will actually trigger watering at set schedules...
Fingers crossed..
ex:
Total scheduled jobs: 672
/usr/lib/python3/dist-packages/apscheduler/util.py:436: PytzUsageWarning: The localize method is no longer necessary, as this time zone supports the fold attribute (PEP 495). For more details on migrating to a PEP 495-compliant implementation, see https://pytz-deprecation-shim.readthedo ... ation.html
return tzinfo.localize(dt)
Scheduled Days: Tue
Scheduled Hours: 7 PM
- GUI2.png
- GUI2.png (89.55 KiB) Viewed 6281 times
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
# Scheduler setup
scheduler = BackgroundScheduler()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for 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 += 1
print(f"Total scheduled jobs: {job_id}")
scheduler.start()
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b", # LED Top Shelves (Yellow)
18: "#ffeb3b", # LED Bottom Shelves (Yellow)
5: "#FFA500", # Fans (Orange)
25: "#87CEEB", # Water Sprayer (Blue)
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}
# Function to turn off water shelf after timer expires
def start_timer(pin, button, duration, label):
time.sleep(duration)
GPIO.output(pin, GPIO.HIGH) # Turn OFF
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle water shelf with timer
def toggle_water_shelf(pin, button, label):
if shelf_enabled_vars[pin].get(): # Check if enabled
if GPIO.input(pin): # If OFF, turn ON
GPIO.output(pin, GPIO.LOW)
button.config(text=f"{label} ON", bg="#2ecc71")
try:
duration = int(timer_values[pin].get())
except ValueError:
duration = 10
threading.Thread(target=start_timer, args=(pin, button, duration, label), daemon=True).start()
else:
GPIO.output(pin, GPIO.HIGH)
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle general GPIO devices
def toggle_device(pin, button, label):
if GPIO.input(pin): # If OFF, turn ON
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"))
# Main layout
frame = 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")
# General control buttons
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 button
controls = [
("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)
# Water shelf buttons
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="w")
timer_values[pin].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
water_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)
# Scheduling section
schedule_frame = tk.Frame(win)
schedule_frame.pack(pady=10)
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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
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 selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
# Add this function to print the selected schedule
def print_schedule():
selected_days = [day for day, var in day_vars.items() if var.get() == 1]
selected_hours = [f"{hour} AM" for hour in range(12) if hour_vars[hour].get() == 1] + \
[f"{hour - 12} PM" for hour in range(12, 24) if hour_vars[hour].get() == 1]
if selected_days and selected_hours:
print(f"Scheduled Days: {', '.join(selected_days)}")
print(f"Scheduled Hours: {', '.join(selected_hours)}")
else:
print("No schedule selected.")
# Exit button
def stopProgram():
GPIO.cleanup()
win.quit()
# Create the Exit button first
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=1, command=stopProgram).pack(side="bottom", pady=1)
# Create the Print Schedule button
tk.Button(win, text="Print Schedule", bg="#2980b9", fg="white", width=20, height=2, command=print_schedule).pack(pady=1)
# --- Scheduler Callback ---
def tk_callback():
while True:
try:
# Read the event from background scheduler
event = eventqueue.get(block=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[f"Water Shelf {shelf_index + 1}"].get() == 1:
print(f"Water Shelf {shelf_index + 1} scheduled!")
except queue.Empty:
break
time.sleep(1)
# Run tkinter event loop in a separate thread
def gui_thread():
while True:
win.update()
tk_callback()
threading.Thread(target=gui_thread, daemon=True).start()
win.mainloop()
Fingers crossed..
Re: Need help with using a scheduler to activate buttons on a GUI.
Instead of waiting hours for something to be triggered you could change the schedule job parameter.
In my own programs, I usually define a 'debug' variable at the beginning to switch such test code on/off.
In my own programs, I usually define a 'debug' variable at the beginning to switch such test code on/off.
Code: Select all
# set to False when code runs in production mode
debug = True # add this line at the beginning of the code, after the imports
...
# --- Create scheduler jobs ---
job_id = 0
for 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}
if debug:
scheduler.add_job(lambda e=event: eventqueue.put(e),
'cron',
second=0, # trigger each minute when second == 0
id=f'job_id_{job_id}')
else:
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 += 1
Re: Need help with using a scheduler to activate buttons on a GUI.
Ok, I updated the code below.
When I set debug = True, I get something like this in the Terminal:
2025年03月26日 15:14:02,289 - WARNING - Run time of job "<lambda> (trigger: cron[second='0'], next run at: 2025年03月26日 15:15:00 ADT)" was missed by 0:00:02.289717
When I set debug = False and configure the scheduler, I would get something like this:
2025年03月26日 15:15:34,619 - DEBUG - Selected schedule: Days - ['Wed'], Hours - [18], Shelves - ['Water Shelf 1', 'Water Shelf 2']
The scheduler seems to work, however it is not simulation a watering shelf button press as nothing is happening.
Any idea why?
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# Set debug mode (set to False for production)
debug = True # Change to False for normal operation
def process_scheduled_event(event):
try:
logging.debug(f"Executing event for shelf {event['shelf']} on day {event['day_of_week']} at {event['hour']} o'clock")
# Your watering logic here
except Exception as e:
logging.error(f"Error processing scheduled event: {event}, Exception: {e}", exc_info=True)
# Initialize APScheduler
scheduler = BackgroundScheduler()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for 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}
# Debug mode: trigger job every minute
if debug:
scheduler.add_job(lambda e=event: eventqueue.put(e), 'cron',
second=0, # Trigger every minute when second == 0
id=f'job_id_{job_id}')
else:
scheduler.add_job(lambda e=event: eventqueue.put(e), 'cron',
day_of_week=day_of_week, hour=hour, # Use real schedule
id=f'job_id_{job_id}')
job_id += 1
logging.debug(f"Total scheduled jobs: {job_id}")
scheduler.start()
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b", # LED Top Shelves (Yellow)
18: "#ffeb3b", # LED Bottom Shelves (Yellow)
5: "#FFA500", # Fans (Orange)
25: "#87CEEB", # Water Sprayer (Blue)
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}
# Function to turn off water shelf after timer expires
def start_timer(pin, button, duration, label):
time.sleep(duration)
GPIO.output(pin, GPIO.HIGH) # Turn OFF
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle water shelf with timer
def toggle_water_shelf(pin, button, label):
print(f"Toggling water shelf {label} with pin {pin}")
if shelf_enabled_vars[pin].get(): # Check if enabled
if GPIO.input(pin): # If OFF, turn ON
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}.")
# Function to toggle general GPIO devices
def toggle_device(pin, button, label):
if GPIO.input(pin): # If OFF, turn ON
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 layout
frame = 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")
# General control buttons
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 button
controls = [
("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)
# Water shelf buttons
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="w")
timer_values[pin].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
water_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)
# Scheduling section
schedule_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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
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 selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
# Add this function to print the selected schedule
def print_schedule():
selected_days = [day for day, var in day_vars.items() if var.get() == 1]
selected_hours = [hour for hour, var in enumerate(hour_vars) if var.get() == 1]
selected_shelves = [f"Water Shelf {i}" for i in range(1, 5) if shelf_schedule_vars[f"Water Shelf {i}"].get() == 1]
logging.debug(f"Selected schedule: Days - {selected_days}, Hours - {selected_hours}, Shelves - {selected_shelves}")
submit_button = tk.Button(schedule_frame, text="Submit Schedule", command=print_schedule)
submit_button.grid(row=4, column=0, columnspan=7, pady=10)
# Scheduler event processor
def process_scheduler_events():
while True:
try:
event = eventqueue.get(timeout=1)
process_scheduled_event(event)
except queue.Empty:
pass
scheduler_thread = threading.Thread(target=process_scheduler_events, daemon=True)
scheduler_thread.start()
# Start the Tkinter main loop
win.mainloop()
Re: Need help with using a scheduler to activate buttons on a GUI.
OK, trying something else, using button.invoke() for the scheduler to simulates a button click to see how it does...
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
# Enable detailed logging
#logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
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(): # Check if shelf is enabled
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)
# Initialize APScheduler
scheduler = BackgroundScheduler()
# Add scheduled jobs using this function
scheduler.add_job(process_scheduled_event, 'cron', day_of_week=1, hour=21, args=[{'shelf': 1, 'day_of_week': 1, 'hour': 21}])
# Start the scheduler
scheduler.start()
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
# Scheduler setup
scheduler = BackgroundScheduler()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for 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 += 1
logging.debug(f"Total scheduled jobs: {job_id}")
scheduler.start()
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b", # LED Top Shelves (Yellow)
18: "#ffeb3b", # LED Bottom Shelves (Yellow)
5: "#FFA500", # Fans (Orange)
25: "#87CEEB", # Water Sprayer (Blue)
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_values = {pins[f"Water Shelf {i}"]: tk.StringVar() for i in range(1, 5)}
# Function to turn off water shelf after timer expires
def start_timer(pin, button, duration, label):
time.sleep(duration)
GPIO.output(pin, GPIO.HIGH) # Turn OFF
button.config(text=f"{label} OFF", bg="#87CEFA")
# Function to toggle water shelf with timer
def toggle_water_shelf(pin, button, label):
print(f"Toggling water shelf {label} with pin {pin}")
if shelf_enabled_vars[pin].get(): # Check if enabled
if GPIO.input(pin): # If OFF, turn ON
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}.")
# Function to toggle general GPIO devices
def toggle_device(pin, button, label):
if GPIO.input(pin): # If OFF, turn ON
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 layout
frame = 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")
# General control buttons
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 button
controls = [
("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)
# Water shelf buttons
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="w")
timer_values[pin].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
water_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)
# Scheduling section
schedule_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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
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 selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 in range(1, 5):
tk.Checkbutton(schedule_frame, text=f"Water Shelf {i}", variable=shelf_schedule_vars[f"Water Shelf {i}"]).grid(row=3, column=i, pady=5)
# Add this function to print the selected schedule
def print_schedule():
selected_days = [day for day, var in day_vars.items() if var.get() == 1]
selected_hours = [f"{hour} AM" for hour in range(12) if hour_vars[hour].get() == 1] + \
[f"{hour - 12} PM" for hour in range(12, 24) if hour_vars[hour].get() == 1]
# List of selected shelves and their watering duration in seconds
selected_shelves = []
for i in range(1, 5):
if shelf_schedule_vars[f"Water Shelf {i}"].get() == 1: # Use the scheduler section checkboxes
# Retrieve the watering duration for each shelf from the timer_values dictionary
duration_seconds = int(timer_values[pins[f"Water Shelf {i}"]].get()) # Get seconds
selected_shelves.append(f"Water Shelf {i} ({duration_seconds} seconds)")
if selected_days and selected_hours and selected_shelves:
logging.debug(f"Selected Shelves: {', '.join(selected_shelves)}")
logging.debug(f"Scheduled Days: {', '.join(selected_days)}")
logging.debug(f"Scheduled Hours: {', '.join(selected_hours)}")
else:
logging.debug("No schedule selected.")
# Exit button
def stopProgram():
GPIO.cleanup()
win.quit()
# Create the Exit button first
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=1, command=stopProgram).pack(side="bottom", pady=1)
# Create the Print Schedule button
tk.Button(win, text="Print Schedule", command=print_schedule, bg="#16a085", fg="white", width=20, height=1).pack(side="bottom", pady=1)
# Run the tkinter event loop
win.mainloop()
Re: Need help with using a scheduler to activate buttons on a GUI.
In one of the last code snippet, there is the tk_callback method like this. There is a print statement on the action, but no call the button callback.
The problem is: the code calling the button callback method is removed. If you compare this with previous versions, there is
Code: Select all
def tk_callback():
while True:
try:
# Read the event from background scheduler
event = eventqueue.get(block=False)
... shorted for clarity
if day_vars[event["day_of_week"]].get() == 1 and \
... shorted for clarity :
print(f"Water Shelf {shelf_index + 1} scheduled!")
Code: Select all
def tk_callback():
while not eventqueue.empty():
try:
event = eventqueue.get(block=False)
... shorted for clarity
if (day[event["day_of_week"]].get() == 1 and
... shorted for clarity
):
toggle_water_shelf(activation_pins[shelf_index],
button_shelf[shelf_index],
activation_labels[shelf_index])
Re: Need help with using a scheduler to activate buttons on a GUI.
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
- 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
Code: Select all
import threading
import time
import tkinter as tk
import queue
import apscheduler.schedulers.background
debug = True
debug_no_gpio = True
class Hardware:
LED_TOP_SHELVES = 0
LED_BOTTOM_SHELVES = 1
WATER_SPRAYER = 2
FANS = 3
WATER_SHELF_1 = 4
WATER_SHELF_2 = 5
WATER_SHELF_3 = 6
WATER_SHELF_4 = 7
PIN_LED_TOP_SHELVES = 17
PIN_LED_BOTTOM_SHELVES = 18
PIN_WATER_SPRAYER = 25
PIN_FANS = 5
PIN_WATER_SHELF_1 = 27
PIN_WATER_SHELF_2 = 22
PIN_WATER_SHELF_3 = 23
PIN_WATER_SHELF_4 = 24
INDEX_WATER_SHELF_1 = 0
INDEX_WATER_SHELF_2 = 1
INDEX_WATER_SHELF_3 = 2
INDEX_WATER_SHELF_4 = 3
STATE_OFF = 0
STATE_ON = 1
def __init__(self):
if not debug_no_gpio:
# GPIO Setup
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
self.states = [Hardware.STATE_OFF,Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF,
Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF ]
self.pins = [ Hardware.PIN_FANS,
Hardware.PIN_LED_BOTTOM_SHELVES,
Hardware.PIN_LED_TOP_SHELVES,
Hardware.PIN_WATER_SPRAYER,
Hardware.PIN_WATER_SHELF_1,
Hardware.PIN_WATER_SHELF_2,
Hardware.PIN_WATER_SHELF_3,
Hardware.PIN_WATER_SHELF_4
]
if not debug_no_gpio:
for pin in self.pins:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
def _set_output(self, pin, state):
if debug_no_gpio:
print( f"_set_output(self, {pin}, {state})")
else:
if state == Hardware.STATE_ON:
GPIO.output(pin, GPIO.LOW)
else:
GPIO.output(pin, GPIO.HIGH)
def cleanup(self):
if not debug_no_gpio:
for pin in self.pins:
GPIO.output(pin, GPIO.LOW) # Default to OFF (HIGH means OFF for relays)
GPIO.cleanup()
def get_state(self, channel):
return self.states[channel]
def set_state(self, channel, state):
self.states[channel] = state
self._set_output(self.pins[ channel], state)
@property
def led_top_shelves(self):
return self.state_led_top_shelves
@led_top_shelves.setter
def led_top_shelves(self, state):
self.states[Hardware.LED_TOP_SHELVES] = state
self._set_output(Hardware.PIN_LED_TOP_SHELVES, state)
@property
def led_bottom_shelves(self):
return self.states[Hardware.LED_TOP_SHELVES]
@led_bottom_shelves.setter
def led_bottom_shelves(self, state):
self.states[Hardware.LED_BOTTOM_SHELVES] = state
self._set_output(Hardware.PIN_LED_BOTTOM_SHELVES, state)
@property
def fans(self):
return self.states[Hardware.FANS]
@fans.setter
def fans(self, state):
self.states[Hardware.FANS] = state
self._set_output(Hardware.PIN_FANS, state)
@property
def water_sprayer(self):
return self.states[Hardware.WATER_SPRAYER]
@water_sprayer.setter
def water_sprayer(self, state):
self.states[Hardware.WATER_SPRAYER] = state
self._set_output(Hardware.PIN_WATER_SPRAYER, state)
@property
def water_shelf_1(self):
return self.states[Hardware.WATER_SHELF_1]
@water_shelf_1.setter
def water_shelf_1(self, state):
self.states[Hardware.WATER_SHELF_1] = state
self._set_output(Hardware.PIN_WATER_SHELF_1, state)
@property
def water_shelf_2(self):
return self.states[Hardware.WATER_SHELF_2]
@water_shelf_2.setter
def water_shelf_2(self, state):
self.states[Hardware.WATER_SHELF_2] = state
self._set_output(Hardware.PIN_WATER_SHELF_2, state)
@property
def water_shelf_3(self):
return self.states[Hardware.WATER_SHELF_3]
@water_shelf_3.setter
def water_shelf_3(self, state):
self.states[Hardware.WATER_SHELF_3] = state
self._set_output(Hardware.PIN_WATER_SHELF_3, state)
@property
def water_shelf_4(self):
return self.states[Hardware.WATER_SHELF_4]
@water_shelf_4.setter
def water_shelf_4(self, state):
self.states[Hardware.WATER_SHELF_4] = state
self._set_output(Hardware.PIN_WATER_SHELF_4, state)
def set_state_water_shelf(self, index, state):
if index == Hardware.INDEX_WATER_SHELF_1: self.water_shelf_1 = state
if index == Hardware.INDEX_WATER_SHELF_2: self.water_shelf_2 = state
if index == Hardware.INDEX_WATER_SHELF_3: self.water_shelf_3 = state
if index == Hardware.INDEX_WATER_SHELF_4: self.water_shelf_4 = state
def get_state_water_shelf(self, index):
if index == Hardware.INDEX_WATER_SHELF_1: return self.water_shelf_1
if index == Hardware.INDEX_WATER_SHELF_2: return self.water_shelf_2
if index == Hardware.INDEX_WATER_SHELF_3: return self.water_shelf_3
if index == Hardware.INDEX_WATER_SHELF_4: return self.water_shelf_4
hardware = Hardware()
activation_labels = ["Water Shelf 1", "Water Shelf 2", "Water Shelf 3","Water Shelf 4"]
# Scheduler setup
scheduler = apscheduler.schedulers.background.BackgroundScheduler()
scheduler.start()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for shelf in range(4):
for day_of_week in range(7):
for hour in range(24):
# convert the cron days (mon, tue, thu 0, 1, 2,,,) to the day numbering in
# the program (sun, mon, tue... 0,1,2,3)
event = {"shelf": shelf, "day_of_week": (day_of_week-1)%7, "hour": hour}
if debug:
scheduler.add_job(lambda e=event: eventqueue.put(e),
'cron',
second=1, id=f'job_id_{job_id}')
else:
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 += 1
print(f"Total scheduled jobs: {job_id}")
if debug:
print(scheduler.print_jobs())
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
YELLOW = "#ffeb3b"
ORANGE = "#FFA500"
BLUE = "#87CEEB"
DEFAULT_COLORS_CHANNEL = {
Hardware.LED_TOP_SHELVES :YELLOW,
Hardware.LED_BOTTOM_SHELVES:YELLOW,
Hardware.WATER_SPRAYER : BLUE,
Hardware.FANS : ORANGE,
Hardware.WATER_SHELF_1 :BLUE,
Hardware.WATER_SHELF_2 :BLUE,
Hardware.WATER_SHELF_3 :BLUE,
Hardware.WATER_SHELF_4 :BLUE,
}
# Timer values
timer_values = [ tk.StringVar() for _ in range(4) ]
# Function to toggle water shelf with timer
def toggle_water_shelf(index, button, label):
if debug:
print("toggle_water_shelf: toggle index", index, button, label)
if shelf_enabled_vars[index].get() == 1:
button.config(text=f"{label} ON", bg="#2ecc71")
try:
duration = int(timer_values[index].get())
except ValueError:
duration = 10
win.after(duration*1000, lambda button=button, text =f"{label} OFF" : button.config(text=text, bg="#2ecc71"))
hardware.set_state_water_shelf(index, Hardware.STATE_ON)
t = threading.Timer(duration, lambda index=index: hardware.set_state_water_shelf(index, Hardware.STATE_OFF))
t.start()
# Function to toggle general GPIO devices
def toggle_device(channel, button, label):
if debug:
print( f"toggle_device channel {channel}")
if hardware.get_state(channel ) == Hardware.STATE_OFF:
hardware.set_state(channel, Hardware.STATE_ON ) # If OFF, turn ON
button.config(text=f"{label} ON", bg="#2ecc71")
else:
hardware.set_state(channel, Hardware.STATE_OFF )
button.config(text=f"{label} OFF", bg=DEFAULT_COLORS_CHANNEL.get(channel, "#ffeb3b"))
# Main layout
frame = 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")
# General control buttons
def create_toggle_button(label, channel, row):
button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS_CHANNEL.get(channel, "#ffeb3b"), width=20, height=2)
button.config(command=lambda: toggle_device(channel, button, label))
button.grid(row=row, column=0, padx=10, pady=2, sticky="w")
return button
controls = [
("LED Top Shelves", Hardware.LED_TOP_SHELVES),
("LED Bottom Shelves", Hardware.LED_BOTTOM_SHELVES),
("Water Sprayer", Hardware.WATER_SPRAYER),
("Fans", Hardware.FANS),
]
for i, (label, channel) in enumerate(controls):
create_toggle_button(label, channel, i)
# Water shelf buttons
shelf_enabled_vars = [ tk.IntVar() for _ in range(4) ]
def create_water_shelf_button(label, index, row):
button = tk.Button(water_shelf_frame, text=label + " OFF", bg="#87CEFA", width=20, height=2)
button.config(command=lambda: toggle_water_shelf(index, 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[index])
shelf_checkbox.grid(row=row, column=1, padx=10, pady=5, sticky="w")
timer_entry = tk.Entry(water_shelf_frame, textvariable=timer_values[index], width=5)
timer_entry.grid(row=row, column=2, padx=10, pady=5, sticky="w")
timer_values[index].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
button_shelf= []
button = create_water_shelf_button(f"Water Shelf 1", Hardware.INDEX_WATER_SHELF_1, 1)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 2", Hardware.INDEX_WATER_SHELF_2, 2)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 3", Hardware.INDEX_WATER_SHELF_3, 3)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 4", Hardware.INDEX_WATER_SHELF_4, 4)
button_shelf.append(button)
# Scheduling section
schedule_frame = tk.Frame(win)
schedule_frame.pack(pady=10)
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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
day_vars = [tk.IntVar() for day in days]
for i, day in enumerate(days):
tk.Checkbutton(schedule_frame, text=day,
variable=day_vars[i]
).grid(row=1, column=i, padx=30, pady=1)
# Time selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 = [ tk.IntVar() for i in range(1, 5)]
for i in range(1, 5):
tk.Checkbutton(schedule_frame,
text=f"Water Shelf {i}",
variable=shelf_schedule_vars[i-1]).grid(row=3, column=i, pady=5)
# Add this function to print the selected schedule
def print_schedule():
if False:
selected_days = [day for day, var in day_vars.items() if var.get() == 1]
selected_hours = [f"{hour} AM" for hour in range(12) if hour_vars[hour].get() == 1] + \
[f"{hour - 12} PM" for hour in range(12, 24) if hour_vars[hour].get() == 1]
if selected_days and selected_hours:
print(f"Scheduled Days: {', '.join(selected_days)}")
print(f"Scheduled Hours: {', '.join(selected_hours)}")
else:
print("No schedule selected.")
print_variables("day_vars ", day_vars)
print_variables("hour vars ", hour_vars)
print_variables("shelf_schedule_vars ", shelf_schedule_vars)
print_variables("shelf_enabled_vars ", shelf_enabled_vars)
# Exit button
def stopProgram():
hardware.cleanup()
win.quit()
# Create the Exit button first
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=1, command=stopProgram).pack(side="bottom", pady=1)
# Create the Print Schedule button
if debug:
tk.Button(win, text="Print Schedule", bg="#2980b9", fg="white", width=20, height=2, command=print_schedule).pack(pady=1)
def print_variables(label, array):
print(label, end="")
for a in array:
print( f" {a.get()}", end="")
print()
# --- 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()
Re: Need help with using a scheduler to activate buttons on a GUI.
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.ghp wrote: ↑Thu Mar 27, 2025 5:08 pmHad 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 screen
- GUI3.png
- GUI3.png (90.84 KiB) Viewed 6110 times
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: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
import apscheduler.schedulers.background
import logging
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # OFF state for relays
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b",
18: "#ffeb3b",
5: "#FFA500",
25: "#87CEEB",
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_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 layout
frame = 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 button
controls = [
("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 button
water_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 setup
scheduler = 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 = 0
for 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 += 1
logging.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 section
schedule_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 week
days = ["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 selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 action
def on_print_press(event):
print_schedule() # Trigger the schedule print immediately
# Update button binding
print_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 snippet
def 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: Select all
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,
- Attachments
-
- PrintSchedule.png
- PrintSchedule.png (92.22 KiB) Viewed 6110 times
Re: Need help with using a scheduler to activate buttons on a GUI.
Also, this is a little script to test if the APScheduler was able to simulate a button press using "button.invoke" and it is working...
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.OUT) # Set pin 27 as an output
# Create GUI window
root = tk.Tk()
root.title("Scheduler Test")
# Function to start watering (activate GPIO)
def start_watering():
print("Watering started for 10 seconds...")
GPIO.output(27, GPIO.LOW) # Turn ON the water valve (or relay)
root.after(10000, stop_watering) # Schedule stop after 10 sec
# Function to stop watering (deactivate GPIO)
def stop_watering():
print("Watering stopped.")
GPIO.output(27, GPIO.HIGH) # Turn OFF the water valve (or relay)
# Manual watering button
button = tk.Button(root, text="Start Watering", command=start_watering)
button.pack(pady=10)
# APScheduler setup
scheduler = BackgroundScheduler()
scheduler.start()
# Function to schedule watering at selected time
def schedule_watering():
hour = int(hour_var.get())
minute = int(minute_var.get())
now = datetime.now()
run_time = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
if run_time < now:
run_time += time(days=1) # Schedule for next day if time has passed
print(f"Scheduling watering at {run_time.strftime('%H:%M')}...")
scheduler.add_job(lambda: root.after(0, button.invoke), 'date', run_date=run_time)
# Scheduler UI section
tk.Label(root, text="Set Schedule Time").pack()
hour_var = tk.StringVar(value="15")
minute_var = tk.StringVar(value="00")
hour_entry = tk.Entry(root, textvariable=hour_var, width=5)
hour_entry.pack(side=tk.LEFT, padx=5)
tk.Label(root, text=":").pack(side=tk.LEFT)
minute_entry = tk.Entry(root, textvariable=minute_var, width=5)
minute_entry.pack(side=tk.LEFT, padx=5)
schedule_button = tk.Button(root, text="Schedule Watering", command=schedule_watering)
schedule_button.pack(pady=10)
# Run GUI
try:
root.mainloop()
finally:
scheduler.shutdown()
GPIO.cleanup() # Clean up GPIO on exit
Re: Need help with using a scheduler to activate buttons on a GUI.
There have been some issues with last code
- GPIO output was disabled
- library import for RPi.GPIO was in wrong place
- the events produced by the scheduler had a wrong day_of_week value.
With this version, I can switch GPIO and the scheduler triggers GPIO.
- GPIO output was disabled
- library import for RPi.GPIO was in wrong place
- the events produced by the scheduler had a wrong day_of_week value.
With this version, I can switch GPIO and the scheduler triggers GPIO.
Code: Select all
import threading
import tkinter as tk
import queue
import apscheduler.schedulers.background
# --------------------------
# enable debug messages
# setting for production: False
debug = True
# --------------------------
# do not use GPIO output
# setting for production: False
debug_no_gpio = False
# --------------------------
# set cron events to minute rate
# setting for production: False
debug_cron = False
# --------------------------
# trigger not at full hour
# setting for production: 0
debug_cron_minute = 0
if not debug_no_gpio:
import RPi.GPIO as GPIO
class Hardware:
LED_TOP_SHELVES = 0
LED_BOTTOM_SHELVES = 1
WATER_SPRAYER = 2
FANS = 3
WATER_SHELF_1 = 4
WATER_SHELF_2 = 5
WATER_SHELF_3 = 6
WATER_SHELF_4 = 7
PIN_LED_TOP_SHELVES = 17
PIN_LED_BOTTOM_SHELVES = 18
PIN_WATER_SPRAYER = 25
PIN_FANS = 5
PIN_WATER_SHELF_1 = 27
PIN_WATER_SHELF_2 = 22
PIN_WATER_SHELF_3 = 23
PIN_WATER_SHELF_4 = 24
INDEX_WATER_SHELF_1 = 0
INDEX_WATER_SHELF_2 = 1
INDEX_WATER_SHELF_3 = 2
INDEX_WATER_SHELF_4 = 3
STATE_OFF = 0
STATE_ON = 1
def __init__(self):
if not debug_no_gpio:
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
self.states = [Hardware.STATE_OFF,Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF,
Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF, Hardware.STATE_OFF ]
self.pins = [ Hardware.PIN_FANS,
Hardware.PIN_LED_BOTTOM_SHELVES,
Hardware.PIN_LED_TOP_SHELVES,
Hardware.PIN_WATER_SPRAYER,
Hardware.PIN_WATER_SHELF_1,
Hardware.PIN_WATER_SHELF_2,
Hardware.PIN_WATER_SHELF_3,
Hardware.PIN_WATER_SHELF_4
]
if not debug_no_gpio:
for pin in self.pins:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # Default to OFF (HIGH means OFF for relays)
def _set_output(self, pin, state):
if debug_no_gpio:
print( f"_set_output(self, {pin}, {state})")
else:
if state == Hardware.STATE_ON:
GPIO.output(pin, GPIO.LOW)
else:
GPIO.output(pin, GPIO.HIGH)
def cleanup(self):
if not debug_no_gpio:
for pin in self.pins:
GPIO.output(pin, GPIO.LOW) # Default to OFF (HIGH means OFF for relays)
GPIO.cleanup()
def get_state(self, channel):
return self.states[channel]
def set_state(self, channel, state):
self.states[channel] = state
self._set_output(self.pins[ channel], state)
@property
def led_top_shelves(self):
return self.state_led_top_shelves
@led_top_shelves.setter
def led_top_shelves(self, state):
self.states[Hardware.LED_TOP_SHELVES] = state
self._set_output(Hardware.PIN_LED_TOP_SHELVES, state)
@property
def led_bottom_shelves(self):
return self.states[Hardware.LED_TOP_SHELVES]
@led_bottom_shelves.setter
def led_bottom_shelves(self, state):
self.states[Hardware.LED_BOTTOM_SHELVES] = state
self._set_output(Hardware.PIN_LED_BOTTOM_SHELVES, state)
@property
def fans(self):
return self.states[Hardware.FANS]
@fans.setter
def fans(self, state):
self.states[Hardware.FANS] = state
self._set_output(Hardware.PIN_FANS, state)
@property
def water_sprayer(self):
return self.states[Hardware.WATER_SPRAYER]
@water_sprayer.setter
def water_sprayer(self, state):
self.states[Hardware.WATER_SPRAYER] = state
self._set_output(Hardware.PIN_WATER_SPRAYER, state)
@property
def water_shelf_1(self):
return self.states[Hardware.WATER_SHELF_1]
@water_shelf_1.setter
def water_shelf_1(self, state):
self.states[Hardware.WATER_SHELF_1] = state
self._set_output(Hardware.PIN_WATER_SHELF_1, state)
@property
def water_shelf_2(self):
return self.states[Hardware.WATER_SHELF_2]
@water_shelf_2.setter
def water_shelf_2(self, state):
self.states[Hardware.WATER_SHELF_2] = state
self._set_output(Hardware.PIN_WATER_SHELF_2, state)
@property
def water_shelf_3(self):
return self.states[Hardware.WATER_SHELF_3]
@water_shelf_3.setter
def water_shelf_3(self, state):
self.states[Hardware.WATER_SHELF_3] = state
self._set_output(Hardware.PIN_WATER_SHELF_3, state)
@property
def water_shelf_4(self):
return self.states[Hardware.WATER_SHELF_4]
@water_shelf_4.setter
def water_shelf_4(self, state):
self.states[Hardware.WATER_SHELF_4] = state
self._set_output(Hardware.PIN_WATER_SHELF_4, state)
def set_state_water_shelf(self, index, state):
if index == Hardware.INDEX_WATER_SHELF_1: self.water_shelf_1 = state
if index == Hardware.INDEX_WATER_SHELF_2: self.water_shelf_2 = state
if index == Hardware.INDEX_WATER_SHELF_3: self.water_shelf_3 = state
if index == Hardware.INDEX_WATER_SHELF_4: self.water_shelf_4 = state
def get_state_water_shelf(self, index):
if index == Hardware.INDEX_WATER_SHELF_1: return self.water_shelf_1
if index == Hardware.INDEX_WATER_SHELF_2: return self.water_shelf_2
if index == Hardware.INDEX_WATER_SHELF_3: return self.water_shelf_3
if index == Hardware.INDEX_WATER_SHELF_4: return self.water_shelf_4
hardware = Hardware()
activation_labels = ["Water Shelf 1", "Water Shelf 2", "Water Shelf 3","Water Shelf 4"]
# Scheduler setup
scheduler = apscheduler.schedulers.background.BackgroundScheduler()
scheduler.start()
eventqueue = queue.Queue()
# --- Create scheduler jobs ---
job_id = 0
for shelf in range(4):
for day_of_week in range(7):
for hour in range(24):
# convert the cron days (mon, tue, thu 0, 1, 2,,,) to the day numbering in
# the program (sun, mon, tue... 0,1,2,3)
event = {"shelf": shelf,
"day_of_week": (day_of_week+1)%7,
"day": day_of_week, # for debug Purpose
"hour": hour}
if debug_cron:
scheduler.add_job(lambda e=event: eventqueue.put(e),
'cron',
second="0,30", id=f'job_id_{job_id}')
else:
scheduler.add_job(lambda e=event: eventqueue.put(e),
'cron',
day_of_week=day_of_week,
hour=hour,
minute=debug_cron_minute, id=f'job_id_{job_id}')
job_id += 1
print(f"Total scheduled jobs: {job_id}")
if debug:
print(scheduler.print_jobs())
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
YELLOW = "#ffeb3b"
ORANGE = "#FFA500"
BLUE = "#87CEEB"
DEFAULT_COLORS_CHANNEL = {
Hardware.LED_TOP_SHELVES :YELLOW,
Hardware.LED_BOTTOM_SHELVES:YELLOW,
Hardware.WATER_SPRAYER : BLUE,
Hardware.FANS : ORANGE,
Hardware.WATER_SHELF_1 :BLUE,
Hardware.WATER_SHELF_2 :BLUE,
Hardware.WATER_SHELF_3 :BLUE,
Hardware.WATER_SHELF_4 :BLUE,
}
# Timer values
timer_values = [ tk.StringVar() for _ in range(4) ]
# Function to toggle water shelf with timer
def toggle_water_shelf(index, button, label):
if debug:
print("toggle_water_shelf: toggle index", index, button, label)
if shelf_enabled_vars[index].get() == 1:
button.config(text=f"{label} ON", bg="#2ecc71")
try:
duration = int(timer_values[index].get())
except ValueError:
duration = 10
win.after(duration*1000, lambda button=button, text =f"{label} OFF" : button.config(text=text, bg="#2ecc71"))
hardware.set_state_water_shelf(index, Hardware.STATE_ON)
t = threading.Timer(duration, lambda index=index: hardware.set_state_water_shelf(index, Hardware.STATE_OFF))
t.start()
# Function to toggle general GPIO devices
def toggle_device(channel, button, label):
if debug:
print( f"toggle_device channel {channel}")
if hardware.get_state(channel ) == Hardware.STATE_OFF:
hardware.set_state(channel, Hardware.STATE_ON ) # If OFF, turn ON
button.config(text=f"{label} ON", bg="#2ecc71")
else:
hardware.set_state(channel, Hardware.STATE_OFF )
button.config(text=f"{label} OFF", bg=DEFAULT_COLORS_CHANNEL.get(channel, "#ffeb3b"))
# Main layout
frame = 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")
# General control buttons
def create_toggle_button(label, channel, row):
button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS_CHANNEL.get(channel, "#ffeb3b"), width=20, height=2)
button.config(command=lambda: toggle_device(channel, button, label))
button.grid(row=row, column=0, padx=10, pady=2, sticky="w")
return button
controls = [
("LED Top Shelves", Hardware.LED_TOP_SHELVES),
("LED Bottom Shelves", Hardware.LED_BOTTOM_SHELVES),
("Water Sprayer", Hardware.WATER_SPRAYER),
("Fans", Hardware.FANS),
]
for i, (label, channel) in enumerate(controls):
create_toggle_button(label, channel, i)
# Water shelf buttons
shelf_enabled_vars = [ tk.IntVar() for _ in range(4) ]
def create_water_shelf_button(label, index, row):
button = tk.Button(water_shelf_frame, text=label + " OFF", bg="#87CEFA", width=20, height=2)
button.config(command=lambda: toggle_water_shelf(index, 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[index])
shelf_checkbox.grid(row=row, column=1, padx=10, pady=5, sticky="w")
timer_entry = tk.Entry(water_shelf_frame, textvariable=timer_values[index], width=5)
timer_entry.grid(row=row, column=2, padx=10, pady=5, sticky="w")
timer_values[index].set("10")
timer_label = tk.Label(water_shelf_frame, text="Seconds")
timer_label.grid(row=row, column=3, padx=5, pady=5, sticky="w")
return button
button_shelf= []
button = create_water_shelf_button(f"Water Shelf 1", Hardware.INDEX_WATER_SHELF_1, 1)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 2", Hardware.INDEX_WATER_SHELF_2, 2)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 3", Hardware.INDEX_WATER_SHELF_3, 3)
button_shelf.append(button)
button = create_water_shelf_button(f"Water Shelf 4", Hardware.INDEX_WATER_SHELF_4, 4)
button_shelf.append(button)
# Scheduling section
schedule_frame = tk.Frame(win)
schedule_frame.pack(pady=10)
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 week
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
day_vars = [tk.IntVar() for day in days]
for i, day in enumerate(days):
tk.Checkbutton(schedule_frame, text=day,
variable=day_vars[i]
).grid(row=1, column=i, padx=30, pady=1)
# Time selection
time_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 Checkbuttons
hour_vars = [tk.IntVar() for _ in range(24)] # Create an IntVar for each hour of the day
# Create AM hours checkbuttons
for 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 = [ tk.IntVar() for i in range(1, 5)]
for i in range(1, 5):
tk.Checkbutton(schedule_frame,
text=f"Water Shelf {i}",
variable=shelf_schedule_vars[i-1]).grid(row=3, column=i, pady=5)
# Add this function to print the selected schedule
def print_schedule():
if False:
selected_days = [day for day, var in day_vars.items() if var.get() == 1]
selected_hours = [f"{hour} AM" for hour in range(12) if hour_vars[hour].get() == 1] + \
[f"{hour - 12} PM" for hour in range(12, 24) if hour_vars[hour].get() == 1]
if selected_days and selected_hours:
print(f"Scheduled Days: {', '.join(selected_days)}")
print(f"Scheduled Hours: {', '.join(selected_hours)}")
else:
print("No schedule selected.")
print_variables("day_vars ", day_vars)
print_variables("hour vars ", hour_vars)
print_variables("shelf_schedule_vars ", shelf_schedule_vars)
print_variables("shelf_enabled_vars ", shelf_enabled_vars)
# Exit button
def stopProgram():
hardware.cleanup()
win.quit()
# Create the Exit button first
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=1, command=stopProgram).pack(side="bottom", pady=1)
# Create the Print Schedule button
if debug:
tk.Button(win, text="Print Schedule", bg="#2980b9", fg="white", width=20, height=2, command=print_schedule).pack(pady=1)
def print_variables(label, array):
print(label, end="")
for a in array:
print( f" {a.get()}", end="")
print()
# --- Scheduler Callback ---
def tk_callback():
while True:
try:
# Read the event from background scheduler
event = eventqueue.get(block=False)
if debug:
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()
Re: Need help with using a scheduler to activate buttons on a GUI.
I managed to get this code working, I still need to add features to the GUI, clean the code and align stuff better, but it is working now.
;o)
I will update this post with my final code.
For now, here is my current working code:
Cheers,
;o)
I will update this post with my final code.
For now, here is my current working code:
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # OFF state for relays
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b",
18: "#ffeb3b",
5: "#FFA500",
25: "#87CEEB",
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_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 variables
day_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)}
# Main layout
frame = 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 button
controls = [
("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 button
water_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 Frame
scheduler_frame = tk.Frame(win)
scheduler_frame.pack(side="bottom", pady=5)
# Function to toggle devices
def 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"))
# Scheduler setup
scheduler = BackgroundScheduler()
scheduler.start()
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]
for day in selected_days:
for hour in selected_hours:
for shelf in selected_shelves:
scheduler.add_job(lambda s=shelf: toggle_water_shelf(s, water_shelf_buttons[s], f"Water Shelf {s}"), '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 selection
tk.Label(scheduler_frame, text="Days:").grid(row=1, column=0, sticky="n")
for i, (day, var) in enumerate(day_vars.items()):
tk.Checkbutton(scheduler_frame, text=day, variable=var).grid(row=1, column=i+1, sticky="w")
# Hours selection
tk.Label(scheduler_frame, text="Hours:").grid(row=2, column=0, sticky="w")
for i, (hour, var) in enumerate(hour_vars.items()):
tk.Checkbutton(scheduler_frame, text=str(hour), variable=var).grid(row=2 + (i // 12), column=(i % 12) + 1, sticky="w")
# Shelves selection
tk.Label(scheduler_frame, text="Shelves:").grid(row=4, column=0, sticky="w")
for i, (shelf, var) in enumerate(schedule_shelf_vars.items()):
tk.Checkbutton(scheduler_frame, text=f"Shelf {i+1}", variable=var).grid(row=4, column=i+1, sticky="w")
# Schedule Button
tk.Button(scheduler_frame, text="Set Schedule", command=schedule_watering).grid(row=5, 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.")
# Exit program function
def stopProgram():
scheduler.shutdown()
GPIO.cleanup()
win.quit()
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=2, command=stopProgram).pack(side="bottom", pady=1)
win.mainloop()
Re: Need help with using a scheduler to activate buttons on a GUI.
OK, I updated the code to now have a way to set ON and OFF duration for the lighting as microgreen need a resting period.
I am currently testing functionality and all seems good so far...
I still have problem figuring out why the checkboxes in the scheduler are not spaced evenly.
Any recommendation to solve this would be appreciated.
Here is the GUI:
And here is my current code:
Cheers,
I am currently testing functionality and all seems good so far...
I still have problem figuring out why the checkboxes in the scheduler are not spaced evenly.
Any recommendation to solve this would be appreciated.
Here is the GUI:
- GUI6.png
- GUI6.png (95.22 KiB) Viewed 5915 times
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # OFF state for relays
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b",
18: "#ffeb3b",
5: "#FFA500",
25: "#87CEEB",
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_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 variables
day_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 settings
led_on_durations = {pin: tk.StringVar(value="12") for pin in [17, 18]}
led_off_durations = {pin: tk.StringVar(value="6") for pin in [17, 18]}
# Scheduler setup
scheduler = 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 layout
frame = 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=20, height=2)
button.config(command=lambda: toggle_device(pin, button, label))
button.grid(row=row, column=0, padx=2, pady=2, sticky="w")
return button
controls = [
("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=15, 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 button
water_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 Frame
scheduler_frame = tk.Frame(win)
scheduler_frame.pack(side="bottom", pady=5)
# Function to toggle devices
def 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"))
# Scheduler setup
#scheduler = BackgroundScheduler()
#scheduler.start()
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
schedule_popup = tk.Toplevel(win, padx=20, pady=10)
schedule_popup.title("")
tk.Label(schedule_popup, text="Scheduled Confirmation", font= 34, padx=30, pady=5).pack()
tk.Label(schedule_popup, text=f"Scheduled: {', '.join([f'Shelf {list(pins.values()).index(s) -3}' 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:
scheduler.add_job(lambda s=shelf: toggle_water_shelf(s, water_shelf_buttons[s], f"Water Shelf {s}"), '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 selection
tk.Label(scheduler_frame, text="Days:").grid(row=1, column=0, sticky="n")
for i, (day, var) in enumerate(day_vars.items()):
tk.Checkbutton(scheduler_frame, text=day, variable=var).grid(row=1, column=i+1, sticky="w")
# Hours selection
tk.Label(scheduler_frame, text="Hours:").grid(row=2, column=0, sticky="w")
for i, (hour, var) in enumerate(hour_vars.items()):
tk.Checkbutton(scheduler_frame, text=str(hour), variable=var).grid(row=2 + (i // 12), column=(i % 12) + 1, sticky="w")
# Shelves selection
tk.Label(scheduler_frame, text="Shelves:").grid(row=4, column=0, sticky="w")
for i, (shelf, var) in enumerate(schedule_shelf_vars.items()):
tk.Checkbutton(scheduler_frame, text=f"Shelf {i+1}", variable=var).grid(row=4, column=i+1, sticky="w")
# Schedule Button
tk.Button(scheduler_frame, text="Set Schedule", bg="green", fg="white", command=schedule_watering).grid(row=5, 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 Controls
for i, (label, pin) in enumerate([("LED Top Shelves", 17), ("LED Bottom Shelves", 18)]):
button = tk.Button(left_frame, text=label + " OFF", bg=DEFAULT_COLORS[pin], width=20, 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.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 function
def stopProgram():
scheduler.shutdown()
GPIO.cleanup()
win.quit()
tk.Button(win, text="Exit", bg="#c0392b", fg="white", width=20, height=2, command=stopProgram).pack(side="bottom", pady=1)
win.mainloop()
Re: Need help with using a scheduler to activate buttons on a GUI.
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.
Re: Need help with using a scheduler to activate buttons on a GUI.
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.kheylen25 wrote: ↑Tue Apr 01, 2025 12:52 pmThe 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:
- GUI7.png
- GUI7.png (93.79 KiB) Viewed 5875 times
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # OFF state for relays
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b",
18: "#ffeb3b",
5: "#FFA500",
25: "#87CEEB",
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_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 variables
day_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 settings
led_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 setup
scheduler = 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 layout
frame = 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 button
controls = [
("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 button
water_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 Frame
scheduler_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 devices
def 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 selection
tk.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 selection
tk.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 selection
tk.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 Button
tk.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 Controls
for 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 function
def 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()
Re: Need help with using a scheduler to activate buttons on a GUI.
Well, I'm pretty satisfied with what I got for now, I added a section on the GUI to show Temperature and Humidity values coming from a AM2302/DHT22 Digital Temperature and Humidity Sensor.
It now shows time in AM an PM values instead of 0 - 24.
Here is the actual GUI:
And here is the possible final code:
Pretty cool project...
Cheers,
It now shows time in AM an PM values instead of 0 - 24.
Here is the actual GUI:
- GUI9.png
- GUI9.png (101.15 KiB) Viewed 5825 times
Code: Select all
import tkinter as tk
import RPi.GPIO as GPIO
import threading
import time
from datetime import datetime
import queue
from apscheduler.schedulers.background import BackgroundScheduler
import logging
import adafruit_dht
import board
# Enable detailed logging
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
# GPIO Setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# DHT22 Sensor Setup
DHT_SENSOR = adafruit_dht.DHT22(board.D4) # Replace with actual GPIO pin
# Define GPIO pins
pins = {
"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 output
for pin in pins.values():
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH) # OFF state for relays
# Initialize GUI
win = tk.Tk()
win.title("Microgreens Farm Controller")
win.geometry("800x480")
# Default colors for OFF state
DEFAULT_COLORS = {
17: "#ffeb3b",
18: "#ffeb3b",
5: "#FFA500",
25: "#87CEEB",
}
# Checkbox control variables
shelf_enabled_vars = {pins[f"Water Shelf {i}"]: tk.IntVar() for i in range(1, 5)}
# Timer values
timer_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 variables
day_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 settings
led_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 setup
scheduler = 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()
# Temperature & Humidity Sensor Frame
temp_frame = tk.Frame(win)
temp_frame.pack(side="top", pady=0)
temp_label = tk.Label(temp_frame, text="Temp: --", font=("Arial", 12))
temp_label.pack(side="left", padx=5)
humidity_label = tk.Label(temp_frame, text="Humidity: --", font=("Arial", 12))
humidity_label.pack(side="right", padx=5)
"""Reads temperature and humidity from DHT22 and updates the GUI."""
def update_temp_humidity():
try:
# Read values from the DHT22 sensor
temperature = DHT_SENSOR.temperature
humidity = DHT_SENSOR.humidity
except RuntimeError as error:
logging.error(f"Sensor error: {error}")
temperature, humidity = None, None
if humidity is not None and temperature is not None:
# Update the labels with the sensor readings
temp_label.config(text=f"Temp: {temperature:.1f}\u00B0C")
humidity_label.config(text=f"Humidity: {humidity:.1f}%")
else:
# If there is an error with the sensor, display "--"
temp_label.config(text="Temp: --")
humidity_label.config(text="Humidity: --")
# Schedule the next update in 5 seconds
win.after(5000, update_temp_humidity)
# Start the first update when the program starts
update_temp_humidity()
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 layout
frame = 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 button
controls = [
("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 button
water_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 Frame
scheduler_frame = tk.Frame(win)
scheduler_frame.pack(side="top", pady=1)
# Scheduler (Shelves Section)
shelves_frame = tk.Frame(win)
shelves_frame.pack(side="top", pady=0)
# Scheduler (Days Section)
days_frame = tk.Frame(win)
days_frame.pack(side="top", pady=0)
# Scheduler (Hours Section)
hours_frame = tk.Frame(win)
hours_frame.pack(side="top", pady=0)
# Scheduler (Set Schedule Button)
setButton_frame = tk.Frame(win)
setButton_frame.pack(side="top", pady=0)
# Function to toggle devices
def 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 selection
tk.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 selection with AM/PM labels
tk.Label(hours_frame, text="Hours:").grid(row=2, column=0, sticky="w")
hour_vars = {hour: tk.IntVar() for hour in range(24)}
for i, (hour, var) in enumerate(hour_vars.items()):
am_pm_label = f"{hour % 12 if hour % 12 else 12}{'am' if hour < 12 else 'pm'}"
tk.Checkbutton(hours_frame, text=am_pm_label, variable=var).grid(row=2 + (i // 12), column=(i % 12) + 1, sticky="w")
# Shelves selection
tk.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 Button
tk.Button(setButton_frame, text="Set Schedule", bg="green", fg="white", command=schedule_watering).grid(row=1, column=2, columnspan=8, pady=0)
def enqueue_event(shelf):
logging.debug(f"Enqueueing event for {shelf}")
print(f"Watering {shelf} for the scheduled duration.")
# LED Controls
for 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 function
def 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=0)
win.mainloop()
Cheers,
Return to "General programming discussion"
Jump to
- Community
- General discussion
- Announcements
- Other languages
- Deutsch
- Español
- Français
- Italiano
- Nederlands
- 日本語
- Polski
- Português
- Русский
- Türkçe
- User groups and events
- Raspberry Pi Official Magazine
- Using the Raspberry Pi
- Beginners
- Troubleshooting
- Advanced users
- Assistive technology and accessibility
- Education
- Picademy
- Teaching and learning resources
- Staffroom, classroom and projects
- Astro Pi
- Mathematica
- High Altitude Balloon
- Weather station
- Programming
- C/C++
- Java
- Python
- Scratch
- Other programming languages
- Windows 10 for IoT
- Wolfram Language
- Bare metal, Assembly language
- Graphics programming
- OpenGLES
- OpenVG
- OpenMAX
- General programming discussion
- Projects
- Networking and servers
- Automation, sensing and robotics
- Graphics, sound and multimedia
- Other projects
- Media centres
- Gaming
- AIY Projects
- Hardware and peripherals
- Camera board
- Compute Module
- Official Display
- HATs and other add-ons
- Device Tree
- Interfacing (DSI, CSI, I2C, etc.)
- Keyboard computers (400, 500, 500+)
- Raspberry Pi Pico
- General
- SDK
- MicroPython
- Other RP2040 boards
- Zephyr
- Rust
- AI Accelerator
- AI Camera - IMX500
- Hailo
- Software
- Raspberry Pi OS
- Raspberry Pi Connect
- Raspberry Pi Desktop for PC and Mac
- Beta testing
- Other
- Android
- Debian
- FreeBSD
- Gentoo
- Linux Kernel
- NetBSD
- openSUSE
- Plan 9
- Puppy
- Arch
- Pidora / Fedora
- RISCOS
- Ubuntu
- Ye Olde Pi Shoppe
- For sale
- Wanted
- Off topic
- Off topic discussion