@@ -1,21 +1,12 @@
|
|||||||
# Monitoring and/or Watering Your Plants
|
# Monitoring Your Plants
|
||||||
|
|
||||||
The `grow-monitor-and-water.py` example can monitor and, optionally, automatically water all three Grow channels.
|
The example `monitor.py` monitors the moisture level of your soil and sounds an alarm when it drops below a defined threshold.
|
||||||
|
|
||||||
By default auto-watering is disabled and an alarm will sound every 1s if the `warn_level` is reached.
|
It's configured using `settings.yml`. Your settings for monitoring will look something like this:
|
||||||
|
|
||||||
Run it with `python3 grow-monitor-and-water.py`.
|
|
||||||
|
|
||||||
## Monitoring
|
|
||||||
|
|
||||||
Grow can monitor the moisture level of your soil, sounding an alarm when it dries out.
|
|
||||||
|
|
||||||
Grow is configured using `settings.yml`. Your settings for monitoring only will look something like this:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
channel1:
|
channel1:
|
||||||
warn_level: 0.2
|
warn_level: 0.2
|
||||||
icon: icons/flat-4.png
|
|
||||||
channel2:
|
channel2:
|
||||||
warn_level: 0.2
|
warn_level: 0.2
|
||||||
channel3:
|
channel3:
|
||||||
@@ -25,6 +16,16 @@ general:
|
|||||||
alarm_interval: 1.0
|
alarm_interval: 1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`monitor.py` includes a main view showing the moisture status of each channel and the level beyond which the alarm will sound.
|
||||||
|
|
||||||
|
The controls from the main view are as follows:
|
||||||
|
|
||||||
|
* `A` - cycle through the main screen and each channel
|
||||||
|
* `B` - snooze the alarm
|
||||||
|
* `X` - configure global settings or the selected channel
|
||||||
|
|
||||||
|
The warning moisture level can be configured for each channel, along with the Wet and Dry points that store the frequency expected from the sensor when soil is fully wet/dry.
|
||||||
|
|
||||||
## Watering
|
## Watering
|
||||||
|
|
||||||
If you've got pumps attached to Grow and want to automatically water your plants, you'll need some extra configuration options.
|
If you've got pumps attached to Grow and want to automatically water your plants, you'll need some extra configuration options.
|
||||||
@@ -39,8 +40,7 @@ channel1:
|
|||||||
pump_time: 0.7
|
pump_time: 0.7
|
||||||
wet_point: 0.7
|
wet_point: 0.7
|
||||||
dry_point: 27.6
|
dry_point: 27.6
|
||||||
auto_water: False
|
auto_water: True
|
||||||
icon: icons/flat-4.png
|
|
||||||
channel2:
|
channel2:
|
||||||
water_level: 0.8
|
water_level: 0.8
|
||||||
warn_level: 0.2
|
warn_level: 0.2
|
||||||
@@ -48,7 +48,7 @@ channel2:
|
|||||||
pump_time: 0.7
|
pump_time: 0.7
|
||||||
wet_point: 0.7
|
wet_point: 0.7
|
||||||
dry_point: 27.6
|
dry_point: 27.6
|
||||||
auto_water: False
|
auto_water: True
|
||||||
channel3:
|
channel3:
|
||||||
water_level: 0.8
|
water_level: 0.8
|
||||||
warn_level: 0.2
|
warn_level: 0.2
|
||||||
@@ -56,7 +56,7 @@ channel3:
|
|||||||
pump_time: 0.7
|
pump_time: 0.7
|
||||||
wet_point: 0.7
|
wet_point: 0.7
|
||||||
dry_point: 27.6
|
dry_point: 27.6
|
||||||
auto_water: False
|
auto_water: True
|
||||||
general:
|
general:
|
||||||
alarm_enable: True
|
alarm_enable: True
|
||||||
alarm_interval: 1.0
|
alarm_interval: 1.0
|
||||||
@@ -73,7 +73,6 @@ Grow has three channels which are separated into the sections `channel1`, `chann
|
|||||||
* `auto_water` - Whether to run the attached pump (True to auto-water, False for manual watering)
|
* `auto_water` - Whether to run the attached pump (True to auto-water, False for manual watering)
|
||||||
* `wet_point` - Value for the sensor in saturated soil (in Hz)
|
* `wet_point` - Value for the sensor in saturated soil (in Hz)
|
||||||
* `dry_point` - Value for the sensor in totally dry soil (in Hz)
|
* `dry_point` - Value for the sensor in totally dry soil (in Hz)
|
||||||
* `icon` - Optional icon image for the channel, see the icons directory for images.
|
|
||||||
|
|
||||||
## General Settings
|
## General Settings
|
||||||
|
|
||||||
|
|||||||
BIN
examples/icons/icon-backdrop.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
examples/icons/icon-channel.png
Normal file
|
After Width: | Height: | Size: 987 B |
BIN
examples/icons/icon-circle.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
examples/icons/icon-return.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -28,10 +28,11 @@ DISPLAY_WIDTH = 160
|
|||||||
DISPLAY_HEIGHT = 80
|
DISPLAY_HEIGHT = 80
|
||||||
|
|
||||||
COLOR_WHITE = (255, 255, 255)
|
COLOR_WHITE = (255, 255, 255)
|
||||||
COLOR_BLUE = (32, 137, 251)
|
COLOR_BLUE = (31, 137, 251)
|
||||||
COLOR_GREEN = (100, 255, 124)
|
COLOR_GREEN = (99, 255, 124)
|
||||||
COLOR_YELLOW = (254, 219, 82)
|
COLOR_YELLOW = (254, 219, 82)
|
||||||
COLOR_RED = (254, 82, 82)
|
COLOR_RED = (247, 0, 63)
|
||||||
|
COLOR_BLACK = (0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
# Only the ALPHA channel is used from these images
|
# Only the ALPHA channel is used from these images
|
||||||
@@ -42,6 +43,9 @@ icon_alarm = Image.open("icons/icon-alarm.png").convert("RGBA")
|
|||||||
icon_snooze = Image.open("icons/icon-snooze.png").convert("RGBA")
|
icon_snooze = Image.open("icons/icon-snooze.png").convert("RGBA")
|
||||||
icon_help = Image.open("icons/icon-help.png").convert("RGBA")
|
icon_help = Image.open("icons/icon-help.png").convert("RGBA")
|
||||||
icon_settings = Image.open("icons/icon-settings.png").convert("RGBA")
|
icon_settings = Image.open("icons/icon-settings.png").convert("RGBA")
|
||||||
|
icon_channel = Image.open("icons/icon-channel.png").convert("RGBA")
|
||||||
|
icon_backdrop = Image.open("icons/icon-backdrop.png").convert("RGBA")
|
||||||
|
icon_return = Image.open("icons/icon-return.png").convert("RGBA")
|
||||||
|
|
||||||
|
|
||||||
class View:
|
class View:
|
||||||
@@ -71,7 +75,7 @@ class View:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), (255, 255, 255))
|
self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), (0, 0, 0))
|
||||||
|
|
||||||
def icon(self, icon, position, color):
|
def icon(self, icon, position, color):
|
||||||
col = Image.new("RGBA", icon.size, color=color)
|
col = Image.new("RGBA", icon.size, color=color)
|
||||||
@@ -80,7 +84,7 @@ class View:
|
|||||||
def label(
|
def label(
|
||||||
self,
|
self,
|
||||||
position="X",
|
position="X",
|
||||||
text="",
|
text=None,
|
||||||
bgcolor=(0, 0, 0),
|
bgcolor=(0, 0, 0),
|
||||||
textcolor=(255, 255, 255),
|
textcolor=(255, 255, 255),
|
||||||
margin=4,
|
margin=4,
|
||||||
@@ -109,16 +113,16 @@ class View:
|
|||||||
(x + margin, y + margin - 1), text, font=self.font, fill=textcolor
|
(x + margin, y + margin - 1), text, font=self.font, fill=textcolor
|
||||||
)
|
)
|
||||||
|
|
||||||
def overlay(self, text):
|
def overlay(self, text, top=0):
|
||||||
"""Draw an overlay with some auto-sized text."""
|
"""Draw an overlay with some auto-sized text."""
|
||||||
self._draw.rectangle(
|
self._draw.rectangle(
|
||||||
(0, 20, DISPLAY_WIDTH, DISPLAY_HEIGHT), fill=(192, 225, 254)
|
(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT), fill=(192, 225, 254)
|
||||||
) # Overlay backdrop
|
) # Overlay backdrop
|
||||||
self._draw.rectangle((0, 20, DISPLAY_WIDTH, 21), fill=COLOR_BLUE) # Top border
|
self._draw.rectangle((0, top, DISPLAY_WIDTH, top + 1), fill=COLOR_BLUE) # Top border
|
||||||
self.text_in_rect(
|
self.text_in_rect(
|
||||||
text,
|
text,
|
||||||
self.font,
|
self.font,
|
||||||
(3, 20, DISPLAY_WIDTH - 3, DISPLAY_HEIGHT - 2),
|
(3, top, DISPLAY_WIDTH - 3, DISPLAY_HEIGHT - 2),
|
||||||
line_spacing=1,
|
line_spacing=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -186,45 +190,48 @@ class MainView(View):
|
|||||||
View.__init__(self, image)
|
View.__init__(self, image)
|
||||||
|
|
||||||
def render_channel(self, channel):
|
def render_channel(self, channel):
|
||||||
x = [21, 61, 101][channel.channel - 1]
|
bar_x = 33
|
||||||
|
bar_margin = 2
|
||||||
|
bar_width = 30
|
||||||
|
label_width = 16
|
||||||
|
label_height = 16
|
||||||
|
label_y = 0
|
||||||
|
|
||||||
|
x = [
|
||||||
|
bar_x,
|
||||||
|
bar_x + ((bar_width + bar_margin) * 1),
|
||||||
|
bar_x + ((bar_width + bar_margin) * 2),
|
||||||
|
][channel.channel - 1]
|
||||||
|
|
||||||
# Saturation amounts from each sensor
|
# Saturation amounts from each sensor
|
||||||
saturation = channel.sensor.saturation
|
saturation = channel.sensor.saturation
|
||||||
active = channel.sensor.active and channel.enabled
|
active = channel.sensor.active and channel.enabled
|
||||||
warn_level = channel.warn_level
|
warn_level = channel.warn_level
|
||||||
|
|
||||||
self._draw.rectangle((x, 0, x + 37, DISPLAY_HEIGHT), (230, 230, 230))
|
|
||||||
|
|
||||||
if active:
|
if active:
|
||||||
# Draw background bars
|
# Draw background bars
|
||||||
self._draw.rectangle(
|
self._draw.rectangle(
|
||||||
(x, int((1.0 - saturation) * DISPLAY_HEIGHT), x + 37, DISPLAY_HEIGHT),
|
(x, int((1.0 - saturation) * DISPLAY_HEIGHT), x + bar_width - 1, DISPLAY_HEIGHT),
|
||||||
channel.indicator_color(saturation) if active else (229, 229, 229),
|
channel.indicator_color(saturation) if active else (229, 229, 229),
|
||||||
)
|
)
|
||||||
|
|
||||||
y = int((1.0 - warn_level) * DISPLAY_HEIGHT)
|
y = int((1.0 - warn_level) * DISPLAY_HEIGHT)
|
||||||
self._draw.rectangle(
|
self._draw.rectangle(
|
||||||
(x, y, x + 37, y), (255, 0, 0) if channel.alarm else (0, 0, 0)
|
(x, y, x + bar_width - 1, y), (255, 0, 0) if channel.alarm else (0, 0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Channel selection icons
|
# Channel selection icons
|
||||||
x += 11
|
x += (bar_width - label_width) // 2
|
||||||
col = channel.indicator_color(saturation, channel.label_colours)
|
|
||||||
if channel.alarm:
|
self.icon(icon_channel, (x, label_y), (200, 200, 200) if active else (64, 64, 64))
|
||||||
self.icon(icon_alarm, (x - 2, 0), col if active else (129, 129, 129))
|
|
||||||
else:
|
|
||||||
self._draw.rectangle(
|
|
||||||
(x, 2, x + 15, 17),
|
|
||||||
col if active else (129, 129, 129),
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: replace number text with graphic
|
# TODO: replace number text with graphic
|
||||||
tw, th = self.font.getsize("{}".format(channel.channel))
|
tw, th = self.font.getsize(str(channel.channel))
|
||||||
self._draw.text(
|
self._draw.text(
|
||||||
(x + int(math.ceil(8 - (tw / 2.0))), 2),
|
(x + int(math.ceil(8 - (tw / 2.0))), label_y + 1),
|
||||||
"{}".format(channel.channel),
|
str(channel.channel),
|
||||||
font=self.font,
|
font=self.font,
|
||||||
fill=(255, 255, 255) if active else (200, 200, 200),
|
fill=(55, 55, 55) if active else (100, 100, 100),
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
@@ -233,15 +240,14 @@ class MainView(View):
|
|||||||
for channel in self.channels:
|
for channel in self.channels:
|
||||||
self.render_channel(channel)
|
self.render_channel(channel)
|
||||||
|
|
||||||
# Icon backdrops
|
|
||||||
self._draw.rectangle((0, 0, 19, 19), (32, 138, 251))
|
|
||||||
|
|
||||||
# Icons
|
# Icons
|
||||||
self.icon(icon_rightarrow, (0, 0), (255, 255, 255))
|
self.icon(icon_backdrop, (0, 0), COLOR_WHITE)
|
||||||
|
self.icon(icon_rightarrow, (3, 3), (55, 55, 55))
|
||||||
|
|
||||||
self.alarm.render((0, DISPLAY_HEIGHT - 19))
|
self.alarm.render((3, DISPLAY_HEIGHT - 23))
|
||||||
|
|
||||||
self.icon(icon_settings, (DISPLAY_WIDTH - 19, 0), COLOR_RED)
|
self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE)
|
||||||
|
self.icon(icon_settings, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55))
|
||||||
|
|
||||||
|
|
||||||
class EditView(View):
|
class EditView(View):
|
||||||
@@ -257,7 +263,8 @@ class EditView(View):
|
|||||||
View.__init__(self, image)
|
View.__init__(self, image)
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
self.label("X", "Done", textcolor=COLOR_WHITE, bgcolor=COLOR_RED)
|
self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE)
|
||||||
|
self.icon(icon_return, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55))
|
||||||
|
|
||||||
option = self._options[self._current_option]
|
option = self._options[self._current_option]
|
||||||
title = option["title"]
|
title = option["title"]
|
||||||
@@ -272,25 +279,27 @@ class EditView(View):
|
|||||||
self.label(
|
self.label(
|
||||||
"Y",
|
"Y",
|
||||||
"Yes" if mode == "bool" else "++",
|
"Yes" if mode == "bool" else "++",
|
||||||
textcolor=COLOR_WHITE,
|
textcolor=COLOR_BLACK,
|
||||||
bgcolor=COLOR_YELLOW,
|
bgcolor=COLOR_WHITE,
|
||||||
)
|
)
|
||||||
self.label(
|
self.label(
|
||||||
"B",
|
"B",
|
||||||
"No" if mode == "bool" else "--",
|
"No" if mode == "bool" else "--",
|
||||||
textcolor=COLOR_WHITE,
|
textcolor=COLOR_BLACK,
|
||||||
bgcolor=COLOR_BLUE,
|
bgcolor=COLOR_WHITE,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.label("B", "Next", textcolor=COLOR_WHITE, bgcolor=COLOR_BLUE)
|
self.label("B", "Next", textcolor=COLOR_BLACK, bgcolor=COLOR_WHITE)
|
||||||
self.label("Y", "Change", textcolor=COLOR_WHITE, bgcolor=COLOR_YELLOW)
|
self.label("Y", "Change", textcolor=COLOR_BLACK, bgcolor=COLOR_WHITE)
|
||||||
|
|
||||||
self.icon(icon_help, (0, 0), COLOR_BLUE)
|
self._draw.text((3, 36), f"{title} : {text}", font=self.font, fill=COLOR_WHITE)
|
||||||
|
|
||||||
self._draw.text((3, 43), f"{title} : {text}", font=self.font, fill=(0, 0, 0))
|
|
||||||
|
|
||||||
if self._help_mode:
|
if self._help_mode:
|
||||||
self.overlay(help)
|
self.icon(icon_backdrop.rotate(90), (0, 0), COLOR_BLUE)
|
||||||
|
self._draw.rectangle((7, 3, 23, 19), COLOR_BLACK)
|
||||||
|
self.overlay(help, top=26)
|
||||||
|
|
||||||
|
self.icon(icon_help, (0, 0), COLOR_BLUE)
|
||||||
|
|
||||||
def button_a(self):
|
def button_a(self):
|
||||||
self._help_mode = not self._help_mode
|
self._help_mode = not self._help_mode
|
||||||
@@ -366,10 +375,10 @@ class SettingsView(EditView):
|
|||||||
def render(self):
|
def render(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
self._draw.text(
|
self._draw.text(
|
||||||
(23, 3),
|
(28, 5),
|
||||||
"Settings",
|
"Settings",
|
||||||
font=self.font,
|
font=self.font,
|
||||||
fill=(0, 0, 0),
|
fill=COLOR_WHITE,
|
||||||
)
|
)
|
||||||
EditView.render(self)
|
EditView.render(self)
|
||||||
|
|
||||||
@@ -381,16 +390,14 @@ class ChannelView(View):
|
|||||||
self.channel = channel
|
self.channel = channel
|
||||||
View.__init__(self, image)
|
View.__init__(self, image)
|
||||||
|
|
||||||
def draw_status(self, subtle=False):
|
def draw_status(self, position):
|
||||||
status = f"{self.channel.sensor.saturation * 100:.2f}% ({self.channel.sensor.moisture:.2f}Hz)"
|
status = f"Sat: {self.channel.sensor.saturation * 100:.2f}%"
|
||||||
|
|
||||||
self._draw.rectangle((0, 20, DISPLAY_WIDTH, 40), (50, 50, 50))
|
|
||||||
|
|
||||||
self._draw.text(
|
self._draw.text(
|
||||||
(3, 23),
|
position,
|
||||||
status,
|
status,
|
||||||
font=self.font,
|
font=self.font,
|
||||||
fill=(150, 150, 150) if subtle else (255, 255, 255),
|
fill=(255, 255, 255),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -402,65 +409,87 @@ class DetailView(ChannelView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
width, height = self._image.size
|
self.clear()
|
||||||
self._draw.rectangle((0, 0, width, height), (255, 255, 255))
|
|
||||||
|
|
||||||
|
if self.channel.enabled:
|
||||||
|
graph_height = DISPLAY_HEIGHT - 8 - 20
|
||||||
|
graph_width = DISPLAY_WIDTH - 64
|
||||||
|
|
||||||
|
graph_x = (DISPLAY_WIDTH - graph_width) // 2
|
||||||
|
graph_y = 8
|
||||||
|
|
||||||
|
self.draw_status((graph_x, graph_y + graph_height + 4))
|
||||||
|
|
||||||
|
self._draw.rectangle((graph_x, graph_y, graph_x + graph_width, graph_y + graph_height), (50, 50, 50))
|
||||||
|
|
||||||
|
for x, value in enumerate(self.channel.sensor.history[:graph_width]):
|
||||||
|
color = self.channel.indicator_color(value)
|
||||||
|
h = value * graph_height
|
||||||
|
x = graph_x + graph_width - x - 1
|
||||||
|
self._draw.rectangle((x, graph_y + graph_height - h, x + 1, graph_y + graph_height), color)
|
||||||
|
|
||||||
|
alarm_line = int(self.channel.warn_level * graph_height)
|
||||||
|
r = 255
|
||||||
|
if self.channel.alarm:
|
||||||
|
r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127
|
||||||
|
|
||||||
|
self._draw.rectangle(
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
graph_height + 8 - alarm_line,
|
||||||
|
DISPLAY_WIDTH - 40,
|
||||||
|
graph_height + 8 - alarm_line,
|
||||||
|
),
|
||||||
|
(r, 0, 0),
|
||||||
|
)
|
||||||
|
self._draw.rectangle(
|
||||||
|
(
|
||||||
|
DISPLAY_WIDTH - 20,
|
||||||
|
graph_height + 8 - alarm_line,
|
||||||
|
DISPLAY_WIDTH,
|
||||||
|
graph_height + 8 - alarm_line,
|
||||||
|
),
|
||||||
|
(r, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.icon(
|
||||||
|
icon_alarm,
|
||||||
|
(DISPLAY_WIDTH - 40, graph_height + 8 - alarm_line - 10),
|
||||||
|
(r, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Channel icons
|
||||||
|
|
||||||
|
x_positions = [40, 72, 104]
|
||||||
|
label_x = x_positions[self.channel.channel - 1]
|
||||||
|
label_y = 0
|
||||||
|
|
||||||
|
active = self.channel.sensor.active and self.channel.enabled
|
||||||
|
|
||||||
|
for x in x_positions:
|
||||||
|
self.icon(icon_channel, (x, label_y - 10), (16, 16, 16))
|
||||||
|
|
||||||
|
self.icon(icon_channel, (label_x, label_y), (200, 200, 200))
|
||||||
|
|
||||||
|
tw, th = self.font.getsize(str(self.channel.channel))
|
||||||
self._draw.text(
|
self._draw.text(
|
||||||
(23, 3),
|
(label_x + int(math.ceil(8 - (tw / 2.0))), label_y + 1),
|
||||||
"{}".format(self.channel.title),
|
str(self.channel.channel),
|
||||||
font=self.font,
|
font=self.font,
|
||||||
fill=(0, 0, 0),
|
fill=(55, 55, 55) if active else (100, 100, 100),
|
||||||
)
|
|
||||||
|
|
||||||
self.draw_status()
|
|
||||||
|
|
||||||
graph_height = DISPLAY_HEIGHT - 40
|
|
||||||
self._draw.rectangle(
|
|
||||||
(0, DISPLAY_HEIGHT - graph_height, DISPLAY_WIDTH, DISPLAY_HEIGHT),
|
|
||||||
(10, 10, 10),
|
|
||||||
)
|
|
||||||
|
|
||||||
for x, value in enumerate(self.channel.sensor.history[:DISPLAY_WIDTH]):
|
|
||||||
color = self.channel.indicator_color(value)
|
|
||||||
h = value * graph_height
|
|
||||||
x = DISPLAY_WIDTH - x - 1
|
|
||||||
self._draw.rectangle((x, DISPLAY_HEIGHT - h, x + 1, DISPLAY_HEIGHT), color)
|
|
||||||
|
|
||||||
alarm_line = int(self.channel.warn_level * graph_height)
|
|
||||||
r = 255
|
|
||||||
if self.channel.alarm:
|
|
||||||
r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127
|
|
||||||
self._draw.rectangle(
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
DISPLAY_HEIGHT - alarm_line,
|
|
||||||
DISPLAY_WIDTH - 40,
|
|
||||||
DISPLAY_HEIGHT - alarm_line,
|
|
||||||
),
|
|
||||||
(r, 0, 0),
|
|
||||||
)
|
|
||||||
self._draw.rectangle(
|
|
||||||
(
|
|
||||||
DISPLAY_WIDTH - 20,
|
|
||||||
DISPLAY_HEIGHT - alarm_line,
|
|
||||||
DISPLAY_WIDTH,
|
|
||||||
DISPLAY_HEIGHT - alarm_line,
|
|
||||||
),
|
|
||||||
(r, 0, 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.icon(
|
|
||||||
icon_alarm,
|
|
||||||
(DISPLAY_WIDTH - 40, DISPLAY_HEIGHT - alarm_line - 10),
|
|
||||||
(r, 0, 0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Next button
|
# Next button
|
||||||
self._draw.rectangle((0, 0, 19, 19), COLOR_BLUE)
|
self.icon(icon_backdrop, (0, 0), COLOR_WHITE)
|
||||||
self.icon(icon_rightarrow, (0, 0), COLOR_WHITE)
|
self.icon(icon_rightarrow, (3, 3), (55, 55, 55))
|
||||||
|
|
||||||
|
# Prev button
|
||||||
|
# self.icon(icon_backdrop, (0, DISPLAY_HEIGHT - 26), COLOR_WHITE)
|
||||||
|
# self.icon(icon_return, (3, DISPLAY_HEIGHT - 26 + 3), (55, 55, 55))
|
||||||
|
|
||||||
# Edit
|
# Edit
|
||||||
self.label("X", "Edit", textcolor=(255, 255, 255), bgcolor=COLOR_RED)
|
self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE)
|
||||||
|
self.icon(icon_settings, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55))
|
||||||
|
|
||||||
|
|
||||||
class ChannelEditView(ChannelView, EditView):
|
class ChannelEditView(ChannelView, EditView):
|
||||||
@@ -515,29 +544,15 @@ class ChannelEditView(ChannelView, EditView):
|
|||||||
def render(self):
|
def render(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
self._draw.text(
|
|
||||||
(23, 3), "{}".format(self.channel.title), font=self.font, fill=(0, 0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
EditView.render(self)
|
EditView.render(self)
|
||||||
|
|
||||||
if not self._help_mode:
|
|
||||||
self.draw_status(True)
|
|
||||||
|
|
||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
bar_colours = [
|
colors = [
|
||||||
(192, 225, 254), # Blue
|
|
||||||
(196, 255, 209), # Green
|
|
||||||
(255, 243, 192), # Yellow
|
|
||||||
(254, 192, 192), # Red
|
|
||||||
]
|
|
||||||
|
|
||||||
label_colours = [
|
|
||||||
COLOR_BLUE,
|
COLOR_BLUE,
|
||||||
COLOR_GREEN,
|
COLOR_GREEN,
|
||||||
COLOR_YELLOW,
|
COLOR_YELLOW,
|
||||||
COLOR_RED,
|
COLOR_RED
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -603,22 +618,23 @@ class Channel:
|
|||||||
self._dry_point = dry_point
|
self._dry_point = dry_point
|
||||||
self.sensor.set_dry_point(dry_point)
|
self.sensor.set_dry_point(dry_point)
|
||||||
|
|
||||||
def indicator_color(self, value, r=None):
|
def warn_color(self):
|
||||||
|
value = self.sensor.moisture
|
||||||
|
|
||||||
|
def indicator_color(self, value):
|
||||||
value = 1.0 - value
|
value = 1.0 - value
|
||||||
|
|
||||||
if r is None:
|
|
||||||
r = self.bar_colours
|
|
||||||
if value == 1.0:
|
if value == 1.0:
|
||||||
return r[-1]
|
return self.colors[-1]
|
||||||
if value == 0.0:
|
if value == 0.0:
|
||||||
return r[0]
|
return self.colors[0]
|
||||||
|
|
||||||
value *= len(r) - 1
|
value *= len(self.colors) - 1
|
||||||
a = int(math.floor(value))
|
a = int(math.floor(value))
|
||||||
b = a + 1
|
b = a + 1
|
||||||
blend = float(value - a)
|
blend = float(value - a)
|
||||||
|
|
||||||
r, g, b = [int(((r[b][i] - r[a][i]) * blend) + r[a][i]) for i in range(3)]
|
r, g, b = [int(((self.colors[b][i] - self.colors[a][i]) * blend) + self.colors[a][i]) for i in range(3)]
|
||||||
|
|
||||||
return (r, g, b)
|
return (r, g, b)
|
||||||
|
|
||||||
@@ -716,6 +732,7 @@ class Alarm(View):
|
|||||||
if self._sleep_until is not None:
|
if self._sleep_until is not None:
|
||||||
if self._sleep_until > time.time():
|
if self._sleep_until > time.time():
|
||||||
return
|
return
|
||||||
|
self._sleep_until = None
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.enabled
|
self.enabled
|
||||||
@@ -743,7 +760,7 @@ class Alarm(View):
|
|||||||
def render(self, position=(0, 0)):
|
def render(self, position=(0, 0)):
|
||||||
x, y = position
|
x, y = position
|
||||||
# Draw the snooze icon- will be pulsing red if the alarm state is True
|
# Draw the snooze icon- will be pulsing red if the alarm state is True
|
||||||
self._draw.rectangle((x, y, x + 19, y + 19), (255, 255, 255))
|
#self._draw.rectangle((x, y, x + 19, y + 19), (255, 255, 255))
|
||||||
r = 129
|
r = 129
|
||||||
if self._triggered and self._sleep_until is None:
|
if self._triggered and self._sleep_until is None:
|
||||||
r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127
|
r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127
|
||||||
@@ -778,6 +795,10 @@ class ViewController:
|
|||||||
self._current_view = 0
|
self._current_view = 0
|
||||||
self._current_subview = 0
|
self._current_subview = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def home(self):
|
||||||
|
return self._current_view == 0 and self._current_subview == 0
|
||||||
|
|
||||||
def next_subview(self):
|
def next_subview(self):
|
||||||
view = self.views[self._current_view]
|
view = self.views[self._current_view]
|
||||||
if isinstance(view, tuple):
|
if isinstance(view, tuple):
|
||||||
@@ -790,6 +811,12 @@ class ViewController:
|
|||||||
self._current_view %= len(self.views)
|
self._current_view %= len(self.views)
|
||||||
self._current_subview = 0
|
self._current_subview = 0
|
||||||
|
|
||||||
|
def prev_view(self):
|
||||||
|
if self._current_subview == 0:
|
||||||
|
self._current_view -= 1
|
||||||
|
self._current_view %= len(self.views)
|
||||||
|
self._current_subview = 0
|
||||||
|
|
||||||
def get_current_view(self):
|
def get_current_view(self):
|
||||||
view = self.views[self._current_view]
|
view = self.views[self._current_view]
|
||||||
if isinstance(view, tuple):
|
if isinstance(view, tuple):
|
||||||
@@ -812,7 +839,7 @@ class ViewController:
|
|||||||
self.next_view()
|
self.next_view()
|
||||||
|
|
||||||
def button_b(self):
|
def button_b(self):
|
||||||
return self.view.button_b()
|
self.view.button_b()
|
||||||
|
|
||||||
def button_x(self):
|
def button_x(self):
|
||||||
if not self.view.button_x():
|
if not self.view.button_x():
|
||||||
@@ -904,10 +931,11 @@ def main():
|
|||||||
|
|
||||||
if label == "B": # Sleep Alarm
|
if label == "B": # Sleep Alarm
|
||||||
if not viewcontroller.button_b():
|
if not viewcontroller.button_b():
|
||||||
if alarm.sleeping():
|
if viewcontroller.home:
|
||||||
alarm.cancel_sleep()
|
if alarm.sleeping():
|
||||||
else:
|
alarm.cancel_sleep()
|
||||||
alarm.sleep()
|
else:
|
||||||
|
alarm.sleep()
|
||||||
|
|
||||||
if label == "X":
|
if label == "X":
|
||||||
viewcontroller.button_x()
|
viewcontroller.button_x()
|
||||||
|
|||||||