I’m trying to create a plugin and I’m running into this error message in the recent event logs.
Error
10:50:28 pioreactor1 dosing control Unable to find automation multi_media_chemostat. Available automations are [‘chemostat’, ‘continuous_cycle’, ‘fed_batch’, ‘morbidostat’, ‘pid_morbidostat’, ‘silent’, ‘turbidostat’, ‘alt_chemostat’]
This occurs when I go to pioreactor1.local/pioreactors → [Manage] → [Change Dosing Automation] → (dropdown) [my new plugin]
I checked pioreactor1.local/plugins and my new plugin (“Multi Media Chemostat Automation”) is shown under “Installed Plugins”. I refreshed the browser and when I navigate to [Change Dosing Automation] (above), I can select my automation (“Multi Media Chemostat”), configure my settings, and then press “START”.
I’m not sure why I’m getting this error. My plugin is showing up in both pioreactor1.local/pioreactors, which means it has found my .py file, and it is showing up in the UI list of automations, which means it has found my .yaml file. My .py file says automation_name = "multi_media_chemostat"
and my .yaml file says automation_name: multi_media_chemostat
so I am not sure why it is unable to find my automation.
/var/log/pioreactor.log
Traceback (most recent call last):
File “/usr/local/lib/python3.9/dist-packages/pioreactor/background_jobs/dosing_control.py”, line 104, in set_automation
klass = self.available_automations[algo_metadata.automation_name]
KeyError: ‘multi_media_chemostat’
2023-02-23T11:10:43-0800 [dosing_control] WARNING Unable to find automation multi_media_chemostat. Available automations are [‘chemostat’, ‘continuous_cycle’, ‘fed_batch’, ‘morbidostat’, ‘pid_morbidostat’, ‘silent’, ‘turbidostat’, ‘alt_chemostat’]
2023-02-23T11:10:43-0800 [dosing_control] INFO Updated automation from alt_chemostat(skip_first_run=0, media_volume=0.5, alt_media_volume=0.1, duration=60) to alt_chemostat(skip_first_run=0, media_volume=0.5, alt_media_volume=0.1, duration=60).
~/.pioreactor/plugins/ui/contrib/automations/dosing/multi_media_chemostat.yaml
---
display_name: Multi Media Chemostat
automation_name: multi_media_chemostat
description: Testing for now.
fields:
- key: media_volume
unit: mL
label: Volume of media added.
default: 0.5
- key: media_duration
unit: min
label: Interval between media additions.
default: 20
- key: alt_media_volume
unit: mL
label: Volume of alternative media added.
default: 0.1
- key: alt_media_duration
unit: min
label: Interval between alt_media additions.
default: 60
- key: duration
unit: min
label: Interval to wake up. Default to 1.
default: 1
~/.pioreactor/plugins/multi_media_chemostat.py
# -*- coding: utf-8 -*-
from __future__ import annotations
from pioreactor.automations import events
from pioreactor.automations.dosing.base import DosingAutomationJobContrib
from pioreactor.exc import CalibrationError
from pioreactor.utils import local_persistant_storage
__plugin_summary__ = "An extension of the chemostat automation to allow a second media source."
__plugin_version__ = "0.0.1"
__plugin_name__ = "Multi Media Chemostat Automation"
__plugin_author__ = "realPeteDavidson"
__plugin_homepage__ = "https://docs.pioreactor.com"
class MultiMediaChemostat(DosingAutomationJobContrib):
"""
Alternate Chemostat mode - try to keep [media] and [alt_media] constant.
"""
automation_name = "multi_media_chemostat"
published_settings = {
"media_volume": {"datatype": "float", "settable": True, "unit": "mL"},
"duration": {"datatype": "float", "settable": True, "unit": "min"},
"alt_media_volume": {"datatype": "float", "settable": True, "unit": "mL"},
"media_duration": {"datatype": "float", "settable": True, "unit": "min"},
"alt_media_duration": {"datatype": "float", "settable": True, "unit": "min"},
}
def __init__(self, media_volume: float | str, alt_media_volume: float | str, media_duration: float | str, alt_media_duration : float | str, **kwargs) -> None:
super().__init__(**kwargs)
with local_persistant_storage("current_pump_calibration") as cache:
if "media" not in cache:
raise CalibrationError("Media pump calibration must be performed first.")
elif "waste" not in cache:
raise CalibrationError("Waste pump calibration must be performed first.")
elif "alt_media" not in cache:
raise CalibrationError("Alt_Media pump calibration must be performed first.")
self.media = float(media_volume)
self.alt_media = float(alt_media_volume)
self.media_duration = float(media_duration)
self.alt_media_duration = float(alt_media_duration)
self.media_counter = 0
self.alt_media_counter = 0
def execute(self) -> events.DilutionEvent:
media_ml = 0
alt_media_ml = 0
waste_ml = 0
if self.media_counter >= self.media_duration:
media_ml = self.media
self.media_counter -= self.media_duration
if self.alt_media_counter >= self.alt_media_duration:
alt_media_ml = self.alt_media
self.alt_media_counter -= self.alt_media_duration
waste_ml = media_ml + alt_media_ml
volume_actually_cycled = self.execute_io_action(media_ml, alt_media_ml, waste_ml)
self.media_counter += self.duration
self.alt_media_counter += self.duration
return events.DilutionEvent(
f"exchanged {volume_actually_cycled['waste_ml']}mL",
data={"volume_actually_cycled": volume_actually_cycled["waste_ml"]},
)