pioreactor-precision-temperature-plugin 0.2.7__tar.gz → 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {pioreactor_precision_temperature_plugin-0.2.7/pioreactor_precision_temperature_plugin.egg-info → pioreactor_precision_temperature_plugin-0.2.9}/PKG-INFO +1 -1
  2. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/adafruit_mlx90632.py +2 -1
  3. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/fir_temperature_automation.py +9 -2
  4. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9/pioreactor_precision_temperature_plugin.egg-info}/PKG-INFO +1 -1
  5. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/setup.py +1 -1
  6. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/tests/test_fir_temperature_automation.py +65 -0
  7. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/LICENSE.txt +0 -0
  8. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/MANIFEST.in +0 -0
  9. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/README.md +0 -0
  10. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/THIRD_PARTY_LICENSE_ADAFRUIT_MLX90632.txt +0 -0
  11. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/__init__.py +0 -0
  12. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/additional_config.ini +0 -0
  13. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/estimators/fir_temperature_estimator_linear_mlx_ambient_pcb_v1.yaml +0 -0
  14. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/post_install.sh +0 -0
  15. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin/ui/contrib/charts/temperature.yaml +0 -0
  16. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin.egg-info/SOURCES.txt +0 -0
  17. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin.egg-info/dependency_links.txt +0 -0
  18. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin.egg-info/entry_points.txt +0 -0
  19. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/pioreactor_precision_temperature_plugin.egg-info/top_level.txt +0 -0
  20. {pioreactor_precision_temperature_plugin-0.2.7 → pioreactor_precision_temperature_plugin-0.2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pioreactor-precision-temperature-plugin
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: Precision temperature automation for Pioreactor using FIR + MLX90632
5
5
  Home-page: https://github.com/Pioreactor/pioreactor-precision-temperature-plugin
6
6
  Author: Cam Davidson-Pilon
@@ -119,7 +119,8 @@ class MLX90632:
119
119
  except Exception as exc:
120
120
  raise RuntimeError(
121
121
  "Failed to initialize MLX90632 I2C device. "
122
- "Expected a Blinka-compatible I2C bus from board.I2C()."
122
+ f"address=0x{address:02X}, i2c_bus_type={type(i2c_bus).__name__}, "
123
+ f"original_error={type(exc).__name__}: {exc}"
123
124
  ) from exc
124
125
 
125
126
  # Initialize temperature history
@@ -71,6 +71,7 @@ STABILIZATION_MAX_SLOPE_C_PER_MIN = 0.02
71
71
  STABILIZATION_WINDOW_S = 2.0 * 60
72
72
  STABILIZATION_SLOPE_SAMPLE_DELAY_S = 4.1
73
73
  STABILIZATION_TIMEOUT_S = 90 * 60
74
+ DANGER_ZONE_DUTY_CYCLE_REDUCTION_FACTOR = 0.9965
74
75
 
75
76
  available_temperature_automations: dict[str, type["TemperatureAutomationJobFIR"]] = {}
76
77
 
@@ -238,6 +239,12 @@ class TemperatureAutomationJobFIR(AutomationJob):
238
239
  except Exception as e:
239
240
  raise exc.HardwareNotFoundError(f"Unable to import MLX90632 dependencies: {e}")
240
241
 
242
+ if not hardware.is_i2c_device_present(address):
243
+ raise exc.HardwareNotFoundError(
244
+ f"MLX90632 not found at address 0x{address:02X} on I2C bus. "
245
+ "Check mlx_address and sensor wiring."
246
+ )
247
+
241
248
  i2c = busio.I2C(hardware.get_scl_pin(), hardware.get_sda_pin())
242
249
  sensor = adafruit_mlx90632.MLX90632(i2c, address=address)
243
250
  with suppress(Exception):
@@ -359,9 +366,9 @@ class TemperatureAutomationJobFIR(AutomationJob):
359
366
 
360
367
  elif temp > self.MAX_TEMP_TO_REDUCE_HEATING:
361
368
  self.logger.debug(
362
- f"Temperature of heating surface exceeded {self.MAX_TEMP_TO_REDUCE_HEATING}℃ (currently {temp}℃). Reducing heater by 10%."
369
+ f"Temperature of heating surface exceeded {self.MAX_TEMP_TO_REDUCE_HEATING}℃ (currently {temp}℃). Reducing heater slightly."
363
370
  )
364
- self._update_heater(self.heater_duty_cycle * 0.9)
371
+ self._update_heater(self.heater_duty_cycle * DANGER_ZONE_DUTY_CYCLE_REDUCTION_FACTOR)
365
372
 
366
373
  return temp
367
374
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pioreactor-precision-temperature-plugin
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: Precision temperature automation for Pioreactor using FIR + MLX90632
5
5
  Home-page: https://github.com/Pioreactor/pioreactor-precision-temperature-plugin
6
6
  Author: Cam Davidson-Pilon
@@ -4,7 +4,7 @@ from setuptools import setup
4
4
 
5
5
  setup(
6
6
  name="pioreactor-precision-temperature-plugin",
7
- version="0.2.7",
7
+ version="0.2.9",
8
8
  python_requires=">=3.13,<3.14",
9
9
  license_files=("LICENSE.txt",),
10
10
  description="Precision temperature automation for Pioreactor using FIR + MLX90632",
@@ -111,6 +111,7 @@ def test_setup_mlx_driver_uses_busio_i2c(
111
111
  monkeypatch.setattr(fta.whoami, "is_testing_env", lambda: False)
112
112
  monkeypatch.setattr(fta.hardware, "get_scl_pin", lambda: 11)
113
113
  monkeypatch.setattr(fta.hardware, "get_sda_pin", lambda: 13)
114
+ monkeypatch.setattr(fta.hardware, "is_i2c_device_present", lambda _address: True)
114
115
  monkeypatch.setitem(sys.modules, "busio", FakeBusIOModule())
115
116
  monkeypatch.setattr(plugin_pkg, "adafruit_mlx90632", FakeMLXModule, raising=False)
116
117
 
@@ -123,6 +124,40 @@ def test_setup_mlx_driver_uses_busio_i2c(
123
124
  assert sensor.refresh_rate == FakeMLXModule.REFRESH_1HZ
124
125
 
125
126
 
127
+ def test_setup_mlx_driver_raises_clear_error_when_address_missing(
128
+ monkeypatch: pytest.MonkeyPatch,
129
+ ) -> None:
130
+ import pioreactor_precision_temperature_plugin as plugin_pkg
131
+ from pioreactor import exc as pr_exc
132
+
133
+ class FakeBusIOModule:
134
+ @staticmethod
135
+ def I2C(_scl: int, _sda: int) -> object:
136
+ return object()
137
+
138
+ class FakeMLXModule:
139
+ MODE_CONTINUOUS = 3
140
+ REFRESH_1HZ = 1
141
+
142
+ class MLX90632:
143
+ def __init__(self, _i2c_bus: object, _address: int) -> None:
144
+ raise AssertionError("should not attempt MLX init when address is missing")
145
+
146
+ monkeypatch.setattr(fta.whoami, "is_testing_env", lambda: False)
147
+ monkeypatch.setattr(fta.hardware, "get_scl_pin", lambda: 11)
148
+ monkeypatch.setattr(fta.hardware, "get_sda_pin", lambda: 13)
149
+ monkeypatch.setattr(fta.hardware, "is_i2c_device_present", lambda _address: False)
150
+ monkeypatch.setitem(sys.modules, "busio", FakeBusIOModule())
151
+ monkeypatch.setattr(plugin_pkg, "adafruit_mlx90632", FakeMLXModule, raising=False)
152
+
153
+ job: Any = object.__new__(fta.TemperatureAutomationJobFIR)
154
+
155
+ with pytest.raises(pr_exc.HardwareNotFoundError) as e:
156
+ job._setup_mlx_driver(address=0x3A)
157
+
158
+ assert "MLX90632 not found at address 0x3A" in str(e.value)
159
+
160
+
126
161
  def test_update_volume_from_mqtt_ignores_invalid_payloads() -> None:
127
162
  job: Any = object.__new__(fta.TemperatureAutomationJobFIR)
128
163
  job._volume_ml = 25.0
@@ -377,6 +412,36 @@ def test_update_heater_respects_pwm_lock() -> None:
377
412
  assert calls == [50.0]
378
413
 
379
414
 
415
+ def test_check_if_exceeds_max_temp_reduces_heater_gently_in_danger_zone(
416
+ monkeypatch: pytest.MonkeyPatch,
417
+ ) -> None:
418
+ monkeypatch.setattr(
419
+ fta,
420
+ "get_pioreactor_model",
421
+ lambda: SimpleNamespace(
422
+ max_temp_to_reduce_heating=50.0,
423
+ max_temp_to_disable_heating=60.0,
424
+ max_temp_to_shutdown=70.0,
425
+ ),
426
+ )
427
+
428
+ job: Any = object.__new__(fta.TemperatureAutomationJobFIR)
429
+ job.heater_duty_cycle = 80.0
430
+ job.logger = SimpleNamespace(debug=lambda *_args, **_kwargs: None)
431
+ reductions: list[float] = []
432
+
433
+ def fake_update_heater(new_dc: float) -> bool:
434
+ reductions.append(new_dc)
435
+ return True
436
+
437
+ job._update_heater = fake_update_heater
438
+
439
+ observed_temp = job._check_if_exceeds_max_temp(55.0)
440
+
441
+ assert observed_temp == pytest.approx(55.0)
442
+ assert reductions == [pytest.approx(80.0 * fta.DANGER_ZONE_DUTY_CYCLE_REDUCTION_FACTOR)]
443
+
444
+
380
445
  def test_bias_trim_probe_step_ui_uses_store_estimator(monkeypatch: pytest.MonkeyPatch) -> None:
381
446
  base = SimpleNamespace(
382
447
  estimator_name="baseline-estimator",