* 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:
Teagan Glenn 2016-08-27 14:53:12 -06:00 committed by GitHub
parent a4b90c9879
commit fdb6de4d23
7 changed files with 260 additions and 40 deletions

View File

@ -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

View 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

View File

@ -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

View File

@ -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'

View File

@ -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()

View 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))

View File

@ -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)