"""PT1000 curve tests against published IEC 60751 reference values. These numbers are the whole basis for the grill believing our fake probe, so they are checked against the standard table rather than against our own output. """ from __future__ import annotations import pytest from smokescreen.pt1000 import ( MAX_TEMP_C, MIN_TEMP_C, is_plausible, required_span_ohms, resistance_to_temp_c, resistance_to_temp_c_clamped, resolution_c_per_ohm, temp_c_to_resistance, ) # (celsius, ohms) from the IEC 60751 alpha=0.00385 PT1000 table. REFERENCE_POINTS = [ (-20.0, 921.60), (0.0, 1000.00), (20.0, 1077.94), (50.0, 1193.97), (100.0, 1385.06), (150.0, 1573.25), (200.0, 1758.56), (250.0, 1940.98), ] @pytest.mark.parametrize("temp_c,expected_ohms", REFERENCE_POINTS) def test_temp_to_resistance_matches_standard_table(temp_c, expected_ohms): assert temp_c_to_resistance(temp_c) == pytest.approx(expected_ohms, abs=0.05) @pytest.mark.parametrize("temp_c,ohms", REFERENCE_POINTS) def test_resistance_to_temp_matches_standard_table(temp_c, ohms): assert resistance_to_temp_c(ohms) == pytest.approx(temp_c, abs=0.02) @pytest.mark.parametrize("temp_c", [-40, -10, 0, 0.5, 37, 93.3, 165, 250, 350]) def test_round_trip_is_lossless(temp_c): ohms = temp_c_to_resistance(temp_c) assert resistance_to_temp_c(ohms) == pytest.approx(temp_c, abs=0.001) def test_curve_is_monotonic_across_the_food_range(): """A non-monotonic patch would make the inverse ambiguous.""" ohms = [temp_c_to_resistance(t / 10) for t in range(-400, 3501)] assert all(b > a for a, b in zip(ohms, ohms[1:])) @pytest.mark.parametrize("bad_ohms", [50.0, 0.0, 842.0, 2300.0, 1e6, float("nan")]) def test_resistance_off_the_curve_is_rejected(bad_ohms): """Unguarded, the curve extrapolates 50 ohm to a plausible-looking -235 C.""" with pytest.raises(ValueError): resistance_to_temp_c(bad_ohms) @pytest.mark.parametrize("ohms,expected", [(50.0, MIN_TEMP_C), (1e6, MAX_TEMP_C)]) def test_clamped_variant_saturates_instead_of_raising(ohms, expected): assert resistance_to_temp_c_clamped(ohms) == pytest.approx(expected, abs=0.01) def test_clamped_variant_is_exact_inside_the_range(): assert resistance_to_temp_c_clamped(1385.06) == pytest.approx(100.0, abs=0.02) def test_required_span_covers_a_real_cook(): lo, hi = required_span_ohms(0.0, 200.0) assert lo == pytest.approx(1000.0, abs=0.1) assert hi == pytest.approx(1758.6, abs=0.5) # This is the number that sizes the hardware: a 1k rheostat is enough. assert hi - lo < 1000.0 def test_one_ohm_is_finer_than_the_grill_displays(): # Grill shows whole degrees F; 1 ohm must be comfortably under 1 F (0.56 C). assert resolution_c_per_ohm(100.0) < 0.4 @pytest.mark.parametrize("bad", [float("nan"), float("inf"), -273.0, 900.0]) def test_implausible_values_rejected(bad): assert not is_plausible(bad) @pytest.mark.parametrize("good", [-20.0, 0.0, 93.3, 200.0]) def test_plausible_values_accepted(good): assert is_plausible(good)