# 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 a optical density trigger is exceeded. We'll go through each line of code after:

from pioreactor.background_jobs.subjobs.dosing_automation import DosingAutomation

class NaiveTurbidostat(DosingAutomation):

key = "naive_turbidostat"

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.background_jobs.subjobs.dosing_automation 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. There is a limited number of settings to change here: at the time of writing, target_od, target_growth_rate, duration, volume. 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__(**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.background_jobs.subjobs.dosing_automation import DosingAutomation
from pioreactor.whoami import get_unit_name, get_latest_experiment_name

class NaiveTurbidostat(DosingAutomation):
...

if __name__=="__main__":

doser = NaiveTurbidostat(
target_od=2.0,
unit=get_unit_name(),
experiment=get_latest_experiment_name()
)

while True:
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).