mirror of
https://github.com/pimoroni/grow-python
synced 2025-10-25 15:19:23 +00:00
Tweaks, fixes, linting and docs
This commit is contained in:
203
examples/tools/calibrate-pump.py
Normal file
203
examples/tools/calibrate-pump.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import time
|
||||
import logging
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from fonts.ttf import RobotoMedium as UserFont
|
||||
|
||||
import ST7735
|
||||
from grow.moisture import Moisture
|
||||
from grow.pump import Pump
|
||||
|
||||
|
||||
"""
|
||||
Auto water a single target with the channel/pump selected below.
|
||||
|
||||
This example is useful for calibrating watering settings and will give you a good idea
|
||||
what speed/time you need to run your pump for in order to deliver a thorough watering.
|
||||
|
||||
The buttons allow you to find tune and test the pump settings.
|
||||
|
||||
A = Test settings
|
||||
B = Select setting to change
|
||||
X = Decrease value
|
||||
Y = Increase value
|
||||
"""
|
||||
|
||||
# Channel settings
|
||||
pump_channel = 3
|
||||
moisture_channel = 3
|
||||
|
||||
# Default watering settings
|
||||
dry_level = 0.7 # Saturation level considered dry
|
||||
dose_speed = 0.63 # Pump speed for water dose
|
||||
dose_time = 0.96 # Time (in seconds) for water dose
|
||||
|
||||
# Here be dragons!
|
||||
FPS = 15 # Display framerate
|
||||
NUM_SAMPLES = 10 # Number of saturation level samples to average over
|
||||
DOSE_FREQUENCY = 30.0 # Minimum time between automatic waterings (in seconds)
|
||||
|
||||
BUTTONS = [5, 6, 16, 24]
|
||||
LABELS = ["A", "B", "X", "Y"]
|
||||
|
||||
p = Pump(pump_channel)
|
||||
m = Moisture(moisture_channel)
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
|
||||
mode = 0
|
||||
last_dose = time.time()
|
||||
saturation = [1.0 for _ in range(NUM_SAMPLES)]
|
||||
|
||||
display = ST7735.ST7735(
|
||||
port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=80000000
|
||||
)
|
||||
|
||||
display.begin()
|
||||
|
||||
font = ImageFont.truetype(UserFont, 12)
|
||||
image = Image.new("RGBA", (display.width, display.height), color=(0, 0, 0))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
|
||||
level=logging.INFO,
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def handle_button(pin):
|
||||
global mode, last_dose, dose_time, dose_speed, dry_level
|
||||
index = BUTTONS.index(pin)
|
||||
label = LABELS[index]
|
||||
if label == "A": # Test
|
||||
logging.info("Manual watering triggered.")
|
||||
p.dose(dose_speed, dose_time, blocking=False)
|
||||
last_dose = time.time()
|
||||
|
||||
if label == "B": # Switch setting
|
||||
mode += 1
|
||||
mode %= 3 # Wrap 0, 1, 2 (Time, Speed, Dry level)
|
||||
|
||||
if label == "Y": # Inc. setting
|
||||
if mode == 0:
|
||||
dose_time += 0.01
|
||||
logging.info("Dose time increased to: {:.2f}".format(dose_time))
|
||||
elif mode == 1:
|
||||
dose_speed += 0.01
|
||||
logging.info("Dose speed increased to: {:.2f}".format(dose_speed))
|
||||
elif mode == 2:
|
||||
dry_level += 0.01
|
||||
logging.info("Dry level increased to: {:.2f}".format(dry_level))
|
||||
|
||||
if label == "X": # Dec. setting
|
||||
if mode == 0:
|
||||
dose_time -= 0.01
|
||||
logging.info("Dose time decreased to: {:.2f}".format(dose_time))
|
||||
elif mode == 1:
|
||||
dose_speed -= 0.01
|
||||
logging.info("Dose speed decreased to: {:.2f}".format(dose_speed))
|
||||
elif mode == 2:
|
||||
dry_level -= 0.01
|
||||
logging.info("Dry level decreased to: {:.2f}".format(dry_level))
|
||||
|
||||
|
||||
# Bind the button handler (above) to all buttons
|
||||
for pin in BUTTONS:
|
||||
GPIO.add_event_detect(pin, GPIO.FALLING, handle_button, bouncetime=150)
|
||||
|
||||
|
||||
current_saturation = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
# New moisture readings are available approximately 1/sec
|
||||
# push them into the list for averagering
|
||||
if m.new_data:
|
||||
current_saturation = m.saturation
|
||||
saturation.append(current_saturation)
|
||||
saturation = saturation[-NUM_SAMPLES:]
|
||||
|
||||
avg_saturation = sum(saturation) / float(NUM_SAMPLES)
|
||||
|
||||
# Trigger a dose of water if the average saturation is less than the specified dry level
|
||||
# dose frequency is rate limited, so this doesn't re-trigger before the moistrure sensor
|
||||
# has had the opportunity to catch up.
|
||||
if avg_saturation < dry_level and (time.time() - last_dose) > DOSE_FREQUENCY:
|
||||
p.dose(dose_speed, dose_time)
|
||||
logging.info(
|
||||
"Auto watering. Saturation: {:.2f} (Dry: {:.2f})".format(
|
||||
avg_saturation, dry_level
|
||||
)
|
||||
)
|
||||
last_dose = time.time()
|
||||
|
||||
draw.rectangle((0, 0, display.width, display.height), (0, 0, 0))
|
||||
|
||||
# Current and average saturation
|
||||
draw.text(
|
||||
(5 + display.width // 2, 16),
|
||||
"Sat: {:.3f}".format(current_saturation),
|
||||
font=font,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
draw.text(
|
||||
(5 + display.width // 2, 32),
|
||||
"AVG: {:.3f}".format(avg_saturation),
|
||||
font=font,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
|
||||
# Selected setting box
|
||||
draw.rectangle(
|
||||
(0, 16 + (16 * mode), display.width // 2, 31 + (16 * mode)), (30, 30, 30)
|
||||
)
|
||||
|
||||
draw.text(
|
||||
(5, 16),
|
||||
"Time: {:.2f}".format(dose_time),
|
||||
font=font,
|
||||
fill=(255, 255, 255) if mode == 0 else (128, 128, 128),
|
||||
)
|
||||
draw.text(
|
||||
(5, 32),
|
||||
"Speed: {:.2f}".format(dose_speed),
|
||||
font=font,
|
||||
fill=(255, 255, 255) if mode == 1 else (128, 128, 128),
|
||||
)
|
||||
draw.text(
|
||||
(5, 48),
|
||||
"Dry lvl: {:.2f}".format(dry_level),
|
||||
font=font,
|
||||
fill=(255, 255, 255) if mode == 2 else (128, 128, 128),
|
||||
)
|
||||
|
||||
# Button lavel backgrounds
|
||||
draw.rectangle((0, 0, 42, 14), (255, 255, 255))
|
||||
draw.rectangle((display.width - 15, 0, display.width, 14), (255, 255, 255))
|
||||
draw.rectangle(
|
||||
(display.width - 15, display.height - 14, display.width, display.height),
|
||||
(255, 255, 255),
|
||||
)
|
||||
draw.rectangle((0, display.height - 14, 42, display.height), (255, 255, 255))
|
||||
|
||||
# Button labels
|
||||
draw.text((5, 0), "test", font=font, fill=(0, 0, 0))
|
||||
draw.text((5, display.height - 16), "next", font=font, fill=(0, 0, 0))
|
||||
draw.text((display.width - 10, 0), "-", font=font, fill=(0, 0, 0))
|
||||
draw.text(
|
||||
(display.width - 12, display.height - 15), "+", font=font, fill=(0, 0, 0)
|
||||
)
|
||||
|
||||
display.display(image)
|
||||
time.sleep(1.0 / FPS)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(
|
||||
"Dose Time: {:.2f} Dose Speed: {:.2f}, Dry Level: {:.2f}".format(
|
||||
dose_time, dose_speed, dry_level
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user