hermes_thermal reads the CPU temperature and throttle flags, publishes /health/*, and at 72 / 77 / 82 C scales the camera, face and detector rates down through their live parameters, restoring them as it cools one level at a time with a margin and a dwell so it does not flap. The face and detector gained live parameter callbacks for this, the bridge's robot://status carries a health block (temperature, level, throttle flags now and since boot), and a bringup test asserts the guard's normal rates equal the real ones. Live testing on the robot (forcing 73 / 78 / 85 C and back) caught a bug the unit tests missed: one logging line whose severity changed between warn and info raised in rclpy and killed the guard on its first cooling step, and the respawn hid it. Fixed with separate call sites; the tick now survives any error; a full heat-then-cool test drives the real node, and a mutation check confirmed it fails on the original bug. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
35 lines
1.3 KiB
Python
35 lines
1.3 KiB
Python
import subprocess
|
|
|
|
from hermes_thermal.sensors import decode_throttled, parse_throttled, read_temp_c, read_throttled
|
|
|
|
|
|
def test_read_temp(tmp_path):
|
|
f = tmp_path / 'temp'
|
|
f.write_text('61850\n')
|
|
assert read_temp_c(str(f)) == 61.85
|
|
assert read_temp_c(str(tmp_path / 'missing')) is None
|
|
f.write_text('garbage')
|
|
assert read_temp_c(str(f)) is None
|
|
|
|
|
|
def test_parse_and_decode_throttled():
|
|
assert parse_throttled('throttled=0x0\n') == 0
|
|
assert parse_throttled('throttled=0x50005') == 0x50005
|
|
assert parse_throttled('nonsense') is None
|
|
d = decode_throttled(0x50005) # under-voltage and throttled, now and since boot
|
|
assert d['now'] == {'under_voltage': True, 'arm_frequency_capped': False, 'throttled': True,
|
|
'soft_temperature_limit': False}
|
|
assert d['since_boot']['under_voltage'] and d['since_boot']['throttled'] and not d['since_boot']['arm_frequency_capped']
|
|
assert decode_throttled(0)['now'] == {k: False for k in d['now']}
|
|
assert decode_throttled(None) == {'available': False}
|
|
|
|
|
|
def test_read_throttled_handles_a_missing_vcgencmd():
|
|
def boom(*a, **k):
|
|
raise FileNotFoundError
|
|
assert read_throttled(boom) is None
|
|
|
|
def ok(*a, **k):
|
|
return subprocess.CompletedProcess(a, 0, stdout='throttled=0x20002\n', stderr='')
|
|
assert read_throttled(ok) == 0x20002
|