Writing custom automation plugins

Writing new automations

An automation is a hands-off way to adjust the environment for the microbes. We currently support three types of automations: dosing, LED, and temperature. A dosing automation involves when to add media (ex: a turbidostat). A LED automation uses the additional LED pockets, and user-supplied LEDs, to add or remove visible, UV, or IR light into the vial (ex: a photobioreactor). A temperature automation is how users can control the temperature of the vial.

Writing an automation involves creating a Python class with specific methods and parent classes. It would be helpful to be familiar with Python classes before beginning. Here's an example of a (naive) turbidostat automation, i.e. it will add fresh media and remove old media when an optical density trigger is exceeded. We'll go through each line of code after:

from pioreactor.automations import DosingAutomation

class NaiveTurbidostat(DosingAutomation):

    key = "naive_turbidostat"
    published_settings = {
"target_od": {"datatype": "float", "settable": True, "unit": "AU"},
} def __init__(self, target_od, **kwargs): super(NaiveTurbidostat, self).__init__(**kwargs) self.target_od = target_od def execute(self): if self.latest_od > self.target_od: self.execute_io_action(media_ml=1.0, waste_ml=1.0)

First important thing is to subclass from DosingAutomation:

from pioreactor.automations import DosingAutomation

class NaiveTurbidostat(DosingAutomation):
   ...

We need a "key" to i) distinguish this from other automations, and ii) be able to be communicated between systems (think: the web UI to Python, and back). The key class attribute does this for us. Normally, it's the snakecase of the class name.

    key = "naive_turbidostat"

Next, we initialize our class. Here we can add settings we want to accept from the user: how much volume to add, what is our target optical density.  Note the boilerplate **kwargs, and super(...) are important.

    def __init__(self, target_od, **kwargs):
        super(NaiveTurbidostat, self).__init__(**kwargs)
        self.target_od = target_od

For example, if we needed duration to compute some important value, we could "ask" for it by including it in the __init__:

def __init__(self, target_od, duration, **kwargs):
    super(NaiveTurbidostat, self).__init__(duration=duration, **kwargs)
    self.target_od = target_od
    self.duration_in_seconds = duration * 60

Finally, every duration minutes, the function execute is called. This is the core logic of the automation. In our (simple) case, we want to dilute the vial if we have exceed the latest_od:

    def execute(self):
        if self.latest_od > self.target_od:
            self.execute_io_action(media_ml=1.0, waste_ml=1.0)

Since we are working with a fixed volume, media_ml must equal waste_ml, else an error will be thrown. What is latest_od? Our class, when active, is listening to growth-rate-calculating job's output of normalized optical density. Hence when execute runs, we'll have access to the most up-to-date value of normalized optical density. Likewise, there is a latest_growth_rate that updates when a new growth-rate value is produced.


How do we run this automation now? We can invoke it in a script like so:

"""
run on the command line with 
$ python3 name_of_script.py

Exit with ctrl-c
"""

import signal
from pioreactor.automations import DosingAutomation
from pioreactor.whoami import get_unit_name, get_latest_experiment_name

class NaiveTurbidostat(DosingAutomation):
  ...


if __name__=="__main__":

    turbidostat = NaiveTurbidostat(
        target_od=2.0,
        unit=get_unit_name(),
        experiment=get_latest_experiment_name()
    )
    signal.pause()

This will start the job, but you will notice nothing is happening. Likely that's because no optical density measurements are being processed. You'll need to have od_reading and growth_rate_calculating running.

Turning your automation in a Python package to share

Following the template here: https://github.com/Pioreactor/pioreactor_custom_dosing_automation, you can package and deploy your automation to your Pioreactor cluster, or for others to use.

Note that in that template package, there are ways to add fields to the configuration (see additional_config.ini, which gets merged with config.ini on installation), and adding your automation to the web UI (see the specific folder structure in the ui folder).