mirror of
https://github.com/pimoroni/grow-python
synced 2025-10-24 15:19:24 +00:00
Tweaks, fixes, linting and docs
This commit is contained in:
12
Makefile
12
Makefile
@@ -27,7 +27,7 @@ check:
|
||||
@echo "Checking for trailing whitespace"
|
||||
@! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO
|
||||
@echo "Checking for DOS line-endings"
|
||||
@! grep -IUrn --color "
|
||||
@! grep -lIUrn --color "
|
||||
" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile
|
||||
@echo "Checking library/CHANGELOG.txt"
|
||||
@cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION}
|
||||
@@ -36,14 +36,14 @@ check:
|
||||
|
||||
tag:
|
||||
git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}"
|
||||
|
||||
|
||||
python-readme: library/README.md
|
||||
|
||||
python-license: library/LICENSE.txt
|
||||
|
||||
library/README.rst: README.md library/CHANGELOG.txt
|
||||
pandoc --from=markdown --to=rst -o library/README.rst README.md
|
||||
echo "" >> library/README.rst
|
||||
|
||||
library/README.md: README.md library/CHANGELOG.txt
|
||||
cp README.md library/README.md
|
||||
printf "\n# Changelog\n" >> library/README.md
|
||||
cat library/CHANGELOG.txt >> library/README.md
|
||||
|
||||
library/LICENSE.txt: LICENSE
|
||||
|
||||
21
README.md
21
README.md
@@ -29,21 +29,22 @@ curl -sSL https://get.pimoroni.com/grow | bash
|
||||
|
||||
## Or... Install from PyPi and configure manually:
|
||||
|
||||
* Run `sudo pip install growhat`
|
||||
* Install dependencies:
|
||||
|
||||
```
|
||||
sudo apt install python3-setuptools python3-pip python3-yaml python3-smbus python3-pil python3-spidev python3-rpi.gpio
|
||||
```
|
||||
|
||||
* Run `sudo pip3 install growhat`
|
||||
|
||||
**Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to:
|
||||
|
||||
* Enable i2c: `raspi-config nonint do_i2c 0`
|
||||
* Enable SPI: `raspi-config nonint do_spi 0`
|
||||
|
||||
And install additional dependencies:
|
||||
|
||||
```
|
||||
sudo apt install python-numpy python-smbus python-pil python-setuptools
|
||||
```
|
||||
* Enable i2c: `sudo raspi-config nonint do_i2c 0`
|
||||
* Enable SPI: `sudo raspi-config nonint do_spi 0`
|
||||
* Add the following to `/boot/config.txt`: `dtoverlay=spi0-cs,cs0_pin=14`
|
||||
|
||||
## Help & Support
|
||||
|
||||
* GPIO Pinout - https://pinout.xyz/pinout/enviro_plus
|
||||
* GPIO Pinout - https://pinout.xyz/pinout/grow_hat_mini
|
||||
* Support forums - http://forums.pimoroni.com/c/support
|
||||
* Discord - https://discord.gg/hr93ByC
|
||||
|
||||
14
REFERENCE.md
14
REFERENCE.md
@@ -1,7 +1,5 @@
|
||||
# Grow <!-- omit in toc -->
|
||||
|
||||
Grow
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Requirements](#requirements)
|
||||
- [Python 3 & pip](#python-3--pip)
|
||||
@@ -38,7 +36,7 @@ You should use Python 3, which may need installing on your Pi:
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip
|
||||
sudo apt install python3 python3-pip python3-setuptools
|
||||
```
|
||||
|
||||
#### Enabling i2c and spi
|
||||
@@ -50,6 +48,14 @@ sudo raspi-config nonint do_i2c 0
|
||||
sudo raspi-config nonint do_spi 0
|
||||
```
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
The following dependencies are required:
|
||||
|
||||
```
|
||||
sudo apt install python3-yaml python3-smbus python3-pil python3-spidev python3-rpi.gpio
|
||||
```
|
||||
|
||||
### Installing the library
|
||||
|
||||
```python
|
||||
@@ -174,7 +180,7 @@ while True:
|
||||
moisture1.active()
|
||||
```
|
||||
|
||||
Returns `True` if the moisture sensor is connected and returning valid readings.
|
||||
Returns `True` if the moisture sensor is connected and returning valid readings.
|
||||
|
||||
Checks if a pulse has happened within the last second, and that the reading is within a sensible range.
|
||||
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
# Watering Settings
|
||||
# Monitoring and/or Watering Your Plants
|
||||
|
||||
The `grow-monitor-and-water.py` example can monitor and, optionally, automatically water all three Grow channels.
|
||||
|
||||
It's configured using a settings file - `water.yml` - that looks like the following:
|
||||
By default auto-watering is disabled and an alarm will sound every 1s if the `warn_level` is reached.
|
||||
|
||||
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
|
||||
channel1:
|
||||
warn_level: 0.2
|
||||
icon: icons/flat-4.png
|
||||
channel2:
|
||||
warn_level: 0.2
|
||||
channel3:
|
||||
warn_level: 0.2
|
||||
general:
|
||||
alarm_enable: True
|
||||
alarm_interval: 1.0
|
||||
```
|
||||
|
||||
## Watering
|
||||
|
||||
If you've got pumps attached to Grow and want to automatically water your plants, you'll need some extra configuration options.
|
||||
|
||||
See [Channel Settings](#channel-settings) and [General Settings](#general-settings) for more information on what these do.
|
||||
|
||||
```yaml
|
||||
channel1:
|
||||
@@ -29,10 +56,10 @@ general:
|
||||
alarm_interval: 1.0
|
||||
```
|
||||
|
||||
By default auto-watering is disabled and an alarm will sound every 1s if the `warn_level` is reached.
|
||||
|
||||
## Channel Settings
|
||||
|
||||
Grow has three channels which are separated into the sections `channel1`, `channel2` and `channel3`, each of these sections has the following configuration options:
|
||||
|
||||
* `water_level` - The level at which auto-watering should be triggered (soil saturation from 0.0 to 1.0)
|
||||
* `warn_level` - The level at which the alarm should be triggered (soil saturation from 0.0 to 1.0)
|
||||
* `pump_speed` - The speed at which the pump should be run (from 0.0 low speed to 1.0 full speed)
|
||||
@@ -42,5 +69,7 @@ By default auto-watering is disabled and an alarm will sound every 1s if the `wa
|
||||
|
||||
## General Settings
|
||||
|
||||
An additional `general` section can be used for global settings:
|
||||
|
||||
* `alarm_enable` - Whether to enable the alarm
|
||||
* `alarm_interval` - The interval at which the alarm should beep (in seconds)
|
||||
|
||||
@@ -6,24 +6,22 @@ from fonts.ttf import RobotoMedium as UserFont
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
logging.info("""lcd.py - Hello, World! example on the 0.96" LCD.
|
||||
logging.info(
|
||||
"""lcd.py - Hello, World! example on the 0.96" LCD.
|
||||
|
||||
Press Ctrl+C to exit!
|
||||
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
# Create LCD class instance.
|
||||
disp = ST7735.ST7735(
|
||||
port=0,
|
||||
cs=1,
|
||||
dc=9,
|
||||
backlight=12,
|
||||
rotation=270,
|
||||
spi_speed_hz=10000000
|
||||
port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=10000000
|
||||
)
|
||||
|
||||
# Initialize display.
|
||||
@@ -34,7 +32,7 @@ WIDTH = disp.width
|
||||
HEIGHT = disp.height
|
||||
|
||||
# New canvas to draw on.
|
||||
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
|
||||
img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Text settings.
|
||||
@@ -20,17 +20,29 @@ class Channel:
|
||||
(192, 225, 254), # Blue
|
||||
(196, 255, 209), # Green
|
||||
(255, 243, 192), # Yellow
|
||||
(254, 192, 192) # Red
|
||||
(254, 192, 192), # Red
|
||||
]
|
||||
|
||||
label_colours = [
|
||||
(32, 137, 251), # Blue
|
||||
(32, 137, 251), # Blue
|
||||
(100, 255, 124), # Green
|
||||
(254, 219, 82), # Yellow
|
||||
(254, 82, 82), # Red
|
||||
(254, 219, 82), # Yellow
|
||||
(254, 82, 82), # Red
|
||||
]
|
||||
|
||||
def __init__(self, display_channel, sensor_channel, pump_channel, water_level=0.5, alarm_level=0.5, pump_speed=0.7, pump_time=0.7, watering_delay=30, icon=None, auto_water=False):
|
||||
def __init__(
|
||||
self,
|
||||
display_channel,
|
||||
sensor_channel,
|
||||
pump_channel,
|
||||
water_level=0.5,
|
||||
alarm_level=0.5,
|
||||
pump_speed=0.7,
|
||||
pump_time=0.7,
|
||||
watering_delay=30,
|
||||
icon=None,
|
||||
auto_water=False,
|
||||
):
|
||||
self.channel = display_channel
|
||||
self.sensor = Moisture(sensor_channel)
|
||||
self.pump = Pump(pump_channel)
|
||||
@@ -77,13 +89,15 @@ class Channel:
|
||||
|
||||
def __str__(self):
|
||||
return """Channel: {channel}
|
||||
Water level: {water_level}
|
||||
Alarm level: {alarm_level}
|
||||
Auto water: {auto_water}
|
||||
Water level: {water_level}
|
||||
Pump speed: {pump_speed}
|
||||
Pump time: {pump_time}
|
||||
Delay: {watering_delay}
|
||||
""".format(**self.__dict__)
|
||||
""".format(
|
||||
**self.__dict__
|
||||
)
|
||||
|
||||
def water(self):
|
||||
if not self.auto_water:
|
||||
@@ -103,7 +117,10 @@ Delay: {watering_delay}
|
||||
active = self.sensor.active
|
||||
|
||||
# Draw background bars
|
||||
draw.rectangle((x, int(c * HEIGHT), x + 37, HEIGHT), self.indicator_color(c) if active else (229, 229, 229))
|
||||
draw.rectangle(
|
||||
(x, int(c * HEIGHT), x + 37, HEIGHT),
|
||||
self.indicator_color(c) if active else (229, 229, 229),
|
||||
)
|
||||
|
||||
# Draw plant image
|
||||
x -= 3
|
||||
@@ -115,37 +132,58 @@ Delay: {watering_delay}
|
||||
|
||||
# Channel selection icons
|
||||
x += 15
|
||||
draw.rectangle((x, 2, x + 15, 17), self.indicator_color(c, self.label_colours) if active else (129, 129, 129))
|
||||
draw.rectangle(
|
||||
(x, 2, x + 15, 17),
|
||||
self.indicator_color(c, self.label_colours) if active else (129, 129, 129),
|
||||
)
|
||||
|
||||
if selected:
|
||||
selected_x = x - 2
|
||||
draw.rectangle((selected_x, 0, selected_x + 19, 20), self.indicator_color(c, self.label_colours) if active else (129, 129, 129))
|
||||
draw.rectangle(
|
||||
(selected_x, 0, selected_x + 19, 20),
|
||||
self.indicator_color(c, self.label_colours)
|
||||
if active
|
||||
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=self.indicator_color(c, self.label_colours) if active else (129, 129, 129))
|
||||
draw.polygon(
|
||||
[(selected_x, 20), (selected_x + 9, 25), (selected_x + 19, 20)],
|
||||
fill=self.indicator_color(c, self.label_colours)
|
||||
if active
|
||||
else (129, 129, 129),
|
||||
)
|
||||
|
||||
# TODO: replace number text with graphic
|
||||
|
||||
tw, th = font.getsize("{}".format(self.channel))
|
||||
draw.text((x + int(math.ceil(8 - (tw / 2.0))), 2), "{}".format(self.channel), font=font, fill=(255, 255, 255))
|
||||
draw.text(
|
||||
(x + int(math.ceil(8 - (tw / 2.0))), 2),
|
||||
"{}".format(self.channel),
|
||||
font=font,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
|
||||
def update(self):
|
||||
sat = self.sensor.saturation
|
||||
if sat < self.water_level:
|
||||
if self.water():
|
||||
logging.info("Watering Channel: {} - rate {:.2f} for {:.2f}sec".format(self.channel, self.pump_speed, self.pump_time))
|
||||
logging.info(
|
||||
"Watering Channel: {} - rate {:.2f} for {:.2f}sec".format(
|
||||
self.channel, self.pump_speed, self.pump_time
|
||||
)
|
||||
)
|
||||
if sat < self.alarm_level and not self.alarm:
|
||||
logging.warning("Alarm on Channel: {} - saturation is {:.2f} (warn level {:.2f})".format(self.channel, sat, self.alarm_level))
|
||||
logging.warning(
|
||||
"Alarm on Channel: {} - saturation is {:.2f} (warn level {:.2f})".format(
|
||||
self.channel, sat, self.alarm_level
|
||||
)
|
||||
)
|
||||
self.alarm = True
|
||||
|
||||
|
||||
BUTTONS = [5, 6, 16, 24]
|
||||
LABELS = ['A', 'B', 'X', 'Y']
|
||||
LABELS = ["A", "B", "X", "Y"]
|
||||
|
||||
channel_selected = 0
|
||||
alarm = False
|
||||
@@ -158,15 +196,16 @@ for x in range(1, 15):
|
||||
|
||||
# Pick a random selection of plant icons to display on screen
|
||||
channels = [
|
||||
Channel(1, 1, 1, icon=random.choice(plants)),
|
||||
Channel(2, 2, 2, icon=random.choice(plants)),
|
||||
Channel(3, 3, 3, icon=random.choice(plants))
|
||||
Channel(1, 1, 1, icon=random.choice(plants)),
|
||||
Channel(2, 2, 2, icon=random.choice(plants)),
|
||||
Channel(3, 3, 3, icon=random.choice(plants)),
|
||||
]
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def handle_button(pin):
|
||||
@@ -174,19 +213,19 @@ def handle_button(pin):
|
||||
index = BUTTONS.index(pin)
|
||||
label = LABELS[index]
|
||||
|
||||
if label == 'A': # Select Channel
|
||||
if label == "A": # Select Channel
|
||||
channel_selected += 1
|
||||
channel_selected %= len(channels)
|
||||
|
||||
if label == 'B': # Cancel Alarm
|
||||
if label == "B": # Cancel Alarm
|
||||
alarm = False
|
||||
for channel in channels:
|
||||
channel.alarm = False
|
||||
|
||||
if label == 'X': # Set Wet Point
|
||||
if label == "X": # Set Wet Point
|
||||
channels[channel_selected].sensor.set_wet_point()
|
||||
|
||||
if label == 'Y': # Set Dry Point
|
||||
if label == "Y": # Set Dry Point
|
||||
channels[channel_selected].sensor.set_dry_point()
|
||||
|
||||
|
||||
@@ -209,12 +248,7 @@ 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
|
||||
port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=80000000
|
||||
)
|
||||
display.begin()
|
||||
WIDTH, HEIGHT = display.width, display.height
|
||||
@@ -256,7 +290,7 @@ def render():
|
||||
draw.rectangle((21, 0, 138, HEIGHT), (255, 255, 255)) # Erase channel area
|
||||
|
||||
for channel in channels:
|
||||
channel.render(image, font, channel_selected==channel.channel - 1)
|
||||
channel.render(image, font, channel_selected == channel.channel - 1)
|
||||
|
||||
# Draw the snooze icon- will be pulsing red if the alarm state is True
|
||||
r = 129
|
||||
@@ -271,7 +305,7 @@ def main():
|
||||
piezo = Piezo()
|
||||
time_last_beep = time.time()
|
||||
|
||||
settings_file = "water.yml"
|
||||
settings_file = "settings.yml"
|
||||
if len(sys.argv) > 1:
|
||||
settings_file = sys.argv[1]
|
||||
settings_file = pathlib.Path(settings_file)
|
||||
@@ -279,7 +313,9 @@ def main():
|
||||
try:
|
||||
config = yaml.safe_load(open(settings_file))
|
||||
except yaml.parser.ParserError as e:
|
||||
raise yaml.parser.ParserError("Error parsing settings file: {} ({})".format(settings_file, e))
|
||||
raise yaml.parser.ParserError(
|
||||
"Error parsing settings file: {} ({})".format(settings_file, e)
|
||||
)
|
||||
|
||||
for channel in channels:
|
||||
ch = config.get("channel{}".format(channel.channel), None)
|
||||
@@ -294,13 +330,14 @@ def main():
|
||||
for channel in channels:
|
||||
print(channel)
|
||||
|
||||
print("""Settings:
|
||||
print(
|
||||
"""Settings:
|
||||
Alarm Enabled: {}
|
||||
Alarm Interval: {:.2f}s
|
||||
""".format(
|
||||
alarm_enable,
|
||||
alarm_interval
|
||||
))
|
||||
alarm_enable, alarm_interval
|
||||
)
|
||||
)
|
||||
|
||||
while True:
|
||||
update()
|
||||
|
||||
10
examples/settings.yml
Normal file
10
examples/settings.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
channel1:
|
||||
warn_level: 0.2
|
||||
icon: icons/flat-4.png
|
||||
channel2:
|
||||
warn_level: 0.2
|
||||
channel3:
|
||||
warn_level: 0.2
|
||||
general:
|
||||
alarm_enable: True
|
||||
alarm_interval: 1.0
|
||||
@@ -29,17 +29,17 @@ 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
|
||||
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
|
||||
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']
|
||||
LABELS = ["A", "B", "X", "Y"]
|
||||
|
||||
p = Pump(pump_channel)
|
||||
m = Moisture(moisture_channel)
|
||||
@@ -53,12 +53,7 @@ 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
|
||||
port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=80000000
|
||||
)
|
||||
|
||||
display.begin()
|
||||
@@ -68,25 +63,26 @@ 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',
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
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
|
||||
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
|
||||
if label == "B": # Switch setting
|
||||
mode += 1
|
||||
mode %= 3 # Wrap 0, 1, 2 (Time, Speed, Dry level)
|
||||
mode %= 3 # Wrap 0, 1, 2 (Time, Speed, Dry level)
|
||||
|
||||
if label == 'Y': # Inc. setting
|
||||
if label == "Y": # Inc. setting
|
||||
if mode == 0:
|
||||
dose_time += 0.01
|
||||
logging.info("Dose time increased to: {:.2f}".format(dose_time))
|
||||
@@ -97,7 +93,7 @@ def handle_button(pin):
|
||||
dry_level += 0.01
|
||||
logging.info("Dry level increased to: {:.2f}".format(dry_level))
|
||||
|
||||
if label == 'X': # Dec. setting
|
||||
if label == "X": # Dec. setting
|
||||
if mode == 0:
|
||||
dose_time -= 0.01
|
||||
logging.info("Dose time decreased to: {:.2f}".format(dose_time))
|
||||
@@ -132,36 +128,76 @@ try:
|
||||
# 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))
|
||||
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))
|
||||
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.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))
|
||||
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(
|
||||
(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))
|
||||
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))
|
||||
print(
|
||||
"Dose Time: {:.2f} Dose Speed: {:.2f}, Dry Level: {:.2f}".format(
|
||||
dose_time, dose_speed, dry_level
|
||||
)
|
||||
)
|
||||
@@ -1,22 +0,0 @@
|
||||
channel1:
|
||||
water_level: 0.8
|
||||
warn_level: 0.2
|
||||
pump_speed: 0.7
|
||||
pump_time: 0.7
|
||||
auto_water: False
|
||||
icon: icons/flat-4.png
|
||||
channel2:
|
||||
water_level: 0.8
|
||||
warn_level: 0.2
|
||||
pump_speed: 0.7
|
||||
pump_time: 0.7
|
||||
auto_water: False
|
||||
channel3:
|
||||
water_level: 0.8
|
||||
warn_level: 0.2
|
||||
pump_speed: 0.7
|
||||
pump_time: 0.7
|
||||
auto_water: False
|
||||
general:
|
||||
alarm_enable: True
|
||||
alarm_interval: 1.0
|
||||
@@ -1,70 +0,0 @@
|
||||
Grow
|
||||
=======
|
||||
|
||||
Designed for looking after plants, Grow monitors moisture levels and runs simple pumps to water plants. Learn more -
|
||||
https://shop.pimoroni.com/products/grow
|
||||
|
||||
|Build Status| |Coverage Status| |PyPi Package| |Python Versions|
|
||||
|
||||
Installing
|
||||
==========
|
||||
|
||||
The one-line installer enables the correct interfaces,
|
||||
|
||||
One-line (Installs from GitHub)
|
||||
-------------------------------
|
||||
|
||||
::
|
||||
|
||||
curl -sSL https://get.pimoroni.com/grow | bash
|
||||
|
||||
**Note** report issues with one-line installer here:
|
||||
https://github.com/pimoroni/get
|
||||
|
||||
Or... Install and configure dependencies from GitHub:
|
||||
-----------------------------------------------------
|
||||
|
||||
- ``git clone https://github.com/pimoroni/grow-python``
|
||||
- ``cd grow-python``
|
||||
- ``sudo ./install.sh``
|
||||
|
||||
**Note** Raspbian Lite users may first need to install git:
|
||||
``sudo apt install git``
|
||||
|
||||
Or... Install from PyPi and configure manually:
|
||||
-----------------------------------------------
|
||||
|
||||
- Run ``sudo pip install grow``
|
||||
|
||||
**Note** this wont perform any of the required configuration changes on
|
||||
your Pi, you may additionally need to:
|
||||
|
||||
- Enable i2c: ``raspi-config nonint do_i2c 0``
|
||||
- Enable SPI: ``raspi-config nonint do_spi 0``
|
||||
|
||||
And install additional dependencies:
|
||||
|
||||
::
|
||||
|
||||
sudo apt install python-numpy python-smbus python-pil python-setuptools
|
||||
|
||||
Help & Support
|
||||
--------------
|
||||
|
||||
- GPIO Pinout - https://pinout.xyz/pinout/grow
|
||||
- Support forums - http://forums.pimoroni.com/c/support
|
||||
- Discord - https://discord.gg/hr93ByC
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.com/pimoroni/grow-python.svg?branch=master
|
||||
:target: https://travis-ci.com/pimoroni/grow-python
|
||||
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/grow-python/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/pimoroni/grow-python?branch=master
|
||||
.. |PyPi Package| image:: https://img.shields.io/pypi/v/growhat.svg
|
||||
:target: https://pypi.python.org/pypi/growhat
|
||||
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/growhat.svg
|
||||
:target: https://pypi.python.org/pypi/growhat
|
||||
|
||||
0.0.1
|
||||
-----
|
||||
|
||||
* Initial Release
|
||||
@@ -5,7 +5,8 @@ version = 0.0.1
|
||||
author = Philip Howard, Paul Beech
|
||||
author_email = paul@pimoroni.com
|
||||
description = Grow HAT Mini. A plant valet add-on for the Raspberry Pi
|
||||
long_description = file: README.rst
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
keywords = Raspberry Pi
|
||||
url = https://www.pimoroni.com
|
||||
project_urls =
|
||||
@@ -48,14 +49,14 @@ ignore =
|
||||
[pimoroni]
|
||||
py2deps =
|
||||
python-pip
|
||||
python-numpy
|
||||
python-yaml
|
||||
python-smbus
|
||||
python-pil
|
||||
python-spidev
|
||||
python-rpi.gpio
|
||||
py3deps =
|
||||
python3-pip
|
||||
python3-numpy
|
||||
python3-yaml
|
||||
python3-smbus
|
||||
python3-pil
|
||||
python3-spidev
|
||||
|
||||
@@ -22,12 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from setuptools import setup, __version__
|
||||
from pkg_resources import parse_version
|
||||
from setuptools import setup
|
||||
|
||||
minimum_version = parse_version('30.4.0')
|
||||
|
||||
if parse_version(__version__) < minimum_version:
|
||||
raise RuntimeError("Package setuptools must be at least version {}".format(minimum_version))
|
||||
|
||||
setup()
|
||||
|
||||
@@ -70,12 +70,3 @@ def atexit():
|
||||
sys.modules['atexit'] = atexit
|
||||
yield atexit
|
||||
del sys.modules['atexit']
|
||||
|
||||
|
||||
@pytest.fixture(scope='function', autouse=False)
|
||||
def numpy():
|
||||
"""Mock numpy module."""
|
||||
numpy = mock.MagicMock()
|
||||
sys.modules['numpy'] = numpy
|
||||
yield numpy
|
||||
del sys.modules['numpy']
|
||||
|
||||
@@ -9,7 +9,7 @@ def test_moisture_setup(GPIO, smbus):
|
||||
def test_moisture_read_all(GPIO, smbus):
|
||||
from grow import moisture
|
||||
moisture._is_setup = False
|
||||
|
||||
|
||||
result = moisture.read_all()
|
||||
|
||||
assert type(result(1)) == float
|
||||
|
||||
Reference in New Issue
Block a user