"""MEATER decode tests. These pin the community-derived decode so a refactor cannot silently change it. They do NOT prove the decode is correct -- only bench comparison against the MEATER app can do that. See docs/PROTOCOL_NOTES.md. """ from __future__ import annotations import pytest from smokescreen.sources.meater import decode_battery, decode_temperature def payload(tip: int, ambient_raw: int, ambient_offset: int) -> bytes: return ( tip.to_bytes(2, "little") + ambient_raw.to_bytes(2, "little") + ambient_offset.to_bytes(2, "little") + b"\x00\x00" ) def test_tip_decodes_to_sixteenths_of_a_degree(): # 400 counts -> (400 + 8) / 16 = 25.5 C tip_c, _ = decode_temperature(payload(400, 0, 0)) assert tip_c == pytest.approx(25.5) def test_zero_counts_is_just_above_freezing(): tip_c, _ = decode_temperature(payload(0, 0, 0)) assert tip_c == pytest.approx(0.5) def test_ambient_never_reads_below_tip(): """The max(0, ...) term means ambient floors at the tip temperature.""" tip_c, ambient_c = decode_temperature(payload(1000, 0, 200)) assert ambient_c == pytest.approx(tip_c) def test_ambient_rises_above_tip_when_raw_exceeds_offset(): tip_c, ambient_c = decode_temperature(payload(1000, 500, 48)) assert ambient_c > tip_c def test_short_payload_is_rejected_not_guessed(): with pytest.raises(ValueError): decode_temperature(b"\x01\x02") def test_battery_scales_by_ten(): assert decode_battery((7).to_bytes(2, "little")) == 70 def test_battery_clamps_to_100(): assert decode_battery((25).to_bytes(2, "little")) == 100 def test_short_battery_payload_is_rejected(): with pytest.raises(ValueError): decode_battery(b"\x01")