How to resolve conf...
 
Notifications
Clear all

How to resolve conflits with PID Controllers using the same output?  

   RSS

1

What happens if I have two PID controller functions that are giving Mycodo conflicting commands? For example, if my temperature controller uses a heater to raise temperature and a fogger to lower it, and my humidity controller uses the same fogger to raise humidity and an intake fan to lower it, might there be a scenario (where the chamber is too hot and too humid) where the temperature controller keeps telling the fogger to turn on, and the humidity controller keeps telling it to turn off again? What is the simplest way to handle this scenario?

@joe_sullivan After thinking about this more, I suspect you may be misunderstanding how the PID controllers are operating. The PID controllers don't turn off outputs, so the reason for the conflict you mention isn't an accurate description of what's happening. The PID controllers merely turn outputs on for a duration. If, during that duration, they are instructed to turn on for another duration, they stay on and the duration is updated to the new duration to be on. I recommend you turn Log Level: Debug on for both of your PID controllers (as well as potentially Configure -> Enable Daemon Debug Logging and restart the daemon) and analyze the Daemon Log to understand what is going on (disable ALL other controllers not being used or you may have way too many log lines to handle). You may just have a bad PID tuning that is resulting in some weird behavior. If you supply more information to your original question post, I can help determine this. You should include PID settings and two graphs (one for each PID), each containing the PID Output Duration, PID Setpoint, Output Duration, and Input Measurement. This information will be invaluable to understanding how they're interacting.

Topic Tags
3 Answers
2

Another solution using a Conditional Controller utilizes the PIDControl class I just created (slated for release in Mycodo 8.5.0, otherwise upgrade to master). This class allows a custom PID controller to be easily created in any Python code in Mycodo, including Conditional Statements. Here's a simple example Conditional Statement for operating two PID controllers to modulate 3 outputs. You'll need to add two Measurement Conditions, one for temperature and the other for humidity. Then, enter the full IDs for the 3 outputs being used. I've added comments to describe what's happening, but it should be fairly straightforward. This is a bare-bones example, so there's lots of different directions this can be taken. This is currently untested.

  • Add a "Controller: Conditional" on the Setup -> Function page, then expand the options for that Conditional controller, and continue.
  • Set the Period to 30 seconds and enable Logging Level: Debug (this can be turned off later, but is valuable for testing whether the code is working by having debug log lines scattered throughout the code).
  • Add 2 "Measurement (Single, Last)" Conditions. Set one to your temperature measurement and save, then set the other one to your humidity measurement and save. In the code, below, find "self.condition("{asdf1234}")" for each measurement and replace the short ID with the ID found under each of the Conditions you just added.
  • Go to the Setup -> Output page and hover your mouse over the disabled button for each output and notice it will popup showing the ID of the output. If you click the button, the ID will be copied to your clipboard. Copy the ID for each of the outputs and replace "FULL_OUTPUT_ID" with each output for the humidifier, exhaust, and heater outputs.
  • You should now have the Conditional set up to measure the temperature and humidity, and manipulate the 3 outputs. Activate and go to the Daemon Log to see if it's working. Also, add a graph with Input, Output, and PID measurements to further verify what it's doing.

 

import time
from mycodo.utils.pid_controller_default import PIDControl

output_id_humidifier = "FULL_OUTPUT_ID"
output_id_exhaust = "FULL_OUTPUT_ID"
output_id_heater = "FULL_OUTPUT_ID"

period = 30

pid_1_setpoint = 20
pid_1_kp = 1.0
pid_1_ki = 0.1
pid_1_kd = 0.01
pid_1_integrator_min = -100
pid_1_integrator_max = 100
pid_1_direction = "both"
pid_1_band = 0

pid_2_setpoint = 60
pid_2_kp = 1.0
pid_2_ki = 0.1
pid_2_kd = 0.01
pid_2_integrator_min = -100
pid_2_integrator_max = 100
pid_2_direction = "both"
pid_2_band = 0

# Initialize
if 'timer' not in self.variables:
self.variables['timer'] = time.time()

if 'PID_Temperature' not in self.variables:
self.variables['PID_Temperature'] = PIDControl(
self.logger, pid_1_setpoint, pid_1_kp, pid_1_ki, pid_1_kd, pid_1_direction,
pid_1_band, pid_1_integrator_min, pid_1_integrator_max)
self.logger.debug("PID 1 initialized")

if 'PID_Humidity' not in self.variables:
self.variables['PID_Humidity'] = PIDControl(
self.logger, pid_2_setpoint, pid_2_kp, pid_2_ki, pid_2_kd, pid_2_direction,
pid_2_band, pid_2_integrator_min, pid_2_integrator_max)
self.logger.debug("PID 2 initialized")

# Start loop
while self.running:
if time.time() > self.variables['timer']:
while time.time() > self.variables['timer']:
self.variables['timer'] = self.variables['timer'] + period
# Get latest measurements
measurement_temperature = self.condition("{asdf1234}")
measurement_humidity = self.condition("{asdf1234}")
self.logger.debug("Last measurements: temperature: {temp}, humidity: {hum}".format(
temp=measurement_temperature, hum=measurement_humidity))

# Update control variables (CV)
self.variables['PID_Temperature'].update_pid_output(measurement_temperature)
self.variables['PID_Humidity'].update_pid_output(measurement_humidity)
self.logger.debug("Control variables: PID_Temp: {cvt}, PID_Hum: {cvh}".format(
cvt=self.variables['PID_Temperature'].control_variable,
cvh=self.variables['PID_Humidity'].control_variable))

# Determine if temperature and humidity need to raise or lower
if self.variables['PID_Temperature'].control_variable > 0:
# CV positive: Temperature is too low and needs to be raised
raise_temperature = True
lower_temperature = False
else:
# CV negative: Temperature is too high and needs to be lowered
raise_temperature = False
lower_temperature = True

if self.variables['PID_Humidity'].control_variable > 0:
# CV positive: Humidity is too low and needs to be raised
raise_humidity = True
lower_humidity = False
else:
# CV negative: Humidity is too high and needs to be lowered
raise_humidity = False
lower_humidity = True

self.logger.debug(
"Temperature: Raise: {tr}, Lower: {tl}; "
"Humidity: Raise: {hr}, Lower: {hl}".format(
tr=raise_temperature, tl=lower_temperature,
hr=raise_humidity, hl=lower_humidity))

# Manipulate outputs based on need to raise or lower temperature and humidity
if lower_humidity and lower_temperature:
# Here is a conflict where the humidifier should stay off to lower humidity
# but should also turn on to lower temperature.
# What to do? Average absolute values of control variables (they're negative),
# and run exhaust for that duration.
average_control_variables = (
abs(self.variables['PID_Temperature'].control_variable) +
abs(self.variables['PID_Humidity'].control_variable)) / 2
self.logger.debug("Both Hum and Temp lower. CV average is {}".format(
average_control_variables))
control.output_on(
output_id_exhaust, amount=average_control_variables)
else:
# Run heater to raise temperature, humidifier to lower temperature
if raise_temperature:
self.logger.debug("Raise temperature")
control.output_on(
output_id_heater,
amount=self.variables['PID_Temperature'].control_variable)
else:
self.logger.debug("Lower temperature")
control.output_on(
output_id_humidifier,
amount=abs(self.variables['PID_Temperature'].control_variable))

# Run humidifier to raise humidity, exhaust to lower humidity
if raise_humidity:
self.logger.debug("Raise humidity")
control.output_on(
output_id_humidifier,
amount=self.variables['PID_Humidity'].control_variable)
else:
self.logger.debug("Lower humidity")
control.output_on(
output_id_exhaust,
amount=abs(self.variables['PID_Humidity'].control_variable))

Mycodo Developer

@kylegabriel
This in particular seems like a great and flexible solution, but I'm having trouble figuring out how to input this code. I tried pasting it into the "Conditional Statement" window and also saving it as a .py file and uploading it as a custom command, but neither of those seemed to work.

While certainly not a Python expert, I know enough to be fairly confident that I can at least modify the code to fit my specific needs. Once I have this sample code running properly I should be good to go.

I just updated the post with instructions and updated the code with debugging lines that will help during testing.

@kylegabriel
I followed the instructions and pasted the modified code in the Conditional Statement box. When I try to save the changes, I get hit with an "Error 502: Bad Gateway Error" page.

1

Here is an alternate way to achieve modulating 3 outputs based on 2 measurements, using a Conditional Controller. Although this may not have the regulation accuracy of a PID controller, it is likely easier to tune. In fact, many applications where I see PID controllers in use can be substituted with simple logic to achieve the same effect. By using this logic in a Conditional Controller, you can easily determine how to respond with the use of three outputs based on two measurement values. The following Conditional Statement assumes there are temperature and humidity measurements as well as heater, exhaust, and humidity outputs.

setpoint_temperature_max = 27
setpoint_temperature_min = 25
setpoint_humidity_max = 60
setpoint_humidity_min = 50

amount_on_humidifier = 10
amount_on_heater = 15
amount_on_exhaust = 20

output_heater_id = "HEATER_OUTPUT_FULL_ID"
output_exhaust_id = "EXHAUST_OUTPUT_FULL_ID"
output_humidifier_id = "HUMIDIFIER_OUTPUT_FULL_ID"

measure_temperature = self.condition("{MEASURE_TEMPERATURE_SHORT_ID}")
measure_humidity = self.condition("{MEASURE_HUMIDITY_SHORT_ID}")

need_to_cool = False
need_to_heat = False
need_to_humidify = False
need_to_dehumidify = False

if measure_temperature < setpoint_temperature:
# Temperature too low, indicate to heat
need_to_heat = True
elif measure_temperature > setpoint_temperature:
# Temperature too high, indicate to cool
need_to_cool = True

if measure_temperature < setpoint_humidity_low:
# Humidity too low, indicate to humidify
need_to_humidify = True
elif measure_humidity > setpoint_humidity_max:
# Humidity too high, indicate to dehumidify
need_to_dehumidify = True

if need_to_humidify and need_to_cool:
# Turn humidifier on to both cool and humidify
self.control.output_on(output_humidifier_id, amount=amount_on_humidifier)
elif need_to_humidify and need_to_heat:
# Turn humidifier and heater on
self.control.output_on(output_heater_id, amount=amount_heater)
self.control.output_on(output_humidifier_id, amount=amount_on_humidifier)
elif need_to_dehumidify and need_to_cool:
# Turn exhaust and humidifier on
self.control.output_on(output_exhaust_id, amount=amount_on_exhaust)
self.control.output_on(output_humidifier_id, amount=amount_on_humidifier)
elif need_to_dehumidify and need_to_heat:
# Turn exhaust and heater on
self.control.output_on(output_exhaust_id, amount=amount_on_exhaust)
self.control.output_on(output_heater_id, amount=amount_on_heater)

Mycodo Developer

0

I am experimenting with using Linux Command outputs and leaving the script fields blank, thus creating an output that does nothing on its own but creates a kind of boolean switch that I can play around with using Conditional Controller functions. Not sure if it will work.