MistBuddy

_images/whale.svg

Warning

MistBuddy is Under Construction!

About

https://docs.google.com/drawings/d/e/2PACX-1vQnc4PqB6jgMzFOIMZqWpJ1dFUUdEsrNfNtB4n6q8jmW68PfWBYvIfANB0gqFjMqUh3rn0Cm_YLLthx/pub?w=984&h=474&align=middle

MistBuddy has a whale of a good time maintaining a grow tent’s ideal vpd level. When the vpd level is too high, MistBuddy spouts out mist through her airhole for just the right amount of time. When the vpd is too low or just right, MistBuddy shuts off until the vpd level is higher than the ideal vpd level (also known as the setpoint).

Note

Our indoor gardening environment is climate controlled. The temperature with the LED lights on is around 75℉. While vpd is dependent on both temperature and humidity, MistBuddy only changes the humidity to adjust the vpd.

MistBuddy’s Body

MistBuddy’s body is a DIY humidifier and two Tasmotized plugs. Here’s the one I built:

_images/MistBuddy.jpeg

MistBuddy Outer Shot

_images/MistBuddy_inside.jpeg

MistBuddy Inside Shot

_images/sonoffplugs.jpeg

Tasmotized Sonoff Plugs Ready for Action

Mistbuddy dispenses vapor at the end of the PVC tube by turning on a 12-head mister. Water comes into the tub through a connection with our house’s plumbing. The water is kept to a constant level in the tub by a float valve.

Let’s Make One

Comments & Questions

Attention

Before MistBuddy can run, the following must happen:

  1. There must be a working SnifferBuddy and Gus.

  2. The steps below must be completed.

1. Gather The Materials

The materials I used to make the humidifier in the image include:

_images/power__supply.jpeg

250W 48V

_images/sonoffplugs.jpeg

Tasmotized Sonoff Plugs Ready for Action

2. Build

Base and Continuous Fill

For the base and continuous fill, get the popcorn … it’s time for a How To YouTube Video on making a DIY Humidifier.

The water level for the float valve is 1 cm above the sensor line.

_images/mister_water_level.jpg

Mister Water Level

Tasmotized Plugs

MistBuddy sends mqtt messages to the mister and fan plugs. The plugs run Tasmota which handles all the mqtt messages. Two [Sonoff S31 plugs need to be flashed with Tasmota.

Software

View manage_vpd.py source

manage_vpd.py

This script utilizes MistBuddy to regulate the operation of the humidifier, turning it on and off as necessary to maintain the optimal vapor pressure deficit (VPD) level. The main() function initiates the program by establishing callback functions and subscribing to SnifferBuddy readings through the growbuddies.mqtt_code.MQTTService class. When a reading is received, the Callbacks.on_snifferbuddy_readings() callback activates the MistBuddy() class to turn on the humidifier if the vapor pressure deficit (VPD) value exceeds the setpoint value in growbuddies_settings.json.

The growbuddy_settings.json file contains several parameters that are used as input for the program.

"vpd_growth_stage": "veg",
"vpd_setpoints": {
    "veg": 0.9,
    "flower": 1.0
    }

vpd_growth_stage lets MistBuddy know what growth stage the plants are in. There are two stages: - “veg” for vegetative. - “flower” For the flowering stage.

The above settings are using by the MistBuddy code, see Callbacks.on_snifferbuddy_readings(). The settings indicate the plants are currently in the vegetative growth stage. Based on the VPD chart, a good choice for the ideal VPD value for this stage is 0.9. The VPD chart is a good reference for choosing the ideal VPD value for each growth stage.

Flu's vpd chart

The two SnifferBuddy callbacks are sent to the MQTTService():

"snifferbuddy_mqtt": {"tele/snifferbuddy/SENSOR": "on_snifferbuddy_readings",
                  "tele/snifferbuddy/LWT": "on_snifferbuddy_status"},

by passing in the “snifferbuddy_mqtt” keyword, the MQTTService() knows which callback goes with which MQTT topic, making it easy to subscribe to the topic and then callback the callback functions when a message is received.

growbuddies.manage_vpd.main()

The steps to adjust to an ideal vpd value include:

1. Receive SnifferBuddy MQTT messages. One message contains air readings such as CO2, humidity, temperature. The other message contains status information on the SnifferBuddy device.

  1. The vpd value is calculated from the temperature and humidity. This is handled by the growbuddies.snifferbuddy_readings.SnifferBuddyReadings class.

The Settings class reads in parameters used by the GrowBuddies from the growbuddy_settings.json file. One of these parameters is a dictionary containing information for subscribing to SnifferBuddy’s MQTT topics. The MQTTService class uses this dictionary to determine which callback function to use when it receives a message. Specifically, it will use the readings callback if the message contains air readings, or the status callback if the message contains SnifferBuddy status information such as “online” or “offline”.

"snifferbuddy_mqtt":
                            {"tele/snifferbuddy/SENSOR": "on_snifferbuddy_readings",
                             "tele/snifferbuddy/LWT": "on_snifferbuddy_status"},

The above is the default entry. The topic is the dictionary’s key. The name of the callback function is the value. Notice this script includes the two methods Callbacks.on_snifferbuddy_readings() and Callbacks.on_snifferbuddy_status().

class growbuddies.mistbuddy_code.MistBuddy(manage=True)

MistBuddy utilizes SnifferBuddy’s readings and a PID controller to determine the optimal duration to activate the humidifier for the amount of time needed to raise the humidity to the level needed to reach the ideal vpd level.

__init__(manage=True)

The growbuddy_settings.json file contains several parameters that are used as input for the program.

vpd_growth_stage lets MistBuddy know what growth stage the plants are in. There are two stages: - “veg” for vegetative. - “flower” For the flowering stage.

The ideal vpd level is determined from this vpd chart:

Flu's vpd chart
Args:
snifferbuddy_status_callback

This callback function is activated when Gus (our mqtt broker) sends out a Last Will and Testament (LWT) mqtt message. The function is called with a string that indicates either “online” or “offline” status. If the returned string is “offline,” it indicates that SnifferBuddy has not been sending mqtt messages. This callback function is optional and is set to “None” by default.

growth_stage

This parameter specifies the growth stage of the plant, either vegetation or flowering. This is important to set to either the vegetative (“veg”) or flowering (“flower”) string.

readings_table_name

A string that will be the name of the table (or a Measurement using influxdb terminology) in InfluxDB containing SnifferBuddy readings, including vpd, while MistBuddy is running. By default, SnifferBuddy readings will not be stored.

manage

A False setting for this parameter causes MistBuddy to refrain from turning the humidifier on and off. This can be helpful for initial debugging, but it has little effect on the PID controller’s output. The default setting is True.

Raises:

The code checks if the vpd setpoint is within an expected range. If it is not, an exception is raised.

hfellpo

Args:

manage (bool, optional): _description_. Defaults to True.

Raises:

Exception: _description_

adjust_humidity(vpd: float) None

This method calls the PID controller and turns the humidifier on if the PID controller determines that the vpd is too low.

Args:

s (SnifferBuddyReadings): A reading from SnifferBuddy within an instance of SnifferBuddyReadings.

turn_off_mistBuddy() None

The timer set in _turn_on_mistBuddy has expired. Send messages to the mistBuddy plugs to turn OFF.

turn_on_mistBuddy(nSecondsON: int) None

Sends an mqtt messages to mistBuddy’s two power sources plugged into Tasmotized Smart plugs, a fan and a mister. The self.fan_power_topic and self.mister_power_topic are set in the __init__ method.

Args:

nSecondsON (int): The number of seconds to turn the plugs on.

A timer is started based on the number of seconds MistBuddy should be on. When the timer expires, the _turn_off_mistBuddy method is called. A connection to the mqtt broker is made, and the mqtt message payload “ON” is sent to the two plugs.

class growbuddies.PID_code.PID

The PID controller returns how many seconds to turn the DIY humidifier on. This class is a modified version of the simple-pid package. Which evolved from Brett Beauregard’s Arduino PID controller. The modification uses the time between mqtt messages as the (fairly) consistent sampling time instead of the system clock.

The PID controller is initialized with values from the growbuddies_settings.file.:

"vpd_growth_stage": "veg",
"vpd_setpoints": {
    "veg": 0.9,
    "flower": 1.0
    }
"PID_settings": {
"Kp": 240,
"Ki": 0.1,
"Kd": 0.1,
"output_limits": [0, 20],
"integral_limits":[0, 7],
"tolerance": 0.01
}

The meanings of the parameters will be discussed below under __init__().

__init__()

Initialize a new PID controller. Instead of passing parameters into the constructor, the PID controller is initialized with values from the growbuddies_settings.file.:

Args:
vpd_growth_stage

The growth stage of the plants being cared for. This can be either ‘veg’ or ‘flower’.

vpd_setpoints

The setpoint for the PID controller. This is the target value that the PID controller will attempt to achieve.If the vpd_growth_stage is set to ‘veg’, the setpoint will be 0.9. If the vpd_growth_stage is set to ‘flower’, the setpoint will be 1.0.

Kp

The value for the proportional gain Kp.

Ki

The value for the integral gain Ki.

Kd

The value for the derivative gain Kd.

output_limits

The upper limit for the humidifier to be on is determined by the output_limits. For example, if the output_limits is set to [0,20], the humidifier will turn off after 20 seconds, even if the PID calculation suggests it should run for a longer duration.

integral_limits

The upper limit for the integral is determined by the integral_limits. As the process continues, the integral value will increase. If the integral_limits is set to [0,7], the integral influence will be limited to 7 seconds.

tolerance

If the vpd_current is within the tolerance of the setpoint, the PID controller will return 0 for the number of seconds to turn the humidifier on.

Note

See The section on PID tuning for more information on tuning these values.

__call__(vpd_current)

Update the PID controller with the vpd value just calculated from the latest SnifferBuddyReading and figure out how many seconds to turn on the humidifier if the vpd value is above the vpd setpoint (i.e.: ideal) value. 0 seconds is returned if the vpd value is and calculate and return a control output if sample_time seconds has passed since the last update.

Args:
vpd_current

The most recent vpd value calculated from the latest SnifferBuddyReading.

_compute_terms(d_vpd, error, dt)

Compute the integral and derivative terms. Clamp the integral term to prevent it from growing too large.

Args:
d_vpd (float)

The difference between the current and previous vpd values. Used by the derivative term.

error (float)

The difference between the current vpd value and the setpoint. Used by the proportional integral term.

dt (float)

The difference in time between the current and previous vpd values. Used by the integral and derivative terms.

property components

The P-, I- and D-terms from the last computation as separate components as a tuple. Useful for visualizing what the controller is doing or when tuning hard-to-tune systems.

property tunings

The tunings used by the controller as a tuple: (Kp, Ki, Kd).

property output_limits

The current output limits as a 2-tuple: (lower, upper).

See also the output_limits parameter in PID.__init__().

PID Tuning

According to the vpd chart, the ideal vpd value when the plant is in the vegetative growth stage ranges between 0.8 and 0.95. The ideal vpd value when the plant is in the flowering growth stage ranges between 0.95 and 1.15. The setpoints comfortably fit within these ranges.

I came up with the following values for Kp, Ki, Kd after runs I made in my attempt to tune the PID controller:

    "PID_settings": {
        "Kp": 43,
        "Ki": 0.1,
        "Kd": 0.1,
        "output_limits": [0, 30]
    }

Using 43 for the Kp gain includes converting from vpd errors that are in tenths values to several seconds to turn on the humidifier.

Let’s Play

Comments & Questions

The GrowBuddies package includes a script, manage-vpd, that works tirelessly to maintain the vpd level given a vegetative setpoint vpd of 0.9 and a flowering setpoint vpd of 1.0.s

Tuning the PID

The following discusses my tuning steps.

Note

For tuning purposes, I used a value of 0.8 for the vpd setpoint. This was because I knew the vpd in the grow tent was about 1.2, which makes the PID controller kick into action to adjust the vpd down.

Challenges

  • The input, setpoint, and error terms are all in floating point units relative to vpd readings. vpd readings are typically between 0.0 and 2.0. The output is the number of seconds to turn on MistBuddy. Thus, the output includes a conversion from vpd (floating point) to the number of seconds (integer)

  • Spewing out vapor into the air using MistBuddy is imprecise. Luckily, the vpd does not have to be precise as shown in the vpd range chart

Getting Started

I start turning by just focusing on the P value. Given the vpd in my grow tent with the lights on is around 1.2, I’ll use a vpd setpoint of 0.8 to adjust.

For example:

  • vpd setpoint = 0.8

  • vpd reading = 1.2

  • the vpd error is -0.4 The negative error says MistBuddy needs to be turned on for several seconds. But how many seconds? The vpd error units are mapped into the Kp value such that the output from the PID controller is the majority of the number of seconds to turn on MistBuddy. If Kp = 43 then MistBuddy turns on the humidifier for abs(43 * -0.4) = 17 seconds.

Starting with P

I ran two runs with setting just the P gain. As shown in the two plots below, setting just the P gain gives an output with a steady state error.

As noted by a StackOverflow answer

The reason for a steady state error with P only is that as your system approaches the set-point the error signal gets smaller and smaller. Your control is Kp times that error signal and eventually the error will be small enough that Kp times the error won’t be enough to force it all the way to zero. An Integrator “saves the day” by accumulating the error over time and therefore even the tiniest error will eventually accumulate to something large enough to force the controller to correct for it.

From these results, I added in a Ki of 0.1.

Kp = 50

I’m expecting an very gradual increase.

     "PID_settings": {
        "Kp": 50.0,
        "Ki": 0.0,
        "Kd": 0.0
    }
  • Add sniferbuddy_table_name input when instantiating a MistBuddy instance in the vpdbuddy_manage.py. This will store the snifferBuddy readings into the snifferBuddy measurement table of the growBuddy influxdb database.

def main():
    vpdbuddy = MistBuddy(vpd_values_callback=vpd_values_callback, manage=True, snifferbuddy_table_name="snifferbuddy")
    vpdbuddy.start()
(pyenv) $ python code/examples/vpdbuddy_manage.py
  • View a graph.

JUST P: Kp=50, Ki=0, Kd=0

PID(Kp=50, Ki=0, Kd=0, setpoint=0.8, sample_time=0.01, output_limits=(None, None), auto_mode=True, proportional_on_measurement=False, error_map=None, mqtt_time=True)
_images/grafana_vpd_08_50_0_0.jpg

MistBuddy Kp=50, Ki=0, Kd=0

JUST P: Kp=25, Ki=0, Kd=0

_images/grafana_vpd_08_25_0_0.jpg

MistBuddy Kp=25, Ki=0, Kd=0

PI: Kp=45, Ki=0.1, Kd=0

PID(Kp=45, Ki=0.1, Kd=0, setpoint=0.8, sample_time=0.01, output_limits=(None, None), auto_mode=True, proportional_on_measurement=False, error_map=None, mqtt_time=True)
_images/grafana_vpd_08_45_01_0.jpg

MistBuddy Kp=45, Ki=0.1, Kd=0

Kp=45, Ki=0.1, Kd=0.1

PID(Kp=45, Ki=0.1, Kd=0.1, setpoint=0.8, sample_time=0.01, output_limits=(None, None), auto_mode=True, proportional_on_measurement=False, error_map=None, mqtt_time=True)
_images/grafana_vpd_08_45_01_01.jpg

MistBuddy Kp=45, Ki=0.1, Kd=0.1

Woops! MistBuddy stopped working for a bit and…as expected…

Kp=40, Ki=0.1, Kd=0.01

PID(Kp=45, Ki=0.1, Kd=0.1, setpoint=0.8, sample_time=0.01, output_limits=(None, None), auto_mode=True, proportional_on_measurement=False, error_map=None, mqtt_time=True)
_images/grafana_vpd_08_40_01_001.jpg

MistBuddy Kp=40, Ki=0.1, Kd=0.01

Kp=43, Ki=0.1, Kd=0

_images/grafana_vpd_08_43_01_0.jpg

MistBuddy Kp=43, Ki=0.1, Kd=0

Kp=43, Ki=0.1, Kd=0.1

_images/grafana_vpd_08_43_01_01.jpg

MistBuddy Kp=43, Ki=0.1, Kd=0

Starting the MistBuddy Service

  • Follow the steps to enable the

Resources

I found these resources helpful in my learning.

vpd

The term “Vapor Pressure Deficit” is not that obvious to immediately understand (at least for me). I found these resources helpful to better understand VPD:

PID controller

  • Udemy course. While the course notes arduino as the cpu/IDE, what I liked was the intuitive simplicity of this course. For example, it is pointed out that if we just use the P term there is a steady state error, etc.

  • Brett Beauregard documentation on his PID. MistBuddy uses a modification of the simple-pid library, which was a port of this work (see [PID]). A reason to love and support the Open Source Community.

VPD Chart

The source for the ideal range is the Flu Cultivation Guide. MistBuddy gets the ideal vpd levels from the growbuddy_settings.json file.

_images/vpd_chart1.jpg
  • vegetative state is 0.8 to 0.95

  • flower state is 0.96 to 1.15

From Germination until ready for the vegetative state, the plants germinate under a humidity dome. Once I take off the dome the plants are in a vegetative state. This is when MistBuddy starts.

If the ideal VPD values are maintained during the vegetative and flowering state, MistBuddy is doing its job!