MistBuddy
Warning
MistBuddy is Under Construction!
About
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:
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
Attention
Before MistBuddy can run, the following must happen:
There must be a working SnifferBuddy and Gus.
The steps below must be completed.
1. Gather The Materials
The materials I used to make the humidifier in the image include:
Waterproof IP67 12v Fan. The first fan I used was a leftover fan. It broke pretty soon. I got this fan. It is more powerful and waterproof. It also has a nice, solid connector connecting the power source to the fan. What’s not to like?
Power source for the fan. This is a very compact power source that has the right connector to connect the fan.
Mist maker from Aliexpress. I did not do any measurements to determine the ideal amount of misters. Perhaps less can be used.
Power source for the mist maker. The Aliexpress listing notes a power source of 350W and 48V is needed. I have an old power supply that is less powerful than this that works fine.
Float Valve to stop the constantly running water line from filling the tub.
1/2” Barb to 1/2 ” NPT female connector Note: The connector fittings assume 1/2” PEX connector to incoming water.
Two [Sonoff S31 plugs need to be flashed with Tasmota. One plug is used to turn on/off the 48V power supply for the mister. The other is to turn on/off the power supply to the fan.
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.
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
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.
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.
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()
andCallbacks.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:
- 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
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.
Set the K settings in growBuddy_settings.json.
"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()
Set the time between mqtt messages to be one minute.
Run vpdbuddy_manage.py.
(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)
JUST P: 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)
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)
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)
Kp=43, Ki=0.1, Kd=0
Kp=43, Ki=0.1, Kd=0.1
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.
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!