diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index bdbe3412539..b4718499d64 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -344,6 +344,9 @@ class MqttFan(MqttEntity, FanEntity): def state_received(msg): """Handle new received MQTT message.""" payload = self._value_templates[CONF_STATE](msg.payload) + if not payload: + _LOGGER.debug("Ignoring empty state from '%s'", msg.topic) + return if payload == self._payload["STATE_ON"]: self._state = True elif payload == self._payload["STATE_OFF"]: @@ -362,22 +365,27 @@ class MqttFan(MqttEntity, FanEntity): def percentage_received(msg): """Handle new received MQTT message for the percentage.""" numeric_val_str = self._value_templates[ATTR_PERCENTAGE](msg.payload) + if not numeric_val_str: + _LOGGER.debug("Ignoring empty speed from '%s'", msg.topic) + return try: percentage = ranged_value_to_percentage( self._speed_range, int(numeric_val_str) ) except ValueError: _LOGGER.warning( - "'%s' received on topic %s is not a valid speed within the speed range", + "'%s' received on topic %s. '%s' is not a valid speed within the speed range", msg.payload, msg.topic, + numeric_val_str, ) return if percentage < 0 or percentage > 100: _LOGGER.warning( - "'%s' received on topic %s is not a valid speed within the speed range", + "'%s' received on topic %s. '%s' is not a valid speed within the speed range", msg.payload, msg.topic, + numeric_val_str, ) return self._percentage = percentage @@ -396,11 +404,15 @@ class MqttFan(MqttEntity, FanEntity): def preset_mode_received(msg): """Handle new received MQTT message for preset mode.""" preset_mode = self._value_templates[ATTR_PRESET_MODE](msg.payload) + if not preset_mode: + _LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic) + return if preset_mode not in self.preset_modes: _LOGGER.warning( - "'%s' received on topic %s is not a valid preset mode", + "'%s' received on topic %s. '%s' is not a valid preset mode", msg.payload, msg.topic, + preset_mode, ) return @@ -436,9 +448,10 @@ class MqttFan(MqttEntity, FanEntity): self._speed = speed else: _LOGGER.warning( - "'%s' received on topic %s is not a valid speed", + "'%s' received on topic %s. '%s' is not a valid speed", msg.payload, msg.topic, + speed, ) return @@ -464,6 +477,9 @@ class MqttFan(MqttEntity, FanEntity): def oscillation_received(msg): """Handle new received MQTT message for the oscillation.""" payload = self._value_templates[ATTR_OSCILLATING](msg.payload) + if not payload: + _LOGGER.debug("Ignoring empty oscillation from '%s'", msg.topic) + return if payload == self._payload["OSCILLATE_ON_PAYLOAD"]: self._oscillation = True elif payload == self._payload["OSCILLATE_OFF_PAYLOAD"]: diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index bfa1f387bcd..ee12a7ce03c 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -408,6 +408,10 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap state = hass.states.get("fan.test") assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + async_fire_mqtt_message(hass, "percentage-state-topic", '{"otherval": 100}') + assert "Ignoring empty speed from" in caplog.text + caplog.clear() + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "low"}') assert "not a valid preset mode" in caplog.text caplog.clear() @@ -424,6 +428,99 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap state = hass.states.get("fan.test") assert state.attributes.get("preset_mode") == "silent" + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"otherval": 100}') + assert "Ignoring empty preset_mode from" in caplog.text + caplog.clear() + + +async def test_controlling_state_via_topic_and_json_message_shared_topic( + hass, mqtt_mock, caplog +): + """Test the controlling state via topic and JSON message using a shared topic.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "shared-state-topic", + "command_topic": "command-topic", + "oscillation_state_topic": "shared-state-topic", + "oscillation_command_topic": "oscillation-command-topic", + "percentage_state_topic": "shared-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_state_topic": "shared-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "auto", + "smart", + "whoosh", + "eco", + "breeze", + "silent", + ], + "state_value_template": "{{ value_json.state }}", + "oscillation_value_template": "{{ value_json.oscillation }}", + "percentage_value_template": "{{ value_json.percentage }}", + "preset_mode_value_template": "{{ value_json.preset_mode }}", + "speed_range_min": 1, + "speed_range_max": 100, + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message( + hass, + "shared-state-topic", + '{"state":"ON","preset_mode":"eco","oscillation":"oscillate_on","percentage": 50}', + ) + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get("oscillating") is True + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + assert state.attributes.get("preset_mode") == "eco" + + async_fire_mqtt_message( + hass, + "shared-state-topic", + '{"state":"ON","preset_mode":"auto","oscillation":"oscillate_off","percentage": 10}', + ) + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get("oscillating") is False + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 10 + assert state.attributes.get("preset_mode") == "auto" + + async_fire_mqtt_message( + hass, + "shared-state-topic", + '{"state":"OFF","preset_mode":"auto","oscillation":"oscillate_off","percentage": 0}', + ) + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get("oscillating") is False + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get("preset_mode") == "auto" + + async_fire_mqtt_message( + hass, + "shared-state-topic", + '{"percentage": 100}', + ) + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get("preset_mode") == "auto" + assert "Ignoring empty preset_mode from" in caplog.text + assert "Ignoring empty state from" in caplog.text + assert "Ignoring empty oscillation from" in caplog.text + caplog.clear() + async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog): """Test optimistic mode without state topic."""