diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 305c99072ab..f484ca36b25 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -422,21 +422,21 @@ class FanEntity(ToggleEntity): return self.speed not in [SPEED_OFF, None] @property - def _implemented_percentage(self): + def _implemented_percentage(self) -> bool: """Return true if percentage has been implemented.""" return not hasattr(self.set_percentage, _FAN_NATIVE) or not hasattr( self.async_set_percentage, _FAN_NATIVE ) @property - def _implemented_preset_mode(self): + def _implemented_preset_mode(self) -> bool: """Return true if preset_mode has been implemented.""" return not hasattr(self.set_preset_mode, _FAN_NATIVE) or not hasattr( self.async_set_preset_mode, _FAN_NATIVE ) @property - def _implemented_speed(self): + def _implemented_speed(self) -> bool: """Return true if speed has been implemented.""" return not hasattr(self.set_speed, _FAN_NATIVE) or not hasattr( self.async_set_speed, _FAN_NATIVE diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 87b063583bf..563a9af2849 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -272,6 +272,11 @@ class TemplateFan(TemplateEntity, FanEntity): # List of valid preset modes self._preset_modes = preset_modes + @property + def _implemented_speed(self): + """Return true if speed has been implemented.""" + return bool(self._set_speed_script or self._speed_template) + @property def name(self): """Return the display name of this fan.""" @@ -290,7 +295,7 @@ class TemplateFan(TemplateEntity, FanEntity): @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" - return self._speed_count or super().speed_count + return self._speed_count or 100 @property def speed_list(self) -> list: diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 34dccd7d172..b5820cd5c76 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -654,8 +654,8 @@ async def test_set_percentage(hass, calls): async def test_increase_decrease_speed(hass, calls): - """Test set valid increase and derease speed.""" - await _register_components(hass) + """Test set valid increase and decrease speed.""" + await _register_components(hass, speed_count=3) # Turn on fan await common.async_turn_on(hass, _TEST_FAN) @@ -693,6 +693,42 @@ async def test_increase_decrease_speed(hass, calls): _verify(hass, STATE_ON, SPEED_LOW, 33, None, None, None) +async def test_increase_decrease_speed_default_speed_count(hass, calls): + """Test set valid increase and decrease speed.""" + await _register_components( + hass, + ) + + # Turn on fan + await common.async_turn_on(hass, _TEST_FAN) + + # Set fan's percentage speed to 100 + await common.async_set_percentage(hass, _TEST_FAN, 100) + + # verify + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 100 + + _verify(hass, STATE_ON, SPEED_HIGH, 100, None, None, None) + + # Set fan's percentage speed to 99 + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 99 + + _verify(hass, STATE_ON, SPEED_HIGH, 99, None, None, None) + + # Set fan's percentage speed to 98 + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 98 + + _verify(hass, STATE_ON, SPEED_HIGH, 98, None, None, None) + + for _ in range(32): + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 66 + + _verify(hass, STATE_ON, SPEED_MEDIUM, 66, None, None, None) + + async def test_set_invalid_speed_from_initial_stage(hass, calls): """Test set invalid speed when fan is in initial state.""" await _register_components(hass) @@ -926,7 +962,9 @@ def _verify( assert attributes.get(ATTR_PRESET_MODE) == expected_preset_mode -async def _register_components(hass, speed_list=None, preset_modes=None): +async def _register_components( + hass, speed_list=None, preset_modes=None, speed_count=None +): """Register basic components for testing.""" with assert_setup_component(1, "input_boolean"): assert await setup.async_setup_component( @@ -1051,6 +1089,9 @@ async def _register_components(hass, speed_list=None, preset_modes=None): if preset_modes: test_fan_config["preset_modes"] = preset_modes + if speed_count: + test_fan_config["speed_count"] = speed_count + assert await setup.async_setup_component( hass, "fan", @@ -1105,3 +1146,217 @@ async def test_unique_id(hass): await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 + + +@pytest.mark.parametrize( + "speed_count, percentage_step", [(0, 1), (100, 1), (3, 100 / 3)] +) +async def test_implemented_percentage(hass, speed_count, percentage_step): + """Test a fan that implements percentage.""" + await setup.async_setup_component( + hass, + "fan", + { + "fan": { + "platform": "template", + "fans": { + "mechanical_ventilation": { + "friendly_name": "Mechanische ventilatie", + "unique_id": "a2fd2e38-674b-4b47-b5ef-cc2362211a72", + "value_template": "{{ states('light.mv_snelheid') }}", + "percentage_template": "{{ (state_attr('light.mv_snelheid','brightness') | int / 255 * 100) | int }}", + "turn_on": [ + { + "service": "switch.turn_off", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": 40}, + }, + ], + "turn_off": [ + { + "service": "light.turn_off", + "target": { + "entity_id": "light.mv_snelheid", + }, + }, + { + "service": "switch.turn_on", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + ], + "set_percentage": [ + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": "{{ percentage }}"}, + } + ], + "speed_count": speed_count, + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + state = hass.states.get("fan.mechanical_ventilation") + attributes = state.attributes + assert attributes["percentage_step"] == percentage_step + + +async def test_implemented_preset_mode(hass): + """Test a fan that implements preset_mode.""" + await setup.async_setup_component( + hass, + "fan", + { + "fan": { + "platform": "template", + "fans": { + "mechanical_ventilation": { + "friendly_name": "Mechanische ventilatie", + "unique_id": "a2fd2e38-674b-4b47-b5ef-cc2362211a72", + "value_template": "{{ states('light.mv_snelheid') }}", + "preset_mode_template": "{{ 'any' }}", + "preset_modes": ["any"], + "set_preset_mode": [ + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": "{{ percentage }}"}, + } + ], + "turn_on": [ + { + "service": "switch.turn_off", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": 40}, + }, + ], + "turn_off": [ + { + "service": "light.turn_off", + "target": { + "entity_id": "light.mv_snelheid", + }, + }, + { + "service": "switch.turn_on", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + ], + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + state = hass.states.get("fan.mechanical_ventilation") + attributes = state.attributes + assert attributes["percentage"] is None + + +async def test_implemented_speed(hass): + """Test a fan that implements speed.""" + await setup.async_setup_component( + hass, + "fan", + { + "fan": { + "platform": "template", + "fans": { + "mechanical_ventilation": { + "friendly_name": "Mechanische ventilatie", + "unique_id": "a2fd2e38-674b-4b47-b5ef-cc2362211a72", + "value_template": "{{ states('light.mv_snelheid') }}", + "speed_template": "{{ 'fast' }}", + "speeds": ["slow", "fast"], + "set_preset_mode": [ + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": "{{ percentage }}"}, + } + ], + "turn_on": [ + { + "service": "switch.turn_off", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + { + "service": "light.turn_on", + "target": { + "entity_id": "light.mv_snelheid", + }, + "data": {"brightness_pct": 40}, + }, + ], + "turn_off": [ + { + "service": "light.turn_off", + "target": { + "entity_id": "light.mv_snelheid", + }, + }, + { + "service": "switch.turn_on", + "target": { + "entity_id": "switch.mv_automatisch", + }, + }, + ], + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + state = hass.states.get("fan.mechanical_ventilation") + attributes = state.attributes + assert attributes["percentage"] == 100 + assert attributes["speed"] == "fast"