Using "static variables" for the Conditional Controller

Hey community,

just to make it clear from the beginning: I am more confident with programming in C and I have a lack in knowledge in terms of python. That is why I often tend to think the way I would program stuff in C…

What I want to realize:
I would like to have a controller that works similar to the function Bang-Bang Hysteretic (On/Off) (Raise/Lower) but with calibrateable thresholds to stop dispensing. I could use a Bang-Bang Hysteretic for raising and a separate Bang-Bang Hysteretic for lowering and define a different setpoint for each. But it would be smart to have it in one function.


The upper and lower thresholds in blue are the boundaries in which I want to keep the pH value of the water.
In case the pH-Value decreases over time: When the pH-value reaches the lower threshold, the pH-Up solution is dispenced until it reaches the upper trigger. For an increasing pH-Value over time, it works vice versa.

To get this working properly, I need an indicator (e.g. “DISPENCING_PH_UP”) that is set to TRUE from the moment the lower threshold is triggered until the upper trigger is reached. In my opinion this is the way to detect whether the pump shall further dispence or not when the value is already slightly above the lower threshold. Of course, I could experiment what amount of pH-Up solution is needed to raise the pH-value from the lower threshold op to the midpoint in one shot, but I am not happy with that.
Why am I not using Bang-Bang: I think that if I dispence until I hit the upper threshold which then tells me that the water has a too high pH value, the system could tend to oscillate because both pumps try to work against each other.

My issue:
In C-Code I would simply define the indicator “DISPENCING_PH_UP” as static and between each function call it’s value is stored. I did some research on how to realize a similar functionality in python in different. As far as I know there are no static variables like in C. I tried to add attributes to a function, and use it as a static variable, but it didn’t work out so far.
I experimented with something like the following code in different variations. My goal was to see if the counter in the log raises with each function call as proove, that I can store a value until the next call.

Full Conditional Statement code:

  1: import os
  2: import sys
  3: sys.path.append(os.path.abspath('/var/mycodo-root'))
  4: from mycodo.controllers.base_conditional import AbstractConditional
  5: from mycodo.mycodo_client import DaemonControl
  6: control = DaemonControl(pyro_timeout=30.0)
  7: 
  8: class ConditionalRun(AbstractConditional):
  9:     def __init__(self, logger, function_id, message):
 10:         super(ConditionalRun, self).__init__(logger, function_id, message, timeout=30.0)
 11: 
 12:         self.logger = logger
 13:         self.function_id = function_id
 14:         self.variables = {}
 15:         self.message = message
 16:         self.running = True
 17: 
 18:     def conditional_code_run(self):
 19:         def test():
 20:             test.a += 1
 21: 
 22:         try: 
 23:             test.a
 24:         except:
 25:             test.a = 0 
 26: 
 27:         test()
 28:         self.logger.debug("Test: {}".format(test.a))
 29: 
 30:     def function_status(self):
 31:         # Example code to provide a return status for other controllers and widgets.
 32:         status_dict = {
 33:             'string_status': "This is the demo status of the conditional controller. "
 34:                              "The controller has looped {} times".format(self.loop_count),
 35:             'loop_count': self.loop_count,
 36:             'error': []
 37:         }
 38:         return status_dict

In the code of the Bang-Bang-Controller there is a @staticmethod used to store values. I think this is what I am looking for.

    @staticmethod
    def get_last_measurement(device_id, measurement_id, max_age=None):
        return get_last_measurement(device_id, measurement_id, max_age=max_age)

And there is also a state used to indicate whether to further raise or lower the value(?)

        output_raise_state = self.control.output_state(
            self.output_raise_device_id, self.output_raise_channel)
        output_lower_state = self.control.output_state(
            self.output_lower_device_id, self.output_raise_channel)

Although having this information I did not get this stuff running so far…
Do you have some advice how to get a “static variable” running correctly in this conditional controller?

I appreciate every advice.

I would recommend using a Conditional Controller. If you haven’t already, I’d read the Electrical Conductivity and pH Regulation section of my Automated Hydroponic System Build article. It lays a good ground work for expanding the functionality of regulating pH and EC.

For the Python Class that is used to run Conditional Controllers, you can simply use the self variable. This can be seen in use in the section I linked to, above. Simply, just create a variable and it will persist across controller loops:

self.my_var = 1

However, you will also need to test for the existence of this variable the first time if you need to read it:

if not hasattr(self, "my_var"):
    self.my_var = 1  # first run
else:
    self.my_var = 2  # after first run

To avoid this, there exists the self.variables dictionary that can be used, since this is created when the Class is instantiated.

if "test" not in self.variables:
    self.variables["test"] = 1
    self.variables["more"] = 2
else:
    self.variables["test"] = 3
    self.variables["more"] = 4

If you want data to persists across reboots or controller restarts, you can save and retrieve data from the database:

my_var = self.get_custom_option("my_variable_name")
self.set_custom_option("my_variable_name", my_var)

If nothing has been stored, it will return None.

Hope this helps.

1 Like

Thanks a lot, this works for me as expected :slight_smile: !

1 Like