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)
Example #2
0
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
Example #3
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)
    ])
Example #4
0
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)
Example #5
0
    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)
Example #6
0
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)
Example #9
0
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,
Example #10
0
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