mirror of
https://github.com/pimoroni/grow-python
synced 2025-10-25 15:19:23 +00:00
Start tidying display.py
This commit is contained in:
@@ -2,6 +2,7 @@ import time
|
|||||||
import ST7735
|
import ST7735
|
||||||
import random
|
import random
|
||||||
import math
|
import math
|
||||||
|
import logging
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
from fonts.ttf import RobotoMedium as UserFont
|
from fonts.ttf import RobotoMedium as UserFont
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
@@ -9,14 +10,33 @@ import RPi.GPIO as GPIO
|
|||||||
from grow.moisture import Moisture
|
from grow.moisture import Moisture
|
||||||
from grow.pump import Pump
|
from grow.pump import Pump
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
# Level at which a watering event is triggered
|
||||||
GPIO.setwarnings(False)
|
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)
|
||||||
|
]
|
||||||
|
|
||||||
BUTTONS = [5, 6, 16, 24]
|
BUTTONS = [5, 6, 16, 24]
|
||||||
LABELS = ['A', 'B', 'X', 'Y']
|
LABELS = ['A', 'B', 'X', 'Y']
|
||||||
|
CHANNEL_COUNT = 3
|
||||||
|
|
||||||
channel_count = 3
|
|
||||||
channel_selected = 0
|
channel_selected = 0
|
||||||
|
alarm = False
|
||||||
|
|
||||||
sensors = [
|
sensors = [
|
||||||
Moisture(channel=1),
|
Moisture(channel=1),
|
||||||
@@ -30,22 +50,70 @@ pumps = [
|
|||||||
Pump(channel=3)
|
Pump(channel=3)
|
||||||
]
|
]
|
||||||
|
|
||||||
GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
logging.basicConfig(
|
||||||
|
format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
|
||||||
|
level=logging.INFO,
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
font = ImageFont.truetype(UserFont, 14)
|
|
||||||
|
|
||||||
def handle_button(pin):
|
def handle_button(pin):
|
||||||
global channel_selected
|
global channel_selected, alarm
|
||||||
index = BUTTONS.index(pin)
|
index = BUTTONS.index(pin)
|
||||||
label = LABELS[index]
|
label = LABELS[index]
|
||||||
if label == 'A':
|
if label == 'A': # Select Channel
|
||||||
channel_selected += 1
|
channel_selected += 1
|
||||||
channel_selected %= channel_count
|
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))
|
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:
|
for pin in BUTTONS:
|
||||||
GPIO.add_event_detect(pin, GPIO.FALLING, handle_button, bouncetime=150)
|
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(
|
display = ST7735.ST7735(
|
||||||
port=0,
|
port=0,
|
||||||
cs=1,
|
cs=1,
|
||||||
@@ -54,25 +122,18 @@ display = ST7735.ST7735(
|
|||||||
rotation=270,
|
rotation=270,
|
||||||
spi_speed_hz=80000000
|
spi_speed_hz=80000000
|
||||||
)
|
)
|
||||||
|
display.begin()
|
||||||
|
WIDTH, HEIGHT = display.width, display.height
|
||||||
|
|
||||||
ramp = [
|
# Set up our canvas and prepare for drawing
|
||||||
(192, 225, 254), # Blue
|
image = Image.new("RGBA", (WIDTH, HEIGHT), color=(255, 255, 255))
|
||||||
(196, 255, 209), # Green
|
font = ImageFont.truetype(UserFont, 14)
|
||||||
(255, 243, 192), # Yellow
|
draw = ImageDraw.Draw(image)
|
||||||
(254, 192, 192) # Red
|
|
||||||
]
|
|
||||||
|
|
||||||
ramp_sat = [
|
|
||||||
(32, 137, 251), # Blue
|
|
||||||
(100, 255, 124), # Green
|
|
||||||
(254, 219, 82), # Yellow
|
|
||||||
(254, 82, 82), # Red
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def indicator_color(value, r=None):
|
def indicator_color(value, r=None):
|
||||||
if r is None:
|
if r is None:
|
||||||
r = ramp
|
r = bar_colours
|
||||||
if value == 1.0:
|
if value == 1.0:
|
||||||
return r[-1]
|
return r[-1]
|
||||||
if value == 0.0:
|
if value == 0.0:
|
||||||
@@ -88,96 +149,97 @@ def indicator_color(value, r=None):
|
|||||||
return (r, g, b)
|
return (r, g, b)
|
||||||
|
|
||||||
|
|
||||||
display.begin()
|
|
||||||
|
|
||||||
WIDTH, HEIGHT = display.width, display.height
|
|
||||||
|
|
||||||
# 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 = []
|
|
||||||
|
|
||||||
for x in range(1, 15):
|
|
||||||
plants.append(Image.open("icons/flat-{}.png".format(x)))
|
|
||||||
|
|
||||||
CHANNEL_W = 40
|
|
||||||
CHANNEL_M = 2
|
|
||||||
|
|
||||||
print(WIDTH, HEIGHT)
|
|
||||||
|
|
||||||
image = Image.new("RGBA", (WIDTH, HEIGHT), color=(255, 255, 255))
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
def icon(image, icon, position, color):
|
def icon(image, icon, position, color):
|
||||||
col = Image.new("RGBA", (20, 20), color=color)
|
col = Image.new("RGBA", (20, 20), color=color)
|
||||||
image.paste(col, position, mask=icon)
|
image.paste(col, position, mask=icon)
|
||||||
|
|
||||||
icon(image, icon_rightarrow, (0, 0), (255, 255, 255))
|
|
||||||
icon(image, icon_snooze, (0, HEIGHT - 20), (129, 129, 129))
|
|
||||||
icon(image, icon_drop, (WIDTH - 20, 0), (255, 255, 255))
|
|
||||||
icon(image, icon_nodrop, (WIDTH - 20, HEIGHT - 20), (255, 255, 255))
|
|
||||||
|
|
||||||
def plant(image, plant, channel):
|
def plant(image, plant, channel, available=True):
|
||||||
x = [18, 58, 98][channel]
|
x = [18, 58, 98][channel]
|
||||||
y = HEIGHT - plant.height
|
y = HEIGHT - plant.height
|
||||||
image.paste(plant, (x, y), mask=plant)
|
mask = plant
|
||||||
|
if not available:
|
||||||
|
plant = plant.convert('LA').convert('RGB')
|
||||||
|
image.paste(plant, (x, y), mask=mask)
|
||||||
|
|
||||||
picked = random.sample(plants, 3)
|
|
||||||
|
|
||||||
# image.save("test.png")
|
def update():
|
||||||
|
pass
|
||||||
|
|
||||||
while True:
|
|
||||||
t = time.time() / 10.0
|
def render():
|
||||||
#c1 = (math.sin(math.pi + t * math.pi) + 1.0) / 2.0
|
t = time.time()
|
||||||
#c2 = (math.sin(t * math.pi) + 1.0) / 2.0
|
|
||||||
#c3 = (math.sin(math.pi + t * math.pi) + 1.0) / 2.0
|
# 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
|
c1 = 1.0 - sensors[0].saturation
|
||||||
c2 = 1.0 - sensors[1].saturation
|
c2 = 1.0 - sensors[1].saturation
|
||||||
c3 = 1.0 - sensors[2].saturation
|
c3 = 1.0 - sensors[2].saturation
|
||||||
print(c1, c2, c3)
|
|
||||||
|
# Channel presence detection
|
||||||
|
# TODO: Implement presence detection that doesn't trigger on random noise!
|
||||||
|
ca1 = sensors[0].moisture > 0
|
||||||
|
ca2 = sensors[1].moisture > 0
|
||||||
|
ca3 = sensors[2].moisture > 0
|
||||||
|
|
||||||
draw.rectangle((21, 0, 138, HEIGHT), (255, 255, 255)) # Erase channel area
|
draw.rectangle((21, 0, 138, HEIGHT), (255, 255, 255)) # Erase channel area
|
||||||
|
|
||||||
# Draw background bars
|
# Draw background bars
|
||||||
draw.rectangle((21, int(c1 * HEIGHT), 58, HEIGHT), indicator_color(c1))
|
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))
|
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))
|
draw.rectangle((101, int(c3 * HEIGHT), 138, HEIGHT), indicator_color(c3) if ca3 else (229, 229, 229))
|
||||||
|
|
||||||
# Draw plant images
|
# Draw plant images
|
||||||
for p in range(3):
|
for p in range(3):
|
||||||
plant(image, picked[p], p)
|
plant(image, picked[p], p, [ca1, ca2, ca3][p])
|
||||||
|
|
||||||
# Channel selection icons
|
# Channel selection icons
|
||||||
draw.rectangle((33, 2, 48, 17), indicator_color(c1, ramp_sat))
|
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, ramp_sat))
|
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, ramp_sat))
|
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
|
selected_x = 21 + (40 * channel_selected) + 10
|
||||||
draw.rectangle((selected_x, 0, selected_x + 19, 20), indicator_color([c1, c2, c3][channel_selected], ramp_sat))
|
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([
|
draw.polygon([
|
||||||
(selected_x, 20),
|
(selected_x, 20),
|
||||||
(selected_x + 9, 25),
|
(selected_x + 9, 25),
|
||||||
(selected_x + 19, 20)
|
(selected_x + 19, 20)
|
||||||
],
|
],
|
||||||
fill=indicator_color([c1, c2, c3][channel_selected], ramp_sat))
|
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 + 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 + 40 + 4, 2), "2", font=font, fill=(255, 255, 255))
|
||||||
draw.text((33 + 80 + 4, 2), "3", font=font, fill=(255, 255, 255))
|
draw.text((33 + 80 + 4, 2), "3", font=font, fill=(255, 255, 255))
|
||||||
|
|
||||||
# Erase snooze icon
|
|
||||||
draw.rectangle((0, HEIGHT - 19, 19, HEIGHT), (255, 255, 255))
|
|
||||||
r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 255)
|
|
||||||
icon(image, icon_snooze, (0, HEIGHT - 20), (r, 129, 129))
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
update()
|
||||||
|
render()
|
||||||
display.display(image.convert("RGB"))
|
display.display(image.convert("RGB"))
|
||||||
|
|
||||||
|
# 5 FPS
|
||||||
time.sleep(1.0 / 5)
|
time.sleep(1.0 / 5)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user