Files
grow-python/examples/display.py
2020-05-26 16:22:45 +01:00

267 lines
7.2 KiB
Python

import time
import ST7735
import random
import math
import logging
from PIL import Image, ImageDraw, ImageFont
from fonts.ttf import RobotoMedium as UserFont
import RPi.GPIO as GPIO
from grow.moisture import Moisture
from grow.pump import Pump
# Level at which a watering event is triggered
trigger_level = [
0.5,
0.5,
0.5
]
# Level at which the alarm is sounded (watering has failed?)
alarm_level = [
0.2,
0.2,
0.2
]
# Dose settings: Pump Speed, and Dose Time
dose_settings = [
(0.7, 0.7),
(0.7, 0.7),
(0.7, 0.7)
]
# Time the last watering was administered
last_dose = [
time.time(),
time.time(),
time.time()
]
BUTTONS = [5, 6, 16, 24]
LABELS = ['A', 'B', 'X', 'Y']
CHANNEL_COUNT = 3
MINIMUM_WATERING_DELAY = 30 # Minimum time between waterings in seconds
channel_selected = 0
alarm = False
sensors = [
Moisture(channel=1),
Moisture(channel=2),
Moisture(channel=3)
]
pumps = [
Pump(channel=1),
Pump(channel=2),
Pump(channel=3)
]
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 channel_selected, alarm
index = BUTTONS.index(pin)
label = LABELS[index]
if label == 'A': # Select Channel
channel_selected += 1
channel_selected %= CHANNEL_COUNT
if label == 'B': # Cancel Alarm
alarm = False
if label == 'X': # Set Wet Point
pass
if label == 'Y': # Set Dry Point
pass
print("Button {} pressed! Channel: {}".format(label, channel_selected))
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
for pin in BUTTONS:
GPIO.add_event_detect(pin, GPIO.FALLING, handle_button, bouncetime=150)
bar_colours = [
(192, 225, 254), # Blue
(196, 255, 209), # Green
(255, 243, 192), # Yellow
(254, 192, 192) # Red
]
label_colours = [
(32, 137, 251), # Blue
(100, 255, 124), # Green
(254, 219, 82), # Yellow
(254, 82, 82), # Red
]
# Only the ALPHA channel is used from these images
icon_drop = Image.open("icons/icon-drop.png")
icon_nodrop = Image.open("icons/icon-nodrop.png")
icon_rightarrow = Image.open("icons/icon-rightarrow.png")
icon_snooze = Image.open("icons/icon-snooze.png")
plants = []
# Load all of the plant icons
for x in range(1, 15):
plants.append(Image.open("icons/flat-{}.png".format(x)))
# Pick a random selection of plant icons to display on screen
# TODO: Make this the default, but allow override in settings
picked = random.sample(plants, 3)
CHANNEL_W = 40
CHANNEL_M = 2
# Set up the ST7735 SPI Display
display = ST7735.ST7735(
port=0,
cs=1,
dc=9,
backlight=12,
rotation=270,
spi_speed_hz=80000000
)
display.begin()
WIDTH, HEIGHT = display.width, display.height
# Set up our canvas and prepare for drawing
image = Image.new("RGBA", (WIDTH, HEIGHT), color=(255, 255, 255))
font = ImageFont.truetype(UserFont, 14)
draw = ImageDraw.Draw(image)
def indicator_color(value, r=None):
if r is None:
r = bar_colours
if value == 1.0:
return r[-1]
if value == 0.0:
return r[0]
value *= len(r) - 1
a = int(math.floor(value))
b = a + 1
blend = float(value - a)
r, g, b = [int(((r[b][i] - r[a][i]) * blend) + r[a][i]) for i in range(3)]
return (r, g, b)
def icon(image, icon, position, color):
col = Image.new("RGBA", (20, 20), color=color)
image.paste(col, position, mask=icon)
def plant(image, plant, channel, available=True):
x = [18, 58, 98][channel]
y = HEIGHT - plant.height
mask = plant
if not available:
plant = plant.convert('LA').convert('RGB')
image.paste(plant, (x, y), mask=mask)
def update():
global alarm
t = time.time()
for channel in range(0, 3):
if sensors[channel].active:
sat = sensors[channel].saturation
if sat < trigger_level[channel]:
if t - last_dose[channel] > MINIMUM_WATERING_DELAY:
dose_speed, dose_time = dose_settings[channel]
pumps[channel].dose(dose_speed, dose_time)
last_dose[channel] = t
logging.info("Watering Channel: {} - rate {:.2f} for {:.2f}sec".format(channel + 1, dose_speed, dose_time))
if sat < alarm_level[channel]:
alarm = True
def render():
t = time.time()
# Icon backdrops
draw.rectangle((0, 0, 19, 19), (32, 138, 251))
draw.rectangle((0, HEIGHT - 19, 19, HEIGHT), (255, 255, 255))
draw.rectangle((WIDTH - 20, 0, WIDTH, 19), (75, 166, 252))
draw.rectangle((WIDTH - 20, HEIGHT - 19, WIDTH, HEIGHT), (254, 218, 80))
# Icons
icon(image, icon_rightarrow, (0, 0), (255, 255, 255))
icon(image, icon_drop, (WIDTH - 20, 0), (255, 255, 255))
icon(image, icon_nodrop, (WIDTH - 20, HEIGHT - 20), (255, 255, 255))
# Draw the snooze icon- will be pulsing red if the alarm state is True
r = 129
if alarm:
r = int(((math.sin(t * 3 * math.pi) + 1.0) / 2.0) * 255)
icon(image, icon_snooze, (0, HEIGHT - 20), (r, 129, 129))
# Saturation amounts from each sensor
c1 = 1.0 - sensors[0].saturation
c2 = 1.0 - sensors[1].saturation
c3 = 1.0 - sensors[2].saturation
# Channel presence detection
ca1 = sensors[0].active
ca2 = sensors[1].active
ca3 = sensors[2].active
draw.rectangle((21, 0, 138, HEIGHT), (255, 255, 255)) # Erase channel area
# Draw background bars
draw.rectangle((21, int(c1 * HEIGHT), 58, HEIGHT), indicator_color(c1) if ca1 else (229, 229, 229))
draw.rectangle((61, int(c2 * HEIGHT), 98, HEIGHT), indicator_color(c2) if ca2 else (229, 229, 229))
draw.rectangle((101, int(c3 * HEIGHT), 138, HEIGHT), indicator_color(c3) if ca3 else (229, 229, 229))
# Draw plant images
for p in range(3):
plant(image, picked[p], p, [ca1, ca2, ca3][p])
# Channel selection icons
draw.rectangle((33, 2, 48, 17), indicator_color(c1, label_colours) if ca1 else (129, 129, 129))
draw.rectangle((33 + 40, 2, 48 + 40, 17), indicator_color(c2, label_colours) if ca2 else (129, 129, 129))
draw.rectangle((33 + 80, 2, 48 + 80, 17), indicator_color(c3, label_colours) if ca3 else (129, 129, 129))
selected_x = 21 + (40 * channel_selected) + 10
draw.rectangle((selected_x, 0, selected_x + 19, 20), indicator_color([c1, c2, c3][channel_selected], label_colours) if [ca1, ca2, ca3][channel_selected] else (129, 129, 129))
# TODO: replace with graphic, since PIL has no anti-aliasing
draw.polygon([
(selected_x, 20),
(selected_x + 9, 25),
(selected_x + 19, 20)
],
fill=indicator_color([c1, c2, c3][channel_selected], label_colours) if [ca1, ca2, ca3][channel_selected] else (129, 129, 129))
# TODO: replace number text with graphic
draw.text((33 + 3, 2), "1", font=font, fill=(255, 255, 255))
draw.text((33 + 40 + 4, 2), "2", font=font, fill=(255, 255, 255))
draw.text((33 + 80 + 4, 2), "3", font=font, fill=(255, 255, 255))
def main():
while True:
update()
render()
display.display(image.convert("RGB"))
# 5 FPS
time.sleep(1.0 / 5)
if __name__ == "__main__":
main()