From 3123e27939205a2818466370e789a6fbf22cd550 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 27 Aug 2020 12:26:08 +0100 Subject: [PATCH] Tweaks, fixes, linting and docs --- Makefile | 12 +- README.md | 21 +-- REFERENCE.md | 14 +- examples/README.md | 37 +++++- .../lcd-demo.py} | 20 ++- examples/grow-monitor-and-water.py | 121 ++++++++++++------ examples/settings.yml | 10 ++ examples/{ => tools}/calibrate-pump.py | 94 +++++++++----- examples/water.yml | 22 ---- library/README.rst | 70 ---------- library/setup.cfg | 7 +- library/setup.py | 7 +- library/tests/conftest.py | 9 -- library/tests/test_setup.py | 2 +- 14 files changed, 229 insertions(+), 217 deletions(-) rename examples/{simple-lcd-demo.py => advanced/lcd-demo.py} (76%) create mode 100644 examples/settings.yml rename examples/{ => tools}/calibrate-pump.py (64%) delete mode 100644 examples/water.yml delete mode 100644 library/README.rst diff --git a/Makefile b/Makefile index d2bba49..23fbcd4 100644 --- a/Makefile +++ b/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 " " --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile + @! 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} @echo "Checking library/${LIBRARY_NAME}/__init__.py" @@ -36,14 +36,14 @@ check: tag: git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" -python-readme: library/README.rst +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 - cat library/CHANGELOG.txt >> 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 cp LICENSE library/LICENSE.txt diff --git a/README.md b/README.md index 9619a79..95e4e25 100644 --- a/README.md +++ b/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 diff --git a/REFERENCE.md b/REFERENCE.md index 46726d4..50dce07 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,7 +1,5 @@ # Grow -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. diff --git a/examples/README.md b/examples/README.md index bf9e5d6..23f7b1e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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) diff --git a/examples/simple-lcd-demo.py b/examples/advanced/lcd-demo.py similarity index 76% rename from examples/simple-lcd-demo.py rename to examples/advanced/lcd-demo.py index 10413b9..d922491 100644 --- a/examples/simple-lcd-demo.py +++ b/examples/advanced/lcd-demo.py @@ -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. diff --git a/examples/grow-monitor-and-water.py b/examples/grow-monitor-and-water.py index 0388263..40abf6a 100644 --- a/examples/grow-monitor-and-water.py +++ b/examples/grow-monitor-and-water.py @@ -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() diff --git a/examples/settings.yml b/examples/settings.yml new file mode 100644 index 0000000..a17f1d1 --- /dev/null +++ b/examples/settings.yml @@ -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 diff --git a/examples/calibrate-pump.py b/examples/tools/calibrate-pump.py similarity index 64% rename from examples/calibrate-pump.py rename to examples/tools/calibrate-pump.py index 0d715f7..f12b329 100644 --- a/examples/calibrate-pump.py +++ b/examples/tools/calibrate-pump.py @@ -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 + ) + ) diff --git a/examples/water.yml b/examples/water.yml deleted file mode 100644 index e3b5f8c..0000000 --- a/examples/water.yml +++ /dev/null @@ -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 diff --git a/library/README.rst b/library/README.rst deleted file mode 100644 index dfcb6d9..0000000 --- a/library/README.rst +++ /dev/null @@ -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 diff --git a/library/setup.cfg b/library/setup.cfg index 0b5f28b..ac09b9f 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -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 diff --git a/library/setup.py b/library/setup.py index e7a35bb..725f19e 100644 --- a/library/setup.py +++ b/library/setup.py @@ -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() diff --git a/library/tests/conftest.py b/library/tests/conftest.py index 9de2bd3..a6f97c9 100644 --- a/library/tests/conftest.py +++ b/library/tests/conftest.py @@ -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'] diff --git a/library/tests/test_setup.py b/library/tests/test_setup.py index 2405135..254a7c9 100644 --- a/library/tests/test_setup.py +++ b/library/tests/test_setup.py @@ -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