diff --git a/pioreactor/actions/leader/experiment_profile.py b/pioreactor/actions/leader/experiment_profile.py index 73e18b86..1befb2e0 100644 --- a/pioreactor/actions/leader/experiment_profile.py +++ b/pioreactor/actions/leader/experiment_profile.py @@ -220,7 +220,7 @@ def execute_experiment_profile(profile_filename: str, dry_run: bool = False) -> timer.cancel() logger.info(f"Exiting profile {profile.experiment_profile_name} early.") else: - logger.info(f"Finished at commands in profile {profile.experiment_profile_name}.") + logger.info(f"Finished executing profile {profile.experiment_profile_name}.") @click.group(name="experiment_profile") @@ -242,6 +242,6 @@ def click_execute_experiment_profile(filename: str, dry_run: bool) -> None: @click.argument("filename", type=click.Path(exists=True)) def click_verify_experiment_profile(filename: str) -> None: """ - (leader only) Verify an experiment profile. + (leader only) Verify an experiment profile for correctness. """ load_and_verify_profile_file(filename) diff --git a/pioreactor/automations/__init__.py b/pioreactor/automations/__init__.py index 1c2f8627..251167b3 100644 --- a/pioreactor/automations/__init__.py +++ b/pioreactor/automations/__init__.py @@ -1,13 +1,26 @@ # -*- coding: utf-8 -*- from __future__ import annotations -from . import dosing -from . import events -from . import led -from . import temperature -from .dosing.base import DosingAutomationJob -from .dosing.base import DosingAutomationJobContrib -from .led.base import LEDAutomationJob -from .led.base import LEDAutomationJobContrib -from .temperature.base import TemperatureAutomationJob -from .temperature.base import TemperatureAutomationJobContrib +from pioreactor.background_jobs.subjobs import BackgroundSubJob + +DISALLOWED_AUTOMATION_NAMES = { + "config", +} + + +class BaseAutomationJob(BackgroundSubJob): + automation_name = "base_automation_job" + + def __init__(self, unit: str, experiment: str): + super(BaseAutomationJob, self).__init__(unit, experiment) + + if self.automation_name in DISALLOWED_AUTOMATION_NAMES: + raise NameError(f"{self.automation_name} is not allowed.") + + self.add_to_published_settings( + "latest_event", + { + "datatype": "AutomationEvent", + "settable": False, + }, + ) diff --git a/pioreactor/automations/dosing/base.py b/pioreactor/automations/dosing/base.py index 164bbf28..08e7af70 100644 --- a/pioreactor/automations/dosing/base.py +++ b/pioreactor/automations/dosing/base.py @@ -238,7 +238,7 @@ def __init__( ), **kwargs, ) -> None: - super(DosingAutomationJob, self).__init__(unit=unit, experiment=experiment) + super(DosingAutomationJob, self).__init__(unit, experiment) self.skip_first_run = skip_first_run self._latest_settings_started_at = current_utc_datetime() diff --git a/pioreactor/automations/led/base.py b/pioreactor/automations/led/base.py index 6635d773..7a483c41 100644 --- a/pioreactor/automations/led/base.py +++ b/pioreactor/automations/led/base.py @@ -15,9 +15,9 @@ from pioreactor import structs from pioreactor import types as pt from pioreactor.actions.led_intensity import led_intensity +from pioreactor.automations import BaseAutomationJob from pioreactor.automations import events from pioreactor.background_jobs.led_control import LEDController -from pioreactor.background_jobs.subjobs import BackgroundSubJob from pioreactor.pubsub import QOS from pioreactor.utils import is_pio_job_running from pioreactor.utils.timing import current_utc_datetime @@ -30,7 +30,7 @@ def brief_pause() -> float: return d -class LEDAutomationJob(BackgroundSubJob): +class LEDAutomationJob(BaseAutomationJob): """ This is the super class that LED automations inherit from. The `run` function will execute every `duration` minutes (selected at the start of the program), and call the `execute` function @@ -79,7 +79,7 @@ def __init__( skip_first_run: bool = False, **kwargs, ) -> None: - super(LEDAutomationJob, self).__init__(unit=unit, experiment=experiment) + super(LEDAutomationJob, self).__init__(unit, experiment) self.skip_first_run = skip_first_run self._latest_settings_started_at: datetime = current_utc_datetime() @@ -87,14 +87,6 @@ def __init__( self.latest_growth_rate_at: datetime = current_utc_datetime() self.edited_channels: set[pt.LedChannel] = set() - self.add_to_published_settings( - "latest_event", - { - "datatype": "AutomationEvent", - "settable": False, - }, - ) - self.set_duration(duration) def on_init_to_ready(self): diff --git a/pioreactor/automations/temperature/base.py b/pioreactor/automations/temperature/base.py index a890d612..925c11f8 100644 --- a/pioreactor/automations/temperature/base.py +++ b/pioreactor/automations/temperature/base.py @@ -61,7 +61,7 @@ def __init__( temperature_control_parent: TemperatureController, **kwargs, ) -> None: - super(TemperatureAutomationJob, self).__init__(unit=unit, experiment=experiment) + super(TemperatureAutomationJob, self).__init__(unit, experiment) self.latest_normalized_od_at: datetime = current_utc_datetime() self.latest_growth_rate_at: datetime = current_utc_datetime() diff --git a/pioreactor/automations/temperature/only_record_temperature.py b/pioreactor/automations/temperature/only_record_temperature.py index c3f80ae2..1c7f5704 100644 --- a/pioreactor/automations/temperature/only_record_temperature.py +++ b/pioreactor/automations/temperature/only_record_temperature.py @@ -13,5 +13,6 @@ def __init__(self, **kwargs) -> None: self.update_heater(0) def execute(self) -> NoEvent: - self.update_heater(0) + if self.heater_duty_cycle != 0: + self.update_heater(0) return NoEvent() diff --git a/pioreactor/background_jobs/base.py b/pioreactor/background_jobs/base.py index 8be3a9cc..ed6d8be1 100644 --- a/pioreactor/background_jobs/base.py +++ b/pioreactor/background_jobs/base.py @@ -35,6 +35,16 @@ T = t.TypeVar("T") BJT = t.TypeVar("BJT", bound="_BackgroundJob") +# these are used elsewhere in our software +DISALLOWED_JOB_NAMES = { + "run", + "dosing_events", + "leds", + "led_change_events", + "unit_label", + "pwm", +} + def cast_bytes_to_type(value: bytes, type_: str): try: @@ -90,17 +100,6 @@ def __call__(cls, *args, **kwargs): return obj -# these are used elsewhere in our software -DISALLOWED_JOB_NAMES = { - "run", - "dosing_events", - "leds", - "led_change_events", - "unit_label", - "pwm", -} - - class _BackgroundJob(metaclass=PostInitCaller): """ @@ -249,7 +248,7 @@ class _BackgroundJob(metaclass=PostInitCaller): # See pt.PublishableSetting type published_settings: dict[str, pt.PublishableSetting] = dict() - def __init__(self, experiment: str, unit: str, source: str = "app") -> None: + def __init__(self, unit: str, experiment: str, source: str = "app") -> None: if self.job_name in DISALLOWED_JOB_NAMES: raise ValueError("Job name not allowed.") if not self.job_name.islower(): @@ -958,8 +957,8 @@ class BackgroundJob(_BackgroundJob): Native jobs should inherit from this class. """ - def __init__(self, experiment: str, unit: str) -> None: - super().__init__(experiment=experiment, unit=unit, source="app") + def __init__(self, unit: str, experiment: str) -> None: + super().__init__(unit, experiment, source="app") class BackgroundJobContrib(_BackgroundJob): @@ -967,8 +966,8 @@ class BackgroundJobContrib(_BackgroundJob): Plugin jobs should inherit from this class. """ - def __init__(self, experiment: str, unit: str, plugin_name: str) -> None: - super().__init__(experiment=experiment, unit=unit, source=plugin_name) + def __init__(self, unit: str, experiment: str, plugin_name: str) -> None: + super().__init__(unit, experiment, source=plugin_name) class BackgroundJobWithDodging(_BackgroundJob): @@ -1165,5 +1164,5 @@ class BackgroundJobWithDodgingContrib(BackgroundJobWithDodging): Plugin jobs should inherit from this class. """ - def __init__(self, experiment: str, unit: str, plugin_name: str) -> None: - super().__init__(source=plugin_name, experiment=experiment, unit=unit) + def __init__(self, unit: str, experiment: str, plugin_name: str) -> None: + super().__init__(unit=unit, experiment=experiment, source=plugin_name) diff --git a/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py b/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py index 39ecfde0..f3e7a9d7 100644 --- a/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py +++ b/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py @@ -66,9 +66,12 @@ class MqttToDBStreamer(BackgroundJob): _inserts_in_last_60s = 0 def __init__( - self, topics_to_tables: list[TopicToParserToTable], unit: str, experiment: str + self, + unit: str, + experiment: str, + topics_to_tables: list[TopicToParserToTable], ) -> None: - super().__init__(experiment=experiment, unit=unit) + super().__init__(unit, experiment) self.logger.debug(f'Streaming MQTT data to {config["storage"]["database"]}.') self.sqliteworker = Sqlite3Worker( config["storage"]["database"], max_queue_size=250, raise_on_error=False @@ -459,7 +462,7 @@ def register_source_to_sink(t2p2t: TopicToParserToTable | list[TopicToParserToTa def start_mqtt_to_db_streaming() -> MqttToDBStreamer: source_to_sinks = add_default_source_to_sinks() - return MqttToDBStreamer(source_to_sinks, experiment=UNIVERSAL_EXPERIMENT, unit=get_unit_name()) + return MqttToDBStreamer(get_unit_name(), UNIVERSAL_EXPERIMENT, source_to_sinks) @click.command(name="mqtt_to_db_streaming") diff --git a/pioreactor/background_jobs/stirring.py b/pioreactor/background_jobs/stirring.py index 4b851bf9..86c52630 100644 --- a/pioreactor/background_jobs/stirring.py +++ b/pioreactor/background_jobs/stirring.py @@ -356,7 +356,7 @@ def poll(self, poll_for_seconds: float) -> Optional[structs.MeasuredRPM]: if self.rpm_calculator is None: return None - recent_rpm = round(self.rpm_calculator(poll_for_seconds), 1) + recent_rpm = round(self.rpm_calculator(poll_for_seconds), 2) self._measured_rpm = recent_rpm self.measured_rpm = structs.MeasuredRPM( diff --git a/pioreactor/background_jobs/temperature_control.py b/pioreactor/background_jobs/temperature_control.py index 82af7073..c38aba62 100644 --- a/pioreactor/background_jobs/temperature_control.py +++ b/pioreactor/background_jobs/temperature_control.py @@ -434,7 +434,7 @@ def infer_temperature(self) -> None: try: self.temperature = Temperature( - temperature=self.approximate_temperature(features), + temperature=round(self.approximate_temperature(features), 2), timestamp=current_utc_datetime(), ) diff --git a/pioreactor/tests/test_dosing_control.py b/pioreactor/tests/test_dosing_control.py index eb945245..25e59884 100644 --- a/pioreactor/tests/test_dosing_control.py +++ b/pioreactor/tests/test_dosing_control.py @@ -16,9 +16,9 @@ from pioreactor import exc from pioreactor import pubsub from pioreactor import structs -from pioreactor.automations import DosingAutomationJob from pioreactor.automations import events from pioreactor.automations.dosing.base import AltMediaFractionCalculator +from pioreactor.automations.dosing.base import DosingAutomationJob from pioreactor.automations.dosing.base import VialVolumeCalculator from pioreactor.automations.dosing.pid_morbidostat import PIDMorbidostat from pioreactor.automations.dosing.silent import Silent diff --git a/pioreactor/tests/test_mqtt_to_db_streaming.py b/pioreactor/tests/test_mqtt_to_db_streaming.py index 14c6dbbb..728e33d2 100644 --- a/pioreactor/tests/test_mqtt_to_db_streaming.py +++ b/pioreactor/tests/test_mqtt_to_db_streaming.py @@ -48,7 +48,7 @@ def parse_setting(topic, payload) -> dict: ) ] - with m2db.MqttToDBStreamer(parsers, unit=unit, experiment=exp): + with m2db.MqttToDBStreamer(unit, exp, parsers): with collect_all_logs_of_level("ERROR", unit, exp) as bucket: t = TestJob(unit=unit, experiment=exp) t.clean_up() @@ -80,7 +80,7 @@ def test_updated_heater_dc() -> None: ), ] - with m2db.MqttToDBStreamer(parsers, unit=unit, experiment=exp): + with m2db.MqttToDBStreamer(unit, exp, parsers): sleep(1) publish( f"pioreactor/{unit}/test/temperature_automation/latest_event", @@ -122,7 +122,7 @@ def test_dosing_events_land_in_db() -> None: ), ] - with m2db.MqttToDBStreamer(parsers, unit=unit, experiment=exp): + with m2db.MqttToDBStreamer(unit, exp, parsers): from pioreactor.actions.pump import add_media add_media( @@ -197,7 +197,7 @@ def test_kalman_filter_entries() -> None: ) ] - m = m2db.MqttToDBStreamer(parsers, unit=unit, experiment=exp) + m = m2db.MqttToDBStreamer(unit, exp, parsers) # let data collect sleep(10) @@ -259,7 +259,7 @@ def parse_setting(topic, payload) -> dict: ) ] - with m2db.MqttToDBStreamer(parsers, unit=unit, experiment=exp): + with m2db.MqttToDBStreamer(unit, exp, parsers): with collect_all_logs_of_level("ERROR", unit, exp) as bucket: t = TestJob(unit=unit, experiment=exp) t.clean_up() diff --git a/pioreactor/utils/streaming_calculations.py b/pioreactor/utils/streaming_calculations.py index 94c8a0a0..bf22c7af 100644 --- a/pioreactor/utils/streaming_calculations.py +++ b/pioreactor/utils/streaming_calculations.py @@ -535,4 +535,6 @@ def publish_pid_stats(self) -> None: "job_name": self.job_name, "target_name": self.target_name, } - self.client.publish(f"pioreactor/{self.unit}/{self.experiment}/pid_log", dumps(to_send)) + self.client.publish( + f"pioreactor/{self.unit}/{self.experiment}/pid_log/{self.target_name}", dumps(to_send) + ) diff --git a/pioreactor/version.py b/pioreactor/version.py index e19be9cf..eb3d42af 100644 --- a/pioreactor/version.py +++ b/pioreactor/version.py @@ -5,7 +5,7 @@ # Append "dev" if a dev version # Append "rc0" if a rc version -__version__ = "23.8.29" +__version__ = "23.8.29dev" def _get_hardware_version() -> tuple[int, int] | tuple[int, int, str]: