diff --git a/pioreactor/actions/self_test.py b/pioreactor/actions/self_test.py index 0ecda8f3..adf7e846 100644 --- a/pioreactor/actions/self_test.py +++ b/pioreactor/actions/self_test.py @@ -179,9 +179,18 @@ def test_all_positive_correlations_between_pds_and_leds( adc_reader.take_reading(), adc_reader.take_reading(), adc_reader.take_reading(), - adc_reader.take_reading(), ) + # turn off led to cool it down + led_intensity( + {led_channel: 0}, + unit=unit, + experiment=experiment, + verbose=False, + source_of_event="self_test", + ) + sleep(intensity / 100) # let it cool down in proportion to the intensity + # Add to accumulating list for pd_channel in ALL_PD_CHANNELS: varying_intensity_results[pd_channel].append(avg_reading[pd_channel]) @@ -359,6 +368,13 @@ def test_detect_heating_pcb(managed_state, logger: CustomLogger, unit: str, expe assert is_heating_pcb_present(), "Heater PCB is not connected, or i2c is not working." +def test_run_stirring_calibration(managed_state, logger: CustomLogger, unit: str, experiment: str) -> None: + from pioreactor.calibrations.stirring_calibration import run_stirring_calibration + + cal = run_stirring_calibration() + cal.set_as_active_calibration_for_device("stirring") + + def test_positive_correlation_between_temperature_and_heating( managed_state, logger: CustomLogger, unit: str, experiment: str ) -> None: @@ -499,6 +515,7 @@ def click_self_test(k: Optional[str], retry_failed: bool) -> int: test_REF_is_in_correct_position, test_PD_is_near_0_volts_for_blank, test_positive_correlation_between_rpm_and_stirring, + test_run_stirring_calibration, ) with managed_lifecycle(unit, experiment, "self_test") as managed_state, temporary_config_change( diff --git a/pioreactor/background_jobs/od_reading.py b/pioreactor/background_jobs/od_reading.py index 48bcb65b..89500777 100644 --- a/pioreactor/background_jobs/od_reading.py +++ b/pioreactor/background_jobs/od_reading.py @@ -198,12 +198,13 @@ def set_offsets(self, batched_readings: PdChannelToVoltage) -> None: ) def check_on_max(self, value: pt.Voltage) -> None: - unit = whoami.get_unit_name() - exp = whoami.get_assigned_experiment_name(unit) - if value <= 3.0: return elif value > 3.2: + # TODO: sometimes we use ADC in self-tests or calibrations, and it might not be assigned. This will fail if that's the case. + unit = whoami.get_unit_name() + exp = whoami.get_assigned_experiment_name(unit) + self.logger.error( f"An ADC channel is recording a very high voltage, {round(value, 2)}V. We are shutting down components and jobs to keep the ADC safe." ) @@ -235,6 +236,9 @@ def check_on_max(self, value: pt.Voltage) -> None: return elif value > 3.0: + unit = whoami.get_unit_name() + exp = whoami.get_assigned_experiment_name(unit) + self.logger.warning( f"An ADC channel is recording a very high voltage, {round(value, 2)}V. It's recommended to keep it less than 3.0V. Suggestion: decrease the IR intensity, or change the PD angle to a lower angle." ) diff --git a/pioreactor/cli/calibrations.py b/pioreactor/cli/calibrations.py index 067a10a6..6fa96c9b 100644 --- a/pioreactor/cli/calibrations.py +++ b/pioreactor/cli/calibrations.py @@ -21,6 +21,10 @@ def green(string: str) -> str: return click.style(string, fg="green") +def bold(string: str) -> str: + return click.style(string, bold=True) + + @click.group(short_help="calibration utils") def calibration() -> None: """ @@ -35,11 +39,14 @@ def list_calibrations(device: str | None) -> None: """ List existing calibrations for the given device if provided, else all. """ + + header = f"{'Device':<25}{'Name':<50}{'Created At':<25}{'Active?':<10}" + click.echo(header) + click.echo("-" * len(header)) + if device is None: for device in list_devices(): _display_calibrations_by_device(device) - click.echo() - click.echo() else: _display_calibrations_by_device(device) @@ -50,19 +57,21 @@ def _display_calibrations_by_device(device: str) -> None: click.echo(f"No calibrations found for device '{device}'. Directory does not exist.") raise click.Abort() - header = f"{'Device':<25}{'Name':<50}{'Created At':<25}{'Active?':<10}{'Location':<75}" - click.echo(header) - click.echo("-" * len(header)) + calibrations_by_device = list_of_calibrations_by_device(device) - for name in list_of_calibrations_by_device(device): + if len(calibrations_by_device) == 0: + return + + for name in calibrations_by_device: try: location = (calibration_dir / name).with_suffix(".yaml") data = yaml_decode(location.read_bytes(), type=structs.subclass_union(structs.CalibrationBase)) - row = f"{device:<25}{data.calibration_name:<50}{data.created_at.strftime('%Y-%m-%d %H:%M:%S'):<25}{'✅' if data.is_active(device) else '':<10}{location}" + row = f"{device:<25}{data.calibration_name:<50}{data.created_at.strftime('%Y-%m-%d %H:%M:%S'):<25}{'✅' if data.is_active(device) else '':<10}" click.echo(row) - except Exception as e: - error_message = f"Error reading {name}: {e}" - click.echo(f"{error_message:<60}") + except Exception: + pass + # error_message = f"Error reading {name}: {e}" + # click.echo(f"{error_message:<60}") @calibration.command(name="run", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True)) @@ -83,6 +92,11 @@ def run_calibration(ctx, device: str, protocol_name: str | None, y: bool) -> Non # Dispatch to the assistant function for that device if protocol_name is None: + if len(calibration_protocols.get(device, {}).keys()) == 0: + click.echo( + f"No protocols found for device '{device}'. Try `pio calibrations protocols` to see available protocols." + ) + raise click.Abort() if len(calibration_protocols.get(device, {}).keys()) == 1: protocol_name = list(calibration_protocols.get(device, {}).keys())[0] else: @@ -92,7 +106,7 @@ def run_calibration(ctx, device: str, protocol_name: str | None, y: bool) -> Non click.echo(f"Available protocols for {device}:") click.echo() for protocol in calibration_protocols.get(device, {}).values(): - click.echo(click.style(f" • {protocol.protocol_name}", bold=True)) + click.echo(bold(f" • {protocol.protocol_name}")) click.echo(f" Description: {protocol.description}") click.echo() protocol_name = click.prompt( @@ -137,9 +151,11 @@ def run_calibration(ctx, device: str, protocol_name: str | None, y: bool) -> Non @calibration.command(name="protocols") def list_protocols() -> None: + """ + List available protocols for device calibrations. + """ for device, protocols in calibration_protocols.items(): - for protocol in protocols: - click.echo(f"{device}: {protocol}") + click.echo(f"{bold(device)}: {', '.join(protocols.keys())}") @calibration.command(name="display")