diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index f6d60ae8cbe..f5b347d6b71 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -94,6 +94,8 @@ DEFAULT_SPEED_RANGE_MAX = 100 OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_OFF_PAYLOAD = "oscillate_off" +PAYLOAD_NONE = "None" + MQTT_FAN_ATTRIBUTES_BLOCKED = frozenset( { fan.ATTR_DIRECTION, @@ -243,7 +245,7 @@ class MqttFan(MqttEntity, FanEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT fan.""" - self._state = False + self._state = None self._percentage = None self._preset_mode = None self._oscillation = None @@ -367,6 +369,8 @@ class MqttFan(MqttEntity, FanEntity): self._state = True elif payload == self._payload["STATE_OFF"]: self._state = False + elif payload == PAYLOAD_NONE: + self._state = None self.async_write_ha_state() if self._topic[CONF_STATE_TOPIC] is not None: @@ -493,7 +497,7 @@ class MqttFan(MqttEntity, FanEntity): return self._optimistic @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 0a3b2499dc2..ecc1f15c204 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -27,6 +27,7 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNKNOWN, ) from homeassistant.setup import async_setup_component @@ -119,7 +120,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "StAtE_On") @@ -194,6 +195,10 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): assert state.attributes.get(fan.ATTR_PERCENTAGE) is None assert state.attributes.get(fan.ATTR_SPEED) is None + async_fire_mqtt_message(hass, "state-topic", "None") + state = hass.states.get("fan.test") + assert state.state == STATE_UNKNOWN + async def test_controlling_state_via_topic_with_different_speed_range( hass, mqtt_mock, caplog @@ -285,7 +290,7 @@ async def test_controlling_state_via_topic_no_percentage_topics( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "preset-mode-state-topic", "smart") @@ -349,13 +354,17 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", '{"val":"ON"}') state = hass.states.get("fan.test") assert state.state == STATE_ON + async_fire_mqtt_message(hass, "state-topic", '{"val": null}') + state = hass.states.get("fan.test") + assert state.state == STATE_UNKNOWN + async_fire_mqtt_message(hass, "state-topic", '{"val":"OFF"}') state = hass.states.get("fan.test") assert state.state == STATE_OFF @@ -449,7 +458,7 @@ async def test_controlling_state_via_topic_and_json_message_shared_topic( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message( @@ -527,7 +536,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -748,7 +757,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -883,7 +892,7 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -1022,7 +1031,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic( await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_preset_mode(hass, "fan.test", "medium") @@ -1086,7 +1095,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -1344,7 +1353,7 @@ async def test_attributes(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNKNOWN await common.async_turn_on(hass, "fan.test") state = hass.states.get("fan.test")