General settings

This commit is contained in:
Phil Howard
2020-09-01 20:43:24 +01:00
parent e3c7869f40
commit 4a9434b1f7

View File

@@ -42,8 +42,49 @@ alarm = False
alarm_enable = True
def text_in_rect(canvas, text, font, rect, line_spacing=1.1, textcolor=(0, 0, 0)):
width = rect[2] - rect[0]
height = rect[3] - rect[1]
# Given a rectangle, reflow and scale text to fit, centred
while font.size > 0:
space_width = font.getsize(" ")[0]
line_height = int(font.size * line_spacing)
max_lines = math.floor(height / line_height)
lines = []
# Determine if text can fit at current scale.
words = text.split(" ")
while len(lines) < max_lines and len(words) > 0:
line = []
while len(words) > 0 and font.getsize(" ".join(line + [words[0]]))[0] <= width:
line.append(words.pop(0))
lines.append(" ".join(line))
if(len(lines)) <= max_lines and len(words) == 0:
# Solution is found, render the text.
y = int(rect[1] + (height / 2) - (len(lines) * line_height / 2) - (line_height - font.size) / 2)
bounds = [rect[2], y, rect[0], y + len(lines) * line_height]
for line in lines:
line_width = font.getsize(line)[0]
x = int(rect[0] + (width / 2) - (line_width / 2))
bounds[0] = min(bounds[0], x)
bounds[2] = max(bounds[2], x + line_width)
canvas.text((x, y), line, font=font, fill=textcolor)
y += line_height
return tuple(bounds)
font = ImageFont.truetype(font.path, font.size - 1)
def icon(image, icon, position, color):
col = Image.new("RGBA", (20, 20), color=color)
col = Image.new("RGBA", icon.size, color=color)
image.paste(col, position, mask=icon)
@@ -100,9 +141,23 @@ class View:
self._draw.rectangle((x, y, x2, y2), bgcolor)
self._draw.text((x + margin, y + margin - 1), text, font=font, fill=textcolor)
def draw_next_button(self, disabled=False):
if disabled:
# Draw disabled "Next" button
self._draw.rectangle((0, 0, 19, 19), (138, 138, 138))
icon(self._image, icon_rightarrow, (0, 0), (150, 150, 150))
else:
self._draw.rectangle((0, 0, 19, 19), COLOR_BLUE)
icon(self._image, icon_rightarrow, (0, 0), COLOR_WHITE)
class MainView(View):
def __init__(self, image, channels=None):
def __init__(self, image, channels=None, options=[]):
self._options = options
self._current_option = 0
self._change_mode = False
self._edit_mode = False
self.channels = channels
View.__init__(self, image)
@@ -112,6 +167,12 @@ class MainView(View):
# Saturation amounts from each sensor
saturation = channel.sensor.saturation
active = channel.sensor.active and channel.enabled
alarm_level = channel.alarm_level
self._draw.rectangle(
(x, 0, x + 37, DISPLAY_HEIGHT),
(230, 230, 230)
)
if active:
# Draw background bars
@@ -120,8 +181,14 @@ class MainView(View):
channel.indicator_color(saturation) if active else (229, 229, 229),
)
y = int((1.0 - alarm_level) * DISPLAY_HEIGHT)
self._draw.rectangle(
(x, y, x + 37, y),
(255, 0, 0) if channel.alarm else (0, 0, 0)
)
# Channel selection icons
x += 15
x += 11
col = channel.indicator_color(saturation, channel.label_colours)
if channel.alarm:
icon(
@@ -146,6 +213,45 @@ class MainView(View):
)
def render(self):
if self._edit_mode:
self.render_edit()
else:
self.render_overview()
def render_edit(self):
self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), COLOR_WHITE)
self.label("X", "Done", textcolor=COLOR_WHITE, bgcolor=COLOR_RED)
option = self._options[self._current_option]
title = option["title"]
prop = option["prop"]
object = option["object"]
value = getattr(object, prop)
text = option["format"](value)
mode = option.get("mode", "int")
help = option["help"]
if self._change_mode:
self.label("Y", "Yes" if mode == "bool" else "++", textcolor=COLOR_WHITE, bgcolor=COLOR_YELLOW)
self.label("B", "No" if mode == "bool" else "--", textcolor=COLOR_WHITE, bgcolor=COLOR_BLUE)
else:
self.label("B", "Next", textcolor=COLOR_WHITE, bgcolor=COLOR_BLUE)
self.label("Y", "Change", textcolor=COLOR_WHITE, bgcolor=COLOR_YELLOW)
self._draw.rectangle((0, 20, DISPLAY_WIDTH, 40), fill=(200, 200, 200))
text_in_rect(self._draw, help, font, (3, 20, DISPLAY_WIDTH - 3, 38), line_spacing=1)
self._draw.text(
(23, 3),
"Settings",
font=font,
fill=(0, 0, 0),
)
self._draw.text((3, 43), f"{title} : {text}", font=font, fill=(0, 0, 0))
self.draw_next_button(True)
def render_overview(self):
self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), (255, 255, 255))
for channel in self.channels:
@@ -159,27 +265,81 @@ class MainView(View):
alarm.render((0, DISPLAY_HEIGHT - 19))
self.label("X", "S", textcolor=COLOR_WHITE, bgcolor=COLOR_RED)
self.label("Y", "BL", textcolor=COLOR_WHITE, bgcolor=COLOR_GREEN)
def button_a(self):
return self._edit_mode
def button_b(self):
if self._edit_mode:
if self._change_mode:
option = self._options[self._current_option]
prop = option["prop"]
mode = option.get("mode", "int")
object = option["object"]
value = getattr(object, prop)
if mode == "bool":
value = False
else:
inc = option["inc"]
limit = option["min"]
value -= inc
if value < limit:
value = limit
setattr(object, prop, value)
else:
self._current_option += 1
self._current_option %= len(self._options)
return True
else:
return False
def button_x(self):
if self._edit_mode:
if self._change_mode:
self._change_mode = False
else:
self._edit_mode = False
else:
self._edit_mode = True
return True
def button_y(self):
if self._change_mode:
option = self._options[self._current_option]
prop = option["prop"]
mode = option.get("mode", "int")
object = option["object"]
value = getattr(object, prop)
if mode == "bool":
value = True
else:
inc = option["inc"]
limit = option["max"]
value += inc
if value > limit:
value = limit
setattr(object, prop, value)
else:
self._change_mode = True
class ChannelView(View):
def draw_status(self, subtle=False):
status = f"{self.channel.sensor.saturation * 100:.2f}% ({self.channel.sensor.moisture:.2f}Hz)"
self._draw.rectangle((0, 20, DISPLAY_WIDTH, 40), (50, 50, 50))
self._draw.text(
(3, 23),
f"{self.channel.sensor.saturation * 100:.2f}% ({self.channel.sensor.moisture:.2f}Hz)",
status,
font=font,
fill=(150, 150, 150) if subtle else (255, 255, 255),
)
def draw_next_button(self, disabled=False):
if disabled:
# Draw disabled "Next" button
self._draw.rectangle((0, 0, 19, 19), (138, 138, 138))
icon(self._image, icon_rightarrow, (0, 0), (150, 150, 150))
else:
self._draw.rectangle((0, 0, 19, 19), COLOR_BLUE)
icon(self._image, icon_rightarrow, (0, 0), COLOR_WHITE)
class DetailView(ChannelView):
def __init__(self, image, channel=None):
@@ -220,7 +380,7 @@ class DetailView(ChannelView):
0,
DISPLAY_HEIGHT - alarm_line,
DISPLAY_WIDTH - 40,
DISPLAY_HEIGHT - alarm_line + 1,
DISPLAY_HEIGHT - alarm_line,
),
(r, 0, 0),
)
@@ -229,7 +389,7 @@ class DetailView(ChannelView):
DISPLAY_WIDTH - 20,
DISPLAY_HEIGHT - alarm_line,
DISPLAY_WIDTH,
DISPLAY_HEIGHT - alarm_line + 1,
DISPLAY_HEIGHT - alarm_line,
),
(r, 0, 0),
)
@@ -615,7 +775,6 @@ class ViewController:
self._current_view += 1
self._current_view %= len(self.views)
self._current_subview = 0
print(f"Switched to view {self._current_view}")
def get_current_view(self):
view = self.views[self._current_view]
@@ -652,6 +811,8 @@ class ViewController:
def handle_button(pin):
global backlight
index = BUTTONS.index(pin)
label = LABELS[index]
@@ -666,9 +827,13 @@ def handle_button(pin):
viewcontroller.button_x()
if label == "Y":
viewcontroller.button_y()
if not viewcontroller.button_y():
backlight = not backlight
display.set_backlight(backlight)
backlight = True
# Set up the ST7735 SPI Display
display = ST7735.ST7735(
port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=80000000
@@ -681,6 +846,7 @@ light = ltr559.LTR559()
# Set up our canvas and prepare for drawing
image = Image.new("RGBA", (DISPLAY_WIDTH, DISPLAY_HEIGHT), color=(255, 255, 255))
font = ImageFont.truetype(UserFont, 14)
font_small = ImageFont.truetype(UserFont, 10)
# Pick a random selection of plant icons to display on screen
@@ -690,20 +856,11 @@ channels = [
Channel(3, 3, 3),
]
viewcontroller = ViewController(
[
MainView(image, channels=channels),
(DetailView(image, channel=channels[0]), EditView(image, channel=channels[0])),
(DetailView(image, channel=channels[1]), EditView(image, channel=channels[1])),
(DetailView(image, channel=channels[2]), EditView(image, channel=channels[2])),
]
)
alarm = Alarm(image)
def main():
global alarm
global alarm, viewcontroller
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
@@ -745,6 +902,36 @@ Alarm Interval: {:.2f}s
)
)
main_options = [
{
"title": "Alarm Interval",
"prop": "interval",
"inc": 1,
"min": 1,
"max": 60,
"format": lambda value: f"{value:02.0f}sec",
"object": alarm,
"help": "Time between alarm beeps."
},
{
"title": "Alarm Enable",
"prop": "enabled",
"mode": "bool",
"format": lambda value: "Yes" if value else "No",
"object": alarm,
"help": "Enable the piezo alarm beep."
},
]
viewcontroller = ViewController(
[
MainView(image, channels=channels, options=main_options),
(DetailView(image, channel=channels[0]), EditView(image, channel=channels[0])),
(DetailView(image, channel=channels[1]), EditView(image, channel=channels[1])),
(DetailView(image, channel=channels[2]), EditView(image, channel=channels[2])),
]
)
while True:
for channel in channels:
channel.update()