Esempio n. 1
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
Esempio n. 2
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)
Esempio n. 3
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
Esempio n. 4
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
Esempio n. 6
0
#!/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)
Esempio n. 7
0
    while True:
        # New moisture readings are available approximately 1/sec
        # push them into the list for averagering
        if m.new_data:
            current_saturation = m.saturation
            saturation.append(current_saturation)
            saturation = saturation[-NUM_SAMPLES:]

        avg_saturation = sum(saturation) / float(NUM_SAMPLES)

        # Trigger a dose of water if the average saturation is less than the specified dry level
        # dose frequency is rate limited, so this doesn't re-trigger before the moistrure sensor
        # 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))
            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(