diff --git a/packages/modules/devices/shelly/shelly/status_handler.py b/packages/modules/devices/shelly/shelly/status_handler.py index 42ec365d8b..77ee5b270b 100644 --- a/packages/modules/devices/shelly/shelly/status_handler.py +++ b/packages/modules/devices/shelly/shelly/status_handler.py @@ -36,10 +36,13 @@ def parse_temperatures(status: Dict, generation: int) -> Optional[List[float]]: for index in range(3): try: if generation == 1: - value = status['ext_temperature'][str(index)]['tC'] + entry = status['ext_temperature'][str(index)] else: - value = status[f'temperature:{100 + index}']['tC'] - temperatures.append(float(value)) + entry = status[f'temperature:{100 + index}'] + except (KeyError, TypeError): + continue + try: + temperatures.append(float(entry['tC'])) except (KeyError, TypeError, ValueError): log.exception(f"Error parsing temperature for index {index} in status: {status}") return temperatures if len(temperatures) > 0 else None diff --git a/packages/modules/devices/shelly/shelly/status_handler_test.py b/packages/modules/devices/shelly/shelly/status_handler_test.py new file mode 100644 index 0000000000..a2f8f19111 --- /dev/null +++ b/packages/modules/devices/shelly/shelly/status_handler_test.py @@ -0,0 +1,65 @@ +import logging + +from modules.devices.shelly.shelly.status_handler import parse_temperatures + +# real status dump from a Shelly Plug S without external temp probes attached (Discussion #3466, +# discussioncomment-19500699): ext_temperature is an empty dict, not missing/malformed data +GEN1_STATUS_WITHOUT_EXT_SENSORS = { + "temperature": 38.73, + "tmp": {"tC": 38.73, "tF": 101.71, "is_valid": True}, + "ext_sensors": {}, + "ext_temperature": {}, + "ext_humidity": {}, +} + + +def test_parse_temperatures_returns_none_without_logging_error_when_no_ext_sensors(caplog): + with caplog.at_level(logging.ERROR): + result = parse_temperatures(GEN1_STATUS_WITHOUT_EXT_SENSORS, generation=1) + + assert result is None + assert caplog.records == [] + + +def test_parse_temperatures_gen1_reads_available_ext_sensors(): + status = {"ext_temperature": {"0": {"tC": 21.5}, "2": {"tC": 22.1}}} + + result = parse_temperatures(status, generation=1) + + assert result == [21.5, 22.1] + + +def test_parse_temperatures_still_logs_on_genuinely_malformed_value(caplog): + status = {"ext_temperature": {"0": {"tC": "not-a-number"}}} + + with caplog.at_level(logging.ERROR): + result = parse_temperatures(status, generation=1) + + assert result is None + assert len(caplog.records) == 1 + + +def test_parse_temperatures_logs_when_entry_present_but_missing_tc(caplog): + status = {"ext_temperature": {"0": {"hwID": "28FF"}}} + + with caplog.at_level(logging.ERROR): + result = parse_temperatures(status, generation=1) + + assert result is None + assert len(caplog.records) == 1 + + +def test_parse_temperatures_logs_instead_of_raising_when_entry_is_not_a_mapping(): + status = {"ext_temperature": {"0": 21.5}} + + result = parse_temperatures(status, generation=1) + + assert result is None + + +def test_parse_temperatures_gen2_without_ext_sensors_does_not_log(caplog): + with caplog.at_level(logging.ERROR): + result = parse_temperatures({}, generation=2) + + assert result is None + assert caplog.records == []