2016-03-09 22:49:54 +00:00
|
|
|
"""An abstract class for entities."""
|
2016-09-30 19:57:24 +00:00
|
|
|
import asyncio
|
2016-06-22 16:13:18 +00:00
|
|
|
import logging
|
2016-11-16 05:06:50 +00:00
|
|
|
import functools as ft
|
2016-11-04 04:58:25 +00:00
|
|
|
from timeit import default_timer as timer
|
2015-04-25 04:39:35 +00:00
|
|
|
|
2017-01-22 19:19:50 +00:00
|
|
|
from typing import Optional, List
|
2016-07-28 03:33:49 +00:00
|
|
|
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON,
|
|
|
|
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
|
2016-04-20 03:30:44 +00:00
|
|
|
STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
2017-02-08 04:42:45 +00:00
|
|
|
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES)
|
2017-01-28 20:29:51 +00:00
|
|
|
from homeassistant.core import HomeAssistant, DOMAIN as CORE_DOMAIN
|
2015-08-30 02:34:35 +00:00
|
|
|
from homeassistant.exceptions import NoEntitySpecifiedError
|
2016-01-24 07:00:46 +00:00
|
|
|
from homeassistant.util import ensure_unique_string, slugify
|
2016-10-16 16:35:46 +00:00
|
|
|
from homeassistant.util.async import (
|
|
|
|
run_coroutine_threadsafe, run_callback_threadsafe)
|
2017-01-22 19:19:50 +00:00
|
|
|
from homeassistant.helpers.customize import get_overrides
|
2016-06-22 16:13:18 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-01-24 06:37:15 +00:00
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def generate_entity_id(entity_id_format: str, name: Optional[str],
|
|
|
|
current_ids: Optional[List[str]]=None,
|
2016-08-09 03:42:25 +00:00
|
|
|
hass: Optional[HomeAssistant]=None) -> str:
|
2016-03-25 19:35:38 +00:00
|
|
|
"""Generate a unique entity ID based on given entity IDs or used IDs."""
|
2016-01-24 07:00:46 +00:00
|
|
|
if current_ids is None:
|
|
|
|
if hass is None:
|
2016-06-22 16:13:18 +00:00
|
|
|
raise ValueError("Missing required parameter currentids or hass")
|
2016-10-16 16:35:46 +00:00
|
|
|
else:
|
|
|
|
return run_callback_threadsafe(
|
|
|
|
hass.loop, async_generate_entity_id, entity_id_format, name,
|
|
|
|
current_ids, hass
|
|
|
|
).result()
|
2016-01-24 07:00:46 +00:00
|
|
|
|
2016-10-16 16:35:46 +00:00
|
|
|
name = (name or DEVICE_DEFAULT_NAME).lower()
|
2016-01-24 07:00:46 +00:00
|
|
|
|
2016-10-16 16:35:46 +00:00
|
|
|
return ensure_unique_string(
|
|
|
|
entity_id_format.format(slugify(name)), current_ids)
|
2016-10-01 08:22:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
def async_generate_entity_id(entity_id_format: str, name: Optional[str],
|
2016-10-16 16:35:46 +00:00
|
|
|
current_ids: Optional[List[str]]=None,
|
|
|
|
hass: Optional[HomeAssistant]=None) -> str:
|
2016-10-01 08:22:13 +00:00
|
|
|
"""Generate a unique entity ID based on given entity IDs or used IDs."""
|
2016-10-16 16:35:46 +00:00
|
|
|
if current_ids is None:
|
|
|
|
if hass is None:
|
|
|
|
raise ValueError("Missing required parameter currentids or hass")
|
|
|
|
|
|
|
|
current_ids = hass.states.async_entity_ids()
|
2016-10-01 08:22:13 +00:00
|
|
|
name = (name or DEVICE_DEFAULT_NAME).lower()
|
|
|
|
|
2016-01-24 07:00:46 +00:00
|
|
|
return ensure_unique_string(
|
2016-04-23 04:34:49 +00:00
|
|
|
entity_id_format.format(slugify(name)), current_ids)
|
2016-01-24 07:00:46 +00:00
|
|
|
|
|
|
|
|
2015-04-23 13:41:41 +00:00
|
|
|
class Entity(object):
|
2016-03-25 19:35:38 +00:00
|
|
|
"""An abstract class for Home Assistant entities."""
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# pylint: disable=no-self-use
|
2015-04-15 02:57:32 +00:00
|
|
|
# SAFE TO OVERWRITE
|
2016-03-25 19:35:38 +00:00
|
|
|
# The properties and methods here are safe to overwrite when inheriting
|
|
|
|
# this class. These may be used to customize the behavior of the entity.
|
2016-10-01 04:38:39 +00:00
|
|
|
entity_id = None # type: str
|
|
|
|
|
|
|
|
# Owning hass instance. Will be set by EntityComponent
|
|
|
|
hass = None # type: Optional[HomeAssistant]
|
|
|
|
|
2016-12-17 21:00:08 +00:00
|
|
|
# If we reported if this entity was slow
|
|
|
|
_slow_reported = False
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def should_poll(self) -> bool:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return True if entity has to be polled for state.
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
False if entity pushes its state to HA.
|
|
|
|
"""
|
|
|
|
return True
|
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def unique_id(self) -> str:
|
2016-03-25 19:35:38 +00:00
|
|
|
"""Return an unique ID."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return "{}.{}".format(self.__class__, id(self))
|
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def name(self) -> Optional[str]:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the name of the entity."""
|
2016-02-14 08:21:20 +00:00
|
|
|
return None
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def state(self) -> str:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the state of the entity."""
|
2015-09-10 06:37:15 +00:00
|
|
|
return STATE_UNKNOWN
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state_attributes(self):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the state attributes.
|
2016-02-07 06:28:29 +00:00
|
|
|
|
|
|
|
Implemented by component base class.
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return device specific state attributes.
|
2016-02-07 06:28:29 +00:00
|
|
|
|
|
|
|
Implemented by platform classes.
|
|
|
|
"""
|
2015-09-10 06:37:15 +00:00
|
|
|
return None
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the unit of measurement of this entity, if any."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return None
|
|
|
|
|
2015-11-03 08:20:48 +00:00
|
|
|
@property
|
|
|
|
def icon(self):
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the icon to use in the frontend, if any."""
|
2015-11-03 08:20:48 +00:00
|
|
|
return None
|
|
|
|
|
2016-02-24 06:41:24 +00:00
|
|
|
@property
|
|
|
|
def entity_picture(self):
|
|
|
|
"""Return the entity picture to use in the frontend, if any."""
|
|
|
|
return None
|
|
|
|
|
2015-04-23 13:41:41 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def hidden(self) -> bool:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return True if the entity should be hidden from UIs."""
|
2015-09-10 06:37:15 +00:00
|
|
|
return False
|
2015-04-23 13:41:41 +00:00
|
|
|
|
2016-01-31 02:03:51 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def available(self) -> bool:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return True if entity is available."""
|
|
|
|
return True
|
|
|
|
|
2016-02-14 07:42:11 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def assumed_state(self) -> bool:
|
2016-03-25 19:35:38 +00:00
|
|
|
"""Return True if unable to access real state of the entity."""
|
2016-02-14 07:42:11 +00:00
|
|
|
return False
|
|
|
|
|
2016-06-26 07:33:23 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def force_update(self) -> bool:
|
2016-06-26 07:33:23 +00:00
|
|
|
"""Return True if state updates should be forced.
|
|
|
|
|
|
|
|
If True, a state change will be triggered anytime the state property is
|
|
|
|
updated, not just when the value changes.
|
|
|
|
"""
|
|
|
|
return False
|
|
|
|
|
2017-02-08 04:42:45 +00:00
|
|
|
@property
|
|
|
|
def supported_features(self) -> int:
|
|
|
|
"""Flag supported features."""
|
|
|
|
return None
|
|
|
|
|
2015-04-15 02:57:32 +00:00
|
|
|
def update(self):
|
2016-10-01 04:38:39 +00:00
|
|
|
"""Retrieve latest state.
|
2015-04-15 02:57:32 +00:00
|
|
|
|
2016-10-01 04:38:39 +00:00
|
|
|
When not implemented, will forward call to async version if available.
|
|
|
|
"""
|
|
|
|
async_update = getattr(self, 'async_update', None)
|
|
|
|
|
|
|
|
if async_update is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
run_coroutine_threadsafe(async_update(), self.hass.loop).result()
|
2016-01-31 02:55:52 +00:00
|
|
|
|
2015-04-15 02:57:32 +00:00
|
|
|
# DO NOT OVERWRITE
|
|
|
|
# These properties and methods are either managed by Home Assistant or they
|
|
|
|
# are used to perform a very specific function. Overwriting these may
|
|
|
|
# produce undesirable effects in the entity's operation.
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
def update_ha_state(self, force_refresh=False):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Update Home Assistant with current state of entity.
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
If force_refresh == True will update entity before setting state.
|
|
|
|
"""
|
2016-09-30 19:57:24 +00:00
|
|
|
# We're already in a thread, do the force refresh here.
|
|
|
|
if force_refresh and not hasattr(self, 'async_update'):
|
|
|
|
self.update()
|
|
|
|
force_refresh = False
|
|
|
|
|
|
|
|
run_coroutine_threadsafe(
|
|
|
|
self.async_update_ha_state(force_refresh), self.hass.loop
|
|
|
|
).result()
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_update_ha_state(self, force_refresh=False):
|
|
|
|
"""Update Home Assistant with current state of entity.
|
|
|
|
|
|
|
|
If force_refresh == True will update entity before setting state.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
2015-03-22 01:49:30 +00:00
|
|
|
if self.hass is None:
|
|
|
|
raise RuntimeError("Attribute hass is None for {}".format(self))
|
|
|
|
|
|
|
|
if self.entity_id is None:
|
|
|
|
raise NoEntitySpecifiedError(
|
|
|
|
"No entity id specified for entity {}".format(self.name))
|
|
|
|
|
|
|
|
if force_refresh:
|
2016-09-30 19:57:24 +00:00
|
|
|
if hasattr(self, 'async_update'):
|
|
|
|
# pylint: disable=no-member
|
2016-10-01 04:38:39 +00:00
|
|
|
yield from self.async_update()
|
2016-09-30 19:57:24 +00:00
|
|
|
else:
|
|
|
|
# PS: Run this in our own thread pool once we have
|
|
|
|
# future support?
|
|
|
|
yield from self.hass.loop.run_in_executor(None, self.update)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-11-04 04:58:25 +00:00
|
|
|
start = timer()
|
|
|
|
|
|
|
|
state = self.state
|
|
|
|
|
|
|
|
if state is None:
|
|
|
|
state = STATE_UNKNOWN
|
|
|
|
else:
|
|
|
|
state = str(state)
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
attr = self.state_attributes or {}
|
|
|
|
|
2016-02-07 06:28:29 +00:00
|
|
|
device_attr = self.device_state_attributes
|
|
|
|
|
|
|
|
if device_attr is not None:
|
|
|
|
attr.update(device_attr)
|
|
|
|
|
2016-02-24 06:41:24 +00:00
|
|
|
self._attr_setter('unit_of_measurement', str, ATTR_UNIT_OF_MEASUREMENT,
|
|
|
|
attr)
|
2016-02-01 01:11:16 +00:00
|
|
|
|
2016-01-31 02:03:51 +00:00
|
|
|
if not self.available:
|
|
|
|
state = STATE_UNAVAILABLE
|
2016-02-01 01:11:16 +00:00
|
|
|
attr = {}
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2016-02-24 06:41:24 +00:00
|
|
|
self._attr_setter('name', str, ATTR_FRIENDLY_NAME, attr)
|
|
|
|
self._attr_setter('icon', str, ATTR_ICON, attr)
|
|
|
|
self._attr_setter('entity_picture', str, ATTR_ENTITY_PICTURE, attr)
|
|
|
|
self._attr_setter('hidden', bool, ATTR_HIDDEN, attr)
|
|
|
|
self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr)
|
2017-02-08 04:42:45 +00:00
|
|
|
self._attr_setter('supported_features', int, ATTR_SUPPORTED_FEATURES,
|
|
|
|
attr)
|
2016-02-14 07:42:11 +00:00
|
|
|
|
2016-11-04 04:58:25 +00:00
|
|
|
end = timer()
|
|
|
|
|
2016-12-17 21:00:08 +00:00
|
|
|
if not self._slow_reported and end - start > 0.4:
|
|
|
|
self._slow_reported = True
|
2016-11-04 04:58:25 +00:00
|
|
|
_LOGGER.warning('Updating state for %s took %.3f seconds. '
|
2016-11-04 05:45:01 +00:00
|
|
|
'Please report platform to the developers at '
|
|
|
|
'https://goo.gl/Nvioub', self.entity_id,
|
2016-11-04 04:58:25 +00:00
|
|
|
end - start)
|
|
|
|
|
2016-03-07 22:39:52 +00:00
|
|
|
# Overwrite properties that have been set in the config file.
|
2017-01-28 20:29:51 +00:00
|
|
|
attr.update(get_overrides(self.hass, CORE_DOMAIN, self.entity_id))
|
2015-04-15 02:57:32 +00:00
|
|
|
|
2016-03-07 22:39:52 +00:00
|
|
|
# Remove hidden property if false so it won't show up.
|
2015-04-25 18:47:15 +00:00
|
|
|
if not attr.get(ATTR_HIDDEN, True):
|
|
|
|
attr.pop(ATTR_HIDDEN)
|
2015-04-25 04:39:35 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# Convert temperature if we detect one
|
2016-07-31 20:24:49 +00:00
|
|
|
try:
|
|
|
|
unit_of_measure = attr.get(ATTR_UNIT_OF_MEASUREMENT)
|
2016-11-23 01:38:04 +00:00
|
|
|
units = self.hass.config.units
|
|
|
|
if (unit_of_measure in (TEMP_CELSIUS, TEMP_FAHRENHEIT) and
|
|
|
|
unit_of_measure != units.temperature_unit):
|
|
|
|
prec = len(state) - state.index('.') - 1 if '.' in state else 0
|
|
|
|
temp = units.temperature(float(state), unit_of_measure)
|
|
|
|
state = str(round(temp) if prec == 0 else round(temp, prec))
|
2016-08-05 05:37:30 +00:00
|
|
|
attr[ATTR_UNIT_OF_MEASUREMENT] = units.temperature_unit
|
2016-07-31 20:24:49 +00:00
|
|
|
except ValueError:
|
|
|
|
# Could not convert state to float
|
|
|
|
pass
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-09-30 19:57:24 +00:00
|
|
|
self.hass.states.async_set(
|
2016-06-26 07:33:23 +00:00
|
|
|
self.entity_id, state, attr, self.force_update)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-11-16 16:26:29 +00:00
|
|
|
def schedule_update_ha_state(self, force_refresh=False):
|
2017-01-23 21:25:38 +00:00
|
|
|
"""Schedule a update ha state change task.
|
2016-11-16 05:06:50 +00:00
|
|
|
|
|
|
|
That is only needed on executor to not block.
|
|
|
|
"""
|
|
|
|
# We're already in a thread, do the force refresh here.
|
|
|
|
if force_refresh and not hasattr(self, 'async_update'):
|
|
|
|
self.update()
|
|
|
|
force_refresh = False
|
|
|
|
|
|
|
|
self.hass.add_job(self.async_update_ha_state(force_refresh))
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
def remove(self) -> None:
|
|
|
|
"""Remove entitiy from HASS."""
|
2016-10-16 16:35:46 +00:00
|
|
|
run_coroutine_threadsafe(
|
|
|
|
self.async_remove(), self.hass.loop
|
|
|
|
).result()
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_remove(self) -> None:
|
2017-01-23 21:25:38 +00:00
|
|
|
"""Remove entity from async HASS.
|
2016-10-16 16:35:46 +00:00
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
|
|
|
self.hass.states.async_remove(self.entity_id)
|
2016-09-04 15:15:52 +00:00
|
|
|
|
2016-02-24 06:41:24 +00:00
|
|
|
def _attr_setter(self, name, typ, attr, attrs):
|
|
|
|
"""Helper method to populate attributes based on properties."""
|
|
|
|
if attr in attrs:
|
|
|
|
return
|
|
|
|
|
|
|
|
value = getattr(self, name)
|
|
|
|
|
|
|
|
if not value:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
attrs[attr] = typ(value)
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
pass
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
def __eq__(self, other):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the comparison."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return (isinstance(other, Entity) and
|
|
|
|
other.unique_id == self.unique_id)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the representation."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return "<Entity {}: {}>".format(self.name, self.state)
|
|
|
|
|
|
|
|
|
|
|
|
class ToggleEntity(Entity):
|
2016-03-25 19:35:38 +00:00
|
|
|
"""An abstract class for entities that can be turned on and off."""
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# pylint: disable=no-self-use
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def state(self) -> str:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the state."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return STATE_ON if self.is_on else STATE_OFF
|
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def is_on(self) -> bool:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return True if entity is on."""
|
2016-06-25 04:27:40 +00:00
|
|
|
raise NotImplementedError()
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def turn_on(self, **kwargs) -> None:
|
2016-11-04 01:32:14 +00:00
|
|
|
"""Turn the entity on."""
|
2016-11-16 05:06:50 +00:00
|
|
|
raise NotImplementedError()
|
2016-11-04 01:32:14 +00:00
|
|
|
|
|
|
|
def async_turn_on(self, **kwargs):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Turn the entity on.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self.hass.loop.run_in_executor(
|
2016-11-16 05:06:50 +00:00
|
|
|
None, ft.partial(self.turn_on, **kwargs))
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def turn_off(self, **kwargs) -> None:
|
2016-11-04 01:32:14 +00:00
|
|
|
"""Turn the entity off."""
|
2016-11-16 05:06:50 +00:00
|
|
|
raise NotImplementedError()
|
2016-11-04 01:32:14 +00:00
|
|
|
|
|
|
|
def async_turn_off(self, **kwargs):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Turn the entity off.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self.hass.loop.run_in_executor(
|
2016-11-16 05:06:50 +00:00
|
|
|
None, ft.partial(self.turn_off, **kwargs))
|
2016-01-16 15:45:05 +00:00
|
|
|
|
2016-11-04 01:32:14 +00:00
|
|
|
def toggle(self) -> None:
|
|
|
|
"""Toggle the entity."""
|
|
|
|
if self.is_on:
|
|
|
|
self.turn_off()
|
|
|
|
else:
|
|
|
|
self.turn_on()
|
|
|
|
|
|
|
|
def async_toggle(self):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Toggle the entity.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
2016-01-17 21:59:22 +00:00
|
|
|
if self.is_on:
|
2016-12-26 13:10:23 +00:00
|
|
|
return self.async_turn_off()
|
2016-01-16 15:45:05 +00:00
|
|
|
else:
|
2016-12-26 13:10:23 +00:00
|
|
|
return self.async_turn_on()
|