mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Fan demo (#2976)
* Update attr to property and default state method * State prop is defined in parent class * Demo platform fan * PyDoc * Copy-pasta artifact * PyDoc * Linting * Raise error if turn_off and turn_on not implemented * Update demo platform * Initial unit test commit * Readability * Unneeded typing * Should inherit from fan entity * Turn off polling * Initial oscillating flag * Pass HASS into demo * Typing * Invoke set_speed instead of setting directly * Service update * Update demo tests * Forgot to block after service call. * linting * Test to make sure not implemented is thrown * Is On Method test * Update const to match string * Update services yaml * Toggle method * Toggle service * Typing * TYPE O * Attribute check * Type-o * Type-o * Put typing back * ToggleEntity * Linting * Linting * Oops * Stale prints * Demo support
This commit is contained in:
parent
a4b90c9879
commit
fdb6de4d23
@ -11,10 +11,10 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components import group
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.const import (
|
||||
STATE_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF, ATTR_ENTITY_ID,
|
||||
STATE_UNKNOWN)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -43,12 +43,12 @@ SPEED_HIGH = 'high'
|
||||
|
||||
ATTR_SPEED = 'speed'
|
||||
ATTR_SPEED_LIST = 'speed_list'
|
||||
ATTR_OSCILLATE = 'oscillate'
|
||||
ATTR_OSCILLATING = 'oscillating'
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
'speed': ATTR_SPEED,
|
||||
'speed_list': ATTR_SPEED_LIST,
|
||||
'oscillate': ATTR_OSCILLATE,
|
||||
'oscillating': ATTR_OSCILLATING,
|
||||
'supported_features': ATTR_SUPPORTED_FEATURES,
|
||||
} # type: dict
|
||||
|
||||
@ -68,16 +68,21 @@ FAN_TURN_OFF_SCHEMA = vol.Schema({
|
||||
|
||||
FAN_OSCILLATE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_OSCILLATE): cv.boolean
|
||||
vol.Required(ATTR_OSCILLATING): cv.boolean
|
||||
}) # type: dict
|
||||
|
||||
FAN_TOGGLE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids
|
||||
})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_on(hass, entity_id: str=None) -> bool:
|
||||
"""Return if the fans are on based on the statemachine."""
|
||||
entity_id = entity_id or ENTITY_ID_ALL_FANS
|
||||
return not hass.states.is_state(entity_id, STATE_OFF)
|
||||
state = hass.states.get(entity_id)
|
||||
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, STATE_UNKNOWN]
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
@ -102,12 +107,21 @@ def turn_off(hass, entity_id: str=None) -> None:
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
def toggle(hass, entity_id: str=None) -> None:
|
||||
"""Toggle all or specified fans."""
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity_id
|
||||
}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||
|
||||
|
||||
def oscillate(hass, entity_id: str=None, should_oscillate: bool=True) -> None:
|
||||
"""Set oscillation on all or specified fan."""
|
||||
data = {
|
||||
key: value for key, value in [
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_OSCILLATE, should_oscillate),
|
||||
(ATTR_OSCILLATING, should_oscillate),
|
||||
] if value is not None
|
||||
}
|
||||
|
||||
@ -180,38 +194,47 @@ def setup(hass, config: dict) -> None:
|
||||
return True
|
||||
|
||||
|
||||
class FanEntity(Entity):
|
||||
class FanEntity(ToggleEntity):
|
||||
"""Representation of a fan."""
|
||||
|
||||
# pylint: disable=no-self-use, abstract-method
|
||||
|
||||
def set_speed(self: Entity, speed: str) -> None:
|
||||
def set_speed(self: ToggleEntity, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
pass
|
||||
|
||||
def turn_on(self: Entity, speed: str=None) -> None:
|
||||
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
|
||||
"""Turn on the fan."""
|
||||
pass
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_off(self: Entity) -> None:
|
||||
def turn_off(self: ToggleEntity, **kwargs) -> None:
|
||||
"""Turn off the fan."""
|
||||
pass
|
||||
raise NotImplementedError()
|
||||
|
||||
def oscillate(self: Entity) -> None:
|
||||
def oscillate(self: ToggleEntity, oscillating: bool) -> None:
|
||||
"""Oscillate the fan."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def speed_list(self: Entity) -> list:
|
||||
def is_on(self):
|
||||
"""Return true if the entity is on."""
|
||||
return self.state_attributes.get(ATTR_SPEED, STATE_UNKNOWN) \
|
||||
not in [SPEED_OFF, STATE_UNKNOWN]
|
||||
|
||||
@property
|
||||
def speed_list(self: ToggleEntity) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return []
|
||||
|
||||
@property
|
||||
def state_attributes(self: Entity) -> dict:
|
||||
def state_attributes(self: ToggleEntity) -> dict:
|
||||
"""Return optional state attributes."""
|
||||
data = {} # type: dict
|
||||
|
||||
for prop, attr in PROP_TO_ATTR.items():
|
||||
if not hasattr(self, prop):
|
||||
continue
|
||||
|
||||
value = getattr(self, prop)
|
||||
if value is not None:
|
||||
data[attr] = value
|
||||
@ -219,6 +242,6 @@ class FanEntity(Entity):
|
||||
return data
|
||||
|
||||
@property
|
||||
def supported_features(self: Entity) -> int:
|
||||
def supported_features(self: ToggleEntity) -> int:
|
||||
"""Flag supported features."""
|
||||
return 0
|
||||
|
75
homeassistant/components/fan/demo.py
Normal file
75
homeassistant/components/fan/demo.py
Normal file
@ -0,0 +1,75 @@
|
||||
"""
|
||||
Demo garage door platform that has a fake fan.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
|
||||
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH,
|
||||
FanEntity, SUPPORT_SET_SPEED,
|
||||
SUPPORT_OSCILLATE)
|
||||
from homeassistant.const import STATE_OFF
|
||||
|
||||
|
||||
FAN_NAME = 'Living Room Fan'
|
||||
FAN_ENTITY_ID = 'fan.living_room_fan'
|
||||
|
||||
DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Setup demo garage door platform."""
|
||||
add_devices_callback([
|
||||
DemoFan(hass, FAN_NAME, STATE_OFF),
|
||||
])
|
||||
|
||||
|
||||
class DemoFan(FanEntity):
|
||||
"""A demonstration fan component."""
|
||||
|
||||
def __init__(self, hass, name: str, initial_state: str) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.hass = hass
|
||||
self.speed = initial_state
|
||||
self.oscillating = False
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Get entity name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed for a demo fan."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return [STATE_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]
|
||||
|
||||
def turn_on(self, speed: str=SPEED_MED) -> None:
|
||||
"""Turn on the entity."""
|
||||
self.set_speed(speed)
|
||||
|
||||
def turn_off(self) -> None:
|
||||
"""Turn off the entity."""
|
||||
self.oscillate(False)
|
||||
self.set_speed(STATE_OFF)
|
||||
|
||||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
self.speed = speed
|
||||
self.update_ha_state()
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation."""
|
||||
self.oscillating = oscillating
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return DEMO_SUPPORT
|
@ -64,8 +64,3 @@ class InsteonFanDevice(InsteonDevice, FanEntity):
|
||||
def speed_list(self) -> list:
|
||||
"""Get the available speeds for the fan."""
|
||||
return [SPEED_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Get the current device state."""
|
||||
return self.speed
|
||||
|
@ -40,6 +40,14 @@ oscillate:
|
||||
description: Name(s) of the entities to oscillate
|
||||
example: 'fan.desk_fan'
|
||||
|
||||
oscillate:
|
||||
oscillating:
|
||||
description: Flag to turn on/off oscillation
|
||||
example: True
|
||||
example: True
|
||||
|
||||
toggle:
|
||||
description: Toggle the fan on/off
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of the entities to toggle
|
||||
exampl: 'fan.living_room'
|
@ -1 +1,39 @@
|
||||
"""Test fan component plaforms."""
|
||||
|
||||
import unittest
|
||||
|
||||
from homeassistant.components.fan import FanEntity
|
||||
|
||||
|
||||
class BaseFan(FanEntity):
|
||||
"""Implementation of the abstract FanEntity."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the fan."""
|
||||
pass
|
||||
|
||||
|
||||
class TestFanEntity(unittest.TestCase):
|
||||
"""Test coverage for base fan entity class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Setup test data."""
|
||||
self.fan = BaseFan()
|
||||
|
||||
def tearDown(self):
|
||||
"""Tear down unit test data."""
|
||||
self.fan = None
|
||||
|
||||
def test_fanentity(self):
|
||||
"""Test fan entity methods."""
|
||||
self.assertIsNone(self.fan.state)
|
||||
self.assertEqual(0, len(self.fan.speed_list))
|
||||
self.assertEqual(0, self.fan.supported_features)
|
||||
self.assertEqual({}, self.fan.state_attributes)
|
||||
# Test set_speed not required
|
||||
self.fan.set_speed()
|
||||
self.fan.oscillate()
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.fan.turn_on()
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.fan.turn_off()
|
||||
|
84
tests/components/fan/test_demo.py
Normal file
84
tests/components/fan/test_demo.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""Test cases around the demo fan platform."""
|
||||
|
||||
import unittest
|
||||
|
||||
from homeassistant.components import fan
|
||||
from homeassistant.components.fan.demo import FAN_ENTITY_ID
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestDemoFan(unittest.TestCase):
|
||||
"""Test the fan demo platform."""
|
||||
|
||||
def get_entity(self):
|
||||
"""Helper method to get the fan entity."""
|
||||
return self.hass.states.get(FAN_ENTITY_ID)
|
||||
|
||||
def setUp(self):
|
||||
"""Initialize unit test data."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.assertTrue(fan.setup(self.hass, {'fan': {
|
||||
'platform': 'demo',
|
||||
}}))
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
def tearDown(self):
|
||||
"""Tear down unit test data."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_turn_on(self):
|
||||
"""Test turning on the device."""
|
||||
self.assertEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
fan.turn_on(self.hass, FAN_ENTITY_ID)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertNotEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
fan.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(STATE_ON, self.get_entity().state)
|
||||
self.assertEqual(fan.SPEED_HIGH,
|
||||
self.get_entity().attributes[fan.ATTR_SPEED])
|
||||
|
||||
def test_turn_off(self):
|
||||
"""Test turning off the device."""
|
||||
self.assertEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
fan.turn_on(self.hass, FAN_ENTITY_ID)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertNotEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
fan.turn_off(self.hass, FAN_ENTITY_ID)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
def test_set_speed(self):
|
||||
"""Test setting the speed of the device."""
|
||||
self.assertEqual(STATE_OFF, self.get_entity().state)
|
||||
|
||||
fan.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(fan.SPEED_LOW,
|
||||
self.get_entity().attributes.get('speed'))
|
||||
|
||||
def test_oscillate(self):
|
||||
"""Test oscillating the fan."""
|
||||
self.assertFalse(self.get_entity().attributes.get('oscillating'))
|
||||
|
||||
fan.oscillate(self.hass, FAN_ENTITY_ID, True)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertTrue(self.get_entity().attributes.get('oscillating'))
|
||||
|
||||
fan.oscillate(self.hass, FAN_ENTITY_ID, False)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertFalse(self.get_entity().attributes.get('oscillating'))
|
||||
|
||||
def test_is_on(self):
|
||||
"""Test is on service call."""
|
||||
self.assertFalse(fan.is_on(self.hass, FAN_ENTITY_ID))
|
||||
|
||||
fan.turn_on(self.hass, FAN_ENTITY_ID)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertTrue(fan.is_on(self.hass, FAN_ENTITY_ID))
|
@ -1,8 +1,9 @@
|
||||
"""Tests for the insteon hub fan platform."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_UNKNOWN
|
||||
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH)
|
||||
from homeassistant.const import (STATE_OFF, STATE_ON)
|
||||
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH,
|
||||
ATTR_SPEED)
|
||||
from homeassistant.components.fan.insteon_hub import (InsteonFanDevice,
|
||||
SUPPORT_SET_SPEED)
|
||||
|
||||
@ -50,27 +51,23 @@ class TestInsteonHubFanDevice(unittest.TestCase):
|
||||
self._NODE.response = {
|
||||
'status': 'succeeded'
|
||||
}
|
||||
self.assertEqual(STATE_UNKNOWN, self._DEVICE.state)
|
||||
self.assertEqual(STATE_OFF, self._DEVICE.state)
|
||||
self._DEVICE.turn_on()
|
||||
|
||||
self.assertNotEqual(STATE_OFF, self._DEVICE.state)
|
||||
self.assertEqual(STATE_ON, self._DEVICE.state)
|
||||
|
||||
self._DEVICE.turn_on(SPEED_MED)
|
||||
|
||||
self.assertEqual(SPEED_MED, self._DEVICE.state)
|
||||
|
||||
self._NODE.response = {
|
||||
}
|
||||
self._DEVICE.turn_on(SPEED_HIGH)
|
||||
|
||||
self.assertNotEqual(SPEED_HIGH, self._DEVICE.state)
|
||||
self.assertEqual(STATE_ON, self._DEVICE.state)
|
||||
self.assertEqual(SPEED_MED, self._DEVICE.state_attributes[ATTR_SPEED])
|
||||
|
||||
def test_turn_off(self):
|
||||
"""Test turning off device."""
|
||||
self._NODE.response = {
|
||||
'status': 'succeeded'
|
||||
}
|
||||
self.assertEqual(STATE_UNKNOWN, self._DEVICE.state)
|
||||
self._DEVICE.turn_off()
|
||||
|
||||
self.assertEqual(STATE_OFF, self._DEVICE.state)
|
||||
self._DEVICE.turn_on()
|
||||
self.assertEqual(STATE_ON, self._DEVICE.state)
|
||||
self._DEVICE.turn_off()
|
||||
self.assertEqual(STATE_OFF, self._DEVICE.state)
|
||||
|
Loading…
x
Reference in New Issue
Block a user