diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b034c5cb..e852660d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: hooks: - id: mypy additional_dependencies: [ - msgspec==0.15.1 + msgspec==0.16.0 ] diff --git a/CHANGELOG.md b/CHANGELOG.md index ab10a951..97019e58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,29 @@ #### Highlights - The UI now offers a way to upgrade to the bleeding-edge Pioreactor app and UI software, called "development". This software is unstable (and fun!). +#### Better thermostat + + - Improved temperature inference accuracy. + - After some testing, we've found that the following set of PID parameters for `temperature_automation.thermostat` works better¹ than the previous set of parameters: +``` +Kp=3.0 +Ki=0.0 +Kd=4.5 +``` + +This set now ships with all new installations of Pioreactor software. **Existing users can update their parameters in the config.ini** + +¹ Better == less thermal runaways, slightly less overshooting. + #### Everything else + - On startup, the Raspberry Pi will write its IP address to a text file `/boot/ip`. This means that if you (carefully) remove the SD card, you should be able see the IP address (hopefully it hasn't changed). - Fixed `source` in `BackgroundJobContrib` - thanks @odcambc! - `pio add-pioreactor` will now accept an option that is the password of the RPi being added (default: `raspberry`). Ex: `pio add-pioreactor worker1 -p mypass` - Improved some warning and error messages. - Improved watchdog detecting and fixing "lost" Pioreactors. - - Improved temperature inference accuracy. - Starting to test software against Python 3.11, in anticipation of a Python 3.11 coming to Raspberry Pi OS. - Improvements to bash scripts to make them more robust. - - ### 23.6.7 #### Highlights diff --git a/pioreactor/actions/self_test.py b/pioreactor/actions/self_test.py index 7bfe9279..c30ea380 100644 --- a/pioreactor/actions/self_test.py +++ b/pioreactor/actions/self_test.py @@ -60,6 +60,8 @@ def test_REF_is_in_correct_position( # this _also_ uses stirring to increase the variance in the non-REF, so... from statistics import variance + reference_channel = cast(PdChannel, config["od_config.photodiode_channel_reverse"][REF_keyword]) + signal1 = [] signal2 = [] @@ -86,18 +88,16 @@ def test_REF_is_in_correct_position( "2": variance(signal2) / trimmed_mean(signal2) ** 2, } - ref_channel = config["od_config.photodiode_channel_reverse"][REF_keyword] - THRESHOLD = 1.0 - if ref_channel == "1": + if reference_channel == "1": assert ( THRESHOLD * norm_variance_per_channel["1"] < norm_variance_per_channel["2"] - ), f"{ref_channel=}, {norm_variance_per_channel=}" + ), f"{reference_channel=}, {norm_variance_per_channel=}" - elif ref_channel == "2": + elif reference_channel == "2": assert ( THRESHOLD * norm_variance_per_channel["2"] < norm_variance_per_channel["1"] - ), f"{ref_channel=}, {norm_variance_per_channel=}" + ), f"{reference_channel=}, {norm_variance_per_channel=}" def test_all_positive_correlations_between_pds_and_leds( diff --git a/pioreactor/background_jobs/base.py b/pioreactor/background_jobs/base.py index c8cdde6f..c70868d3 100644 --- a/pioreactor/background_jobs/base.py +++ b/pioreactor/background_jobs/base.py @@ -20,6 +20,7 @@ from pioreactor.logging import create_logger from pioreactor.pubsub import Client from pioreactor.pubsub import create_client +from pioreactor.pubsub import MQTT_TOPIC from pioreactor.pubsub import QOS from pioreactor.pubsub import subscribe from pioreactor.utils import append_signal_handlers @@ -424,7 +425,7 @@ def publish( def subscribe_and_callback( self, callback: t.Callable[[pt.MQTTMessage], None], - subscriptions: list[str] | str, + subscriptions: list[str | MQTT_TOPIC] | str | MQTT_TOPIC, allow_retained: bool = True, qos: int = QOS.AT_MOST_ONCE, ) -> None: @@ -462,9 +463,9 @@ def _callback(client, userdata, message: pt.MQTTMessage) -> t.Optional[T]: subscriptions = [subscriptions] if isinstance(subscriptions, str) else subscriptions - for sub in subscriptions: - self.sub_client.message_callback_add(sub, wrap_callback(callback)) - self.sub_client.subscribe(sub, qos=qos) + for topic in subscriptions: + self.sub_client.message_callback_add(str(topic), wrap_callback(callback)) + self.sub_client.subscribe(str(topic), qos=qos) return def set_state(self, new_state: pt.JobState) -> None: diff --git a/pioreactor/background_jobs/growth_rate_calculating.py b/pioreactor/background_jobs/growth_rate_calculating.py index ff4ee41b..e979bfe5 100644 --- a/pioreactor/background_jobs/growth_rate_calculating.py +++ b/pioreactor/background_jobs/growth_rate_calculating.py @@ -239,6 +239,7 @@ def _compute_and_cache_od_statistics( means, variances = od_statistics( self._yield_od_readings_from_mqtt(), action_name="od_normalization", + n_samples=35, unit=self.unit, experiment=self.experiment, logger=self.logger, diff --git a/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py b/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py index 2c6a497d..39ecfde0 100644 --- a/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py +++ b/pioreactor/background_jobs/leader/mqtt_to_db_streaming.py @@ -17,6 +17,7 @@ from pioreactor.background_jobs.base import BackgroundJob from pioreactor.config import config from pioreactor.hardware import PWM_TO_PIN +from pioreactor.pubsub import MQTT_TOPIC from pioreactor.pubsub import QOS from pioreactor.utils.sqlite_worker import Sqlite3Worker from pioreactor.utils.timing import current_utc_datetime @@ -44,13 +45,13 @@ class TopicToParserToTable(Struct): - parsers can return None as well, to skip adding the row to the database. """ - topic: str | list[str] + topic: str | MQTT_TOPIC | list[str | MQTT_TOPIC] parser: Callable[[str, pt.MQTTMessagePayload], Optional[dict | list[dict]]] table: str class TopicToCallback(Struct): - topic: str | list[str] + topic: str | MQTT_TOPIC | list[str | MQTT_TOPIC] callback: Callable[[pt.MQTTMessage], None] @@ -145,7 +146,7 @@ def initialize_callbacks(self, topics_and_callbacks: list[TopicToCallback]) -> N for topic_and_callback in topics_and_callbacks: self.subscribe_and_callback( topic_and_callback.callback, - topic_and_callback.topic, + str(topic_and_callback.topic), qos=QOS.EXACTLY_ONCE, allow_retained=False, ) diff --git a/pioreactor/background_jobs/od_reading.py b/pioreactor/background_jobs/od_reading.py index 559c05a2..ec43b2de 100644 --- a/pioreactor/background_jobs/od_reading.py +++ b/pioreactor/background_jobs/od_reading.py @@ -130,7 +130,7 @@ def __init__( fake_data: bool = False, interval: Optional[float] = 1.0, dynamic_gain: bool = True, - penalizer: float = 525.0, + penalizer: float = 625.0, oversampling_count: int = 28, ) -> None: super().__init__() diff --git a/pioreactor/cli/pio.py b/pioreactor/cli/pio.py index 06d6f2f3..9023b041 100644 --- a/pioreactor/cli/pio.py +++ b/pioreactor/cli/pio.py @@ -747,7 +747,9 @@ def display_data_for(hostname_status: tuple[str, str]) -> bool: ip, state, reachable = get_network_metadata(hostname) - statef = click.style(f"{state:15s}", fg="green" if state == "ready" else "red") + statef = click.style( + f"{state:15s}", fg="green" if state in ("ready", "init") else "red" + ) ipf = f"{ip if (ip is not None) else 'unknown':20s}" is_leaderf = f"{('Y' if hostname==get_leader_hostname() else 'N'):15s}" diff --git a/pioreactor/pubsub.py b/pioreactor/pubsub.py index d93d3684..ae1d7134 100644 --- a/pioreactor/pubsub.py +++ b/pioreactor/pubsub.py @@ -17,28 +17,24 @@ from pioreactor.types import MQTTMessage -class PIOREACTOR: - """ - Use to construct MQTT paths like Pathlib in Python core. - - > PIOREACTOR() / unit / experiment / "+" / "$state" / "set" - >> "pioreactor/test_unit/test_experiment/+/$state/set" +class MQTT_TOPIC: + def __init__(self, init: str): + self.body = init - BETA: may be deleted in the future. + def __truediv__(self, other: str | MQTT_TOPIC) -> MQTT_TOPIC: + return MQTT_TOPIC(self.body + "/" + str(other)) - """ + def __str__(self) -> str: + return self.body - def __init__(self, body="pioreactor"): - self.body = body + def __repr__(self) -> str: + return str(self) - def __truediv__(self, other): - return PIOREACTOR(self.body + "/" + other) + def __iter__(self): + return iter(str(self)) - def __str__(self): - return self.body - def __repr__(self): - return str(self) +PIOREACTOR = MQTT_TOPIC("pioreactor") def add_hash_suffix(s: str) -> str: @@ -364,7 +360,7 @@ def __init__(self, log_level: str, unit: str, experiment: str) -> None: # subscribe to the logs self.client: Client = subscribe_and_callback( self._collect_logs_into_bucket, - str(PIOREACTOR() / self.unit / self.experiment / "logs" / "app"), + str(PIOREACTOR / self.unit / self.experiment / "logs" / "app"), ) def _collect_logs_into_bucket(self, message): diff --git a/update_scripts/upcoming/update.sh b/update_scripts/upcoming/update.sh index aa88036c..e9d9d2d5 100644 --- a/update_scripts/upcoming/update.sh +++ b/update_scripts/upcoming/update.sh @@ -7,4 +7,11 @@ export LC_ALL=C wget -O /usr/local/bin/install_pioreactor_plugin.sh https://raw.githubusercontent.com/Pioreactor/CustoPiZer/pioreactor/workspace/scripts/files/bash/install_pioreactor_plugin.sh wget -O /usr/local/bin/uninstall_pioreactor_plugin.sh https://raw.githubusercontent.com/Pioreactor/CustoPiZer/pioreactor/workspace/scripts/files/bash/uninstall_pioreactor_plugin.sh + +# adding bounds to the while loops wget -O /usr/local/bin/add_new_pioreactor_worker_from_leader.sh https://raw.githubusercontent.com/Pioreactor/CustoPiZer/pioreactor/workspace/scripts/files/bash/add_new_pioreactor_worker_from_leader.sh + + +# we write the IP address to /boot/ip +wget -O /usr/local/bin/everyboot.sh https://raw.githubusercontent.com/Pioreactor/CustoPiZer/pioreactor/workspace/scripts/files/bash/everyboot.sh +wget -O /lib/systemd/system/everyboot.service https://raw.githubusercontent.com/Pioreactor/CustoPiZer/pioreactor/workspace/scripts/files/system/systemd/everyboot.service