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_moisture_setup(GPIO, smbus): from grow.moisture import Moisture ch1 = Moisture(channel=1) ch2 = Moisture(channel=2) ch3 = Moisture(channel=3) assert GPIO.setup.has_calls([ mock.call(ch1._gpio_pin, GPIO.IN), mock.call(ch2._gpio_pin, GPIO.IN), mock.call(ch3._gpio_pin, GPIO.IN) ])
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)
def test_moisture_read(GPIO, smbus): from grow.moisture import Moisture assert Moisture(channel=1).saturation == 1.0 assert Moisture(channel=2).saturation == 1.0 assert Moisture(channel=3).saturation == 1.0 assert Moisture(channel=1).moisture == 0 assert Moisture(channel=2).moisture == 0 assert Moisture(channel=3).moisture == 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
# 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, spi_speed_hz=80000000)
import time from grow.moisture import Moisture print("""moisture.py - Print out sensor reading in Hz Press Ctrl+C to exit! """) m1 = Moisture(1) m2 = Moisture(2) m3 = Moisture(3) while True: print(f"""1: {m1.moisture} 2: {m2.moisture} 3: {m3.moisture} """) time.sleep(1.0)
def main(): args = _get_args() # Setup Logging numeric_log_level = getattr(logging, args.log.upper(), None) if not isinstance(numeric_log_level, int): raise ValueError("Invalid log level: %s" % args.log) logging.basicConfig( level=numeric_log_level, format="[%(levelname)s] %(message)s", filename=args.log_file, ) # Start logging.info("Starting up...") # Import settings config = yaml.safe_load(open(args.config_path)) # Setup moisture sensors for reading sensors = [] for sensor_num in [1, 2, 3]: sensor = Moisture(sensor_num) if ("channel{0}".format(sensor_num)) in config.keys(): sensors.append(sensor) # Setup light sensor for reading light = LTR559() soil_moisture = Gauge("grow_soil_moisture", "Soil Moisture", ["channel", "plant"]) soil_saturation = Gauge("grow_soil_saturation", "Soil Saturation", ["channel", "plant"]) soil_dry_point = Gauge("grow_soil_dry_point", "Dry Point", ["channel", "plant"]) soil_wet_point = Gauge("grow_soil_wet_point", "Dry Point", ["channel", "plant"]) lux = Gauge("grow_light_lux", "Lux") # Start prometheus server start_http_server(args.bind_port) # Gather/export data while True: # Re-read settings if changed by monitor app config = yaml.safe_load(open(args.config_path)) for i in range(0, 3): channel_num = i + 1 channel_name = "channel{}".format(channel_num) if channel_name in config.keys(): plant_name = config[channel_name]["name"] # Set wet/dry points logging.debug("Setting wet/dry points.") sensors[i].set_dry_point(config[channel_name]["dry_point"]) sensors[i].set_wet_point(config[channel_name]["wet_point"]) # Track soil moisture/saturation logging.debug("Tracking soil moisture/saturation.") soil_moisture.labels(channel_name, plant_name).set(sensors[i].moisture) soil_saturation.labels(channel_name, plant_name).set(sensors[i].saturation) # Track wet/dry points logging.debug("Tracking wet/dry points.") soil_dry_point.labels(channel_name, plant_name).set( config[channel_name]["dry_point"]) soil_wet_point.labels(channel_name, plant_name).set( config[channel_name]["wet_point"]) # Update/track light sensor lux logging.debug("Updating light sensor.") light.update_sensor() logging.debug("Tracking light lux.") lux.set(light.get_lux()) logging.debug("Sleeping for {}".format(args.interval)) time.sleep(args.interval) # End logging.info("Shutting down ...")
def __init__(self, channel, wet_point=None, dry_point=None): self._sensor = Moisture(channel, wet_point=wet_point, dry_point=dry_point) self.topic = "grow/soil/{}".format(channel)