diff --git a/miio/integrations/genericmiot/status.py b/miio/integrations/genericmiot/status.py index 54ad51354..4931ced25 100644 --- a/miio/integrations/genericmiot/status.py +++ b/miio/integrations/genericmiot/status.py @@ -17,16 +17,44 @@ class GenericMiotStatus(DeviceStatus): def __init__(self, response, dev): self._model: DeviceModel = dev._miot_model self._dev = dev - self._data = {elem["did"]: elem["value"] for elem in response} - # for hardcoded json output.. see click_common.json_output - self.data = self._data - - self._data_by_siid_piid = { - (elem["siid"], elem["piid"]): elem["value"] for elem in response - } - self._data_by_normalized_name = { - self._normalize_name(elem["did"]): elem["value"] for elem in response - } + self._data = {} + self._data_by_siid_piid = {} + self._data_by_normalized_name = {} + self._initialize_data(response) + + def _initialize_data(self, response): + def _is_valid_property_response(elem): + code = elem.get("code") + if code is None: + _LOGGER.debug("Ignoring due to missing 'code': %s", elem) + return False + + if code != 0: + _LOGGER.warning("Ignoring due to error code '%s': %s", code, elem) + return False + + needed_keys = ("did", "piid", "siid", "value") + for key in needed_keys: + if key not in elem: + _LOGGER.debug("Ignoring due to missing '%s': %s", key, elem) + return False + + return True + + for prop in response: + if not _is_valid_property_response(prop): + continue + + self._data[prop["did"]] = prop["value"] + self._data_by_siid_piid[(prop["siid"], prop["piid"])] = prop["value"] + self._data_by_normalized_name[self._normalize_name(prop["did"])] = prop[ + "value" + ] + + @property + def data(self): + """Implemented to support json output.""" + return self._data def _normalize_name(self, id_: str) -> str: """Return a cleaned id for dict searches.""" diff --git a/miio/integrations/genericmiot/tests/test_status.py b/miio/integrations/genericmiot/tests/test_status.py new file mode 100644 index 000000000..b275cd9c6 --- /dev/null +++ b/miio/integrations/genericmiot/tests/test_status.py @@ -0,0 +1,38 @@ +import logging +from unittest.mock import Mock + +import pytest + +from ..status import GenericMiotStatus + + +@pytest.fixture(scope="session") +def mockdev(): + yield Mock() + + +VALID_RESPONSE = {"code": 0, "did": "valid-response", "piid": 1, "siid": 1, "value": 1} + + +@pytest.mark.parametrize("key", ("did", "piid", "siid", "value", "code")) +def test_response_with_missing_value(key, mockdev, caplog: pytest.LogCaptureFixture): + """Verify that property responses without necessary keys are ignored.""" + caplog.set_level(logging.DEBUG) + + prop = {"code": 0, "did": f"no-{key}-in-response", "piid": 1, "siid": 1, "value": 1} + prop.pop(key) + + status = GenericMiotStatus([VALID_RESPONSE, prop], mockdev) + assert f"Ignoring due to missing '{key}'" in caplog.text + assert len(status.data) == 1 + + +@pytest.mark.parametrize("code", (-123, 123)) +def test_response_with_error_codes(code, mockdev, caplog: pytest.LogCaptureFixture): + caplog.set_level(logging.WARNING) + + did = f"error-code-{code}" + prop = {"code": code, "did": did, "piid": 1, "siid": 1} + status = GenericMiotStatus([VALID_RESPONSE, prop], mockdev) + assert f"Ignoring due to error code '{code}'" in caplog.text + assert len(status.data) == 1