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, wet_point=0.7, dry_point=26.7, icon=None, auto_water=False, enabled=False, ): self.channel = display_channel self.sensor = Moisture(sensor_channel) self.pump = Pump(pump_channel) self.water_level = water_level self.alarm_level = alarm_level self.auto_water = auto_water self.pump_speed = pump_speed self.pump_time = pump_time self.watering_delay = watering_delay self.wet_point = wet_point self.dry_point = dry_point self.last_dose = time.time() self.icon = icon self.enabled = enabled self.alarm = False self.sensor.set_wet_point(wet_point) self.sensor.set_dry_point(dry_point)
def test_pumps_actually_stop(GPIO, smbus): from grow.pump import Pump, global_lock ch1 = Pump(channel=1) ch1.dose(speed=0.5, timeout=0.05, blocking=False) time.sleep(0.1) assert ch1.get_speed() == 0
def test_pump_setup(GPIO, smbus): from grow.pump import Pump, PUMP_PWM_FREQ ch1 = Pump(channel=1) ch2 = Pump(channel=2) ch3 = Pump(channel=3) assert GPIO.setup.has_calls([ mock.call(ch1._gpio_pin, GPIO.OUT, initial=GPIO.LOW), mock.call(ch2._gpio_pin, GPIO.OUT, initial=GPIO.LOW), mock.call(ch3._gpio_pin, GPIO.OUT, initial=GPIO.LOW) ]) assert GPIO.PWM.has_calls([ mock.call(ch1._gpio_pin, PUMP_PWM_FREQ), mock.call(ch2._gpio_pin, PUMP_PWM_FREQ), mock.call(ch3._gpio_pin, PUMP_PWM_FREQ) ])
def test_pumps_run_sequentially(GPIO, smbus): from grow.pump import Pump, global_lock ch1 = Pump(channel=1) ch2 = Pump(channel=2) ch3 = Pump(channel=3) assert ch1.dose(speed=0.5, timeout=0.1, blocking=False) is True assert global_lock.locked() is True time.sleep(0.3) assert ch2.dose(speed=0.5, timeout=0.1, blocking=False) is True assert global_lock.locked() is True time.sleep(0.3) assert ch3.dose(speed=0.5, timeout=0.1, blocking=False) is True assert global_lock.locked() is True time.sleep(0.3)
def __init__( self, display_channel, sensor_channel, pump_channel, title=None, water_level=0.5, warn_level=0.5, pump_speed=0.5, pump_time=0.2, watering_delay=60, wet_point=0.7, dry_point=26.7, icon=None, auto_water=False, enabled=False, ): self.channel = display_channel self.sensor = Moisture(sensor_channel) self.pump = Pump(pump_channel) self.water_level = water_level self.warn_level = warn_level self.auto_water = auto_water self.pump_speed = pump_speed self.pump_time = pump_time self.watering_delay = watering_delay self._wet_point = wet_point self._dry_point = dry_point self.last_dose = time.time() self.icon = icon self._enabled = enabled self.alarm = False self.title = f"Channel {display_channel}" if title is None else title self.sensor.set_wet_point(wet_point) self.sensor.set_dry_point(dry_point)
class Channel: colors = [COLOR_BLUE, COLOR_GREEN, COLOR_YELLOW, COLOR_RED] def __init__( self, display_channel, sensor_channel, pump_channel, title=None, water_level=0.5, warn_level=0.5, pump_speed=0.5, pump_time=0.2, watering_delay=60, wet_point=0.7, dry_point=26.7, icon=None, auto_water=False, enabled=False, ): self.channel = display_channel self.sensor = Moisture(sensor_channel) self.pump = Pump(pump_channel) self.water_level = water_level self.warn_level = warn_level self.auto_water = auto_water self.pump_speed = pump_speed self.pump_time = pump_time self.watering_delay = watering_delay self._wet_point = wet_point self._dry_point = dry_point self.last_dose = time.time() self.icon = icon self._enabled = enabled self.alarm = False self.title = f"Channel {display_channel}" if title is None else title self.sensor.set_wet_point(wet_point) self.sensor.set_dry_point(dry_point) @property def enabled(self): return self._enabled @enabled.setter def enabled(self, enabled): self._enabled = enabled @property def wet_point(self): return self._wet_point @property def dry_point(self): return self._dry_point @wet_point.setter def wet_point(self, wet_point): self._wet_point = wet_point self.sensor.set_wet_point(wet_point) @dry_point.setter def dry_point(self, dry_point): self._dry_point = dry_point self.sensor.set_dry_point(dry_point) def warn_color(self): value = self.sensor.moisture def indicator_color(self, value): value = 1.0 - value if value == 1.0: return self.colors[-1] if value == 0.0: return self.colors[0] value *= len(self.colors) - 1 a = int(math.floor(value)) b = a + 1 blend = float(value - a) 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) def update_from_yml(self, config): if config is not None: self.pump_speed = config.get("pump_speed", self.pump_speed) self.pump_time = config.get("pump_time", self.pump_time) self.warn_level = config.get("warn_level", self.warn_level) self.water_level = config.get("water_level", self.water_level) self.watering_delay = config.get("watering_delay", self.watering_delay) self.auto_water = config.get("auto_water", self.auto_water) self.enabled = config.get("enabled", self.enabled) self.wet_point = config.get("wet_point", self.wet_point) self.dry_point = config.get("dry_point", self.dry_point) pass def __str__(self): return """Channel: {channel} Enabled: {enabled} Alarm level: {warn_level} Auto water: {auto_water} Water level: {water_level} Pump speed: {pump_speed} Pump time: {pump_time} Delay: {watering_delay} Wet point: {wet_point} Dry point: {dry_point} """.format( channel=self.channel, enabled=self.enabled, warn_level=self.warn_level, auto_water=self.auto_water, water_level=self.water_level, pump_speed=self.pump_speed, pump_time=self.pump_time, watering_delay=self.watering_delay, wet_point=self.wet_point, dry_point=self.dry_point, ) def water(self): if not self.auto_water: return False if time.time() - self.last_dose > self.watering_delay: self.pump.dose(self.pump_speed, self.pump_time, blocking=False) self.last_dose = time.time() return True return False def render(self, image, font): pass def update(self): if not self.enabled: return 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)) if sat < self.warn_level: if not self.alarm: logging.warning( "Alarm on Channel: {} - saturation is {:.2f}% (warn level {:.2f}%)" .format(self.channel, sat * 100, self.warn_level * 100)) self.alarm = True else: self.alarm = False
class Channel: 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 ] 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, wet_point=0.7, dry_point=26.7, icon=None, auto_water=False, enabled=False, ): self.channel = display_channel self.sensor = Moisture(sensor_channel) self.pump = Pump(pump_channel) self.water_level = water_level self.alarm_level = alarm_level self.auto_water = auto_water self.pump_speed = pump_speed self.pump_time = pump_time self.watering_delay = watering_delay self.wet_point = wet_point self.dry_point = dry_point self.last_dose = time.time() self.icon = icon self.enabled = enabled self.alarm = False self.sensor.set_wet_point(wet_point) self.sensor.set_dry_point(dry_point) def indicator_color(self, value, r=None): if r is None: r = self.bar_colours if value == 1.0: return r[-1] if value == 0.0: return r[0] value *= len(r) - 1 a = int(math.floor(value)) b = a + 1 blend = float(value - a) r, g, b = [int(((r[b][i] - r[a][i]) * blend) + r[a][i]) for i in range(3)] return (r, g, b) def update_from_yml(self, config): if config is not None: self.pump_speed = config.get("pump_speed", self.pump_speed) self.pump_time = config.get("pump_time", self.pump_time) self.alarm_level = config.get("alarm_level", self.alarm_level) self.water_level = config.get("water_level", self.water_level) self.watering_delay = config.get("watering_delay", self.watering_delay) self.auto_water = config.get("auto_water", self.auto_water) self.enabled = config.get("enabled", self.enabled) self.wet_point = config.get("wet_point", self.wet_point) self.dry_point = config.get("dry_point", self.dry_point) icon = config.get("icon", None) if icon is not None: self.icon = Image.open(icon) pass def __str__(self): return """Channel: {channel} Enabled: {enabled} Alarm level: {alarm_level} Auto water: {auto_water} Water level: {water_level} Pump speed: {pump_speed} Pump time: {pump_time} Delay: {watering_delay} Wet point: {wet_point} Dry point: {dry_point} """.format( **self.__dict__ ) def water(self): if not self.auto_water: return False if time.time() - self.last_dose > self.watering_delay: self.pump.dose(self.pump_speed, self.pump_time, blocking=False) self.last_dose = time.time() return True return False def render(self, image, font, selected=False): draw = ImageDraw.Draw(image) x = [21, 61, 101][self.channel - 1] # Saturation amounts from each sensor c = 1.0 - self.sensor.saturation active = self.sensor.active and self.enabled if active: # Draw background bars draw.rectangle( (x, int(c * HEIGHT), x + 37, HEIGHT), self.indicator_color(c) if active else (229, 229, 229), ) # Draw plant image x -= 3 y = HEIGHT - self.icon.height pl = self.icon if not active: pl = pl.convert("LA").convert("RGB") image.paste(pl, (x, y), mask=self.icon) # 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), ) 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), ) # 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), ) # 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), ) def update(self): if not self.enabled: return 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 ) ) 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 ) ) self.alarm = True
#!/usr/bin/python3 import yaml from grow.pump import Pump # Import settings config = yaml.safe_load(open('/etc/default/grow')) pump = Pump(1) pump.dose(config['channel1']['pump_speed'], config['channel1']['pump_time'], blocking=False)
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 # Here be dragons! 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"] p = Pump(pump_channel) m = Moisture(moisture_channel) GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP) mode = 0 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,
def test_pumps_are_mutually_exclusive(GPIO, smbus): from grow.pump import Pump, global_lock ch1 = Pump(channel=1) ch2 = Pump(channel=2) ch3 = Pump(channel=3) ch1.dose(speed=0.5, timeout=1.0, blocking=False) assert global_lock.locked() is True assert ch2.dose(speed=0.5) is False assert ch2.dose(speed=0.5, blocking=False) is False assert ch3.dose(speed=0.5) is False assert ch3.dose(speed=0.5, blocking=False) is False