Fix SMART temperature always reporting null.
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run

Real drives report attribute 194 raw values like "52 (Min/Max 16/57)"
which int() cannot parse, so every drive's temperature was null.
Prefer pySMART's `temperature` property, which parses these formats
and also works for NVMe devices that have no ATA attribute table.

Verified on real hardware (WD100EMAZ over USB-SAT): 51-53C now
reported where null was returned before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Roland 2026-06-02 13:50:47 -06:00
parent 6088cbfe2a
commit 87ed286f7d
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 46 additions and 1 deletions

View file

@ -62,7 +62,16 @@ def _get_device_smart(device) -> dict:
def _get_temperature(device) -> Optional[int]:
"""Get drive temperature from SMART attributes."""
"""Get drive temperature.
Prefer pySMART's `temperature` property: it parses real-world raw
values like "52 (Min/Max 16/57)" that int() cannot, and works for
NVMe devices which have no ATA attribute table. Fall back to the
plain attributes for devices where the property is unavailable.
"""
temp = getattr(device, "temperature", None)
if isinstance(temp, int):
return temp
temp = _get_attribute(device, "Temperature_Celsius")
if temp is None:
temp = _get_attribute(device, "Airflow_Temperature_Cel")

View file

@ -119,11 +119,47 @@ class TestGetTemperature:
def test_returns_none_if_not_found(self):
"""Should return None if no temperature attribute."""
mock_device = mock.Mock()
mock_device.temperature = None
mock_device.attributes = []
result = _get_temperature(mock_device)
assert result is None
def test_prefers_pysmart_temperature_property(self):
"""Real drives report raw values like '52 (Min/Max 16/57)' which
int() cannot parse; pySMART's temperature property handles them."""
mock_attr = mock.Mock()
mock_attr.name = "Temperature_Celsius"
mock_attr.raw = "52 (Min/Max 16/57)"
mock_device = mock.Mock()
mock_device.temperature = 52
mock_device.attributes = [mock_attr]
result = _get_temperature(mock_device)
assert result == 52
def test_pysmart_temperature_property_for_nvme(self):
"""NVMe devices have no ATA attribute table, only the property."""
mock_device = mock.Mock()
mock_device.temperature = 38
mock_device.attributes = None
result = _get_temperature(mock_device)
assert result == 38
def test_ignores_non_int_temperature_property(self):
"""A missing/bogus temperature property falls back to attributes."""
mock_attr = mock.Mock()
mock_attr.name = "Temperature_Celsius"
mock_attr.raw = "35"
mock_device = mock.Mock() # .temperature is a Mock, not an int
mock_device.attributes = [mock_attr]
result = _get_temperature(mock_device)
assert result == 35
class TestGetAttribute:
"""Tests for _get_attribute function."""