Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	homeassistant/components/light/zwave.py
#	homeassistant/components/switch/zwave.py
This commit is contained in:
Stefan Jonasson 2016-01-26 20:31:42 +01:00
commit 5b6371ecda
33 changed files with 1306 additions and 245 deletions

View File

@ -78,6 +78,7 @@ omit =
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py

View File

@ -29,9 +29,13 @@ import time
import logging
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
import homeassistant.loader as loader
from homeassistant.helpers import validate_config
from homeassistant.helpers.event_decorators import \
track_state_change, track_time_change
from homeassistant.helpers.service import service
import homeassistant.components as core
from homeassistant.components import device_tracker
from homeassistant.components import light
# The domain of your component. Should be equal to the name of your component
DOMAIN = "example"
@ -39,11 +43,14 @@ DOMAIN = "example"
# List of component names (string) your component depends upon
# We depend on group because group will be loaded after all the components that
# initialize devices have been setup.
DEPENDENCIES = ['group']
DEPENDENCIES = ['group', 'device_tracker', 'light']
# Configuration key for the entity id we are targetting
CONF_TARGET = 'target'
# Variable for storing configuration parameters
TARGET_ID = None
# Name of the service that we expose
SERVICE_FLASH = 'flash'
@ -53,84 +60,89 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Setup example component. """
global TARGET_ID
# Validate that all required config options are given
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
return False
target_id = config[DOMAIN][CONF_TARGET]
TARGET_ID = config[DOMAIN][CONF_TARGET]
# Validate that the target entity id exists
if hass.states.get(target_id) is None:
_LOGGER.error("Target entity id %s does not exist", target_id)
if hass.states.get(TARGET_ID) is None:
_LOGGER.error("Target entity id %s does not exist",
TARGET_ID)
# Tell the bootstrapper that we failed to initialize
# Tell the bootstrapper that we failed to initialize and clear the
# stored target id so our functions don't run.
TARGET_ID = None
return False
# We will use the component helper methods to check the states.
device_tracker = loader.get_component('device_tracker')
light = loader.get_component('light')
def track_devices(entity_id, old_state, new_state):
""" Called when the group.all devices change state. """
# If anyone comes home and the core is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, target_id):
core.turn_on(hass, target_id)
# If all people leave the house and the core is on, turn it off
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, target_id):
core.turn_off(hass, target_id)
# Register our track_devices method to receive state changes of the
# all tracked devices group.
hass.states.track_change(
device_tracker.ENTITY_ID_ALL_DEVICES, track_devices)
def wake_up(now):
""" Turn it on in the morning if there are people home and
it is not already on. """
if device_tracker.is_on(hass) and not core.is_on(hass, target_id):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, target_id)
# Register our wake_up service to be called at 7AM in the morning
hass.track_time_change(wake_up, hour=7, minute=0, second=0)
def all_lights_off(entity_id, old_state, new_state):
""" If all lights turn off, turn off. """
if core.is_on(hass, target_id):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, target_id)
# Register our all_lights_off method to be called when all lights turn off
hass.states.track_change(
light.ENTITY_ID_ALL_LIGHTS, all_lights_off, STATE_ON, STATE_OFF)
def flash_service(call):
""" Service that will turn the target off for 10 seconds
if on and vice versa. """
if core.is_on(hass, target_id):
core.turn_off(hass, target_id)
time.sleep(10)
core.turn_on(hass, target_id)
else:
core.turn_on(hass, target_id)
time.sleep(10)
core.turn_off(hass, target_id)
# Register our service with HASS.
hass.services.register(DOMAIN, SERVICE_FLASH, flash_service)
# Tells the bootstrapper that the component was successfully initialized
# Tell the bootstrapper that we initialized successfully
return True
@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES)
def track_devices(hass, entity_id, old_state, new_state):
""" Called when the group.all devices change state. """
# If the target id is not set, return
if not TARGET_ID:
return
# If anyone comes home and the entity is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID):
core.turn_on(hass, TARGET_ID)
# If all people leave the house and the entity is on, turn it off
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
@track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now):
"""
Turn it on in the morning (7 AM) if there are people home and
it is not already on.
"""
if not TARGET_ID:
return
if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, TARGET_ID)
@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF)
def all_lights_off(hass, entity_id, old_state, new_state):
""" If all lights turn off, turn off. """
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, TARGET_ID)
@service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call):
"""
Service that will turn the target off for 10 seconds if on and vice versa.
"""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
time.sleep(10)
core.turn_on(hass, TARGET_ID)
else:
core.turn_on(hass, TARGET_ID)
time.sleep(10)
core.turn_off(hass, TARGET_ID)

View File

@ -24,6 +24,7 @@ import homeassistant.config as config_util
import homeassistant.loader as loader
import homeassistant.components as core_components
import homeassistant.components.group as group
from homeassistant.helpers import event_decorators, service
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
__version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
@ -199,6 +200,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
_LOGGER.info('Home Assistant core initialized')
# give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Setup the components
for domain in loader.load_order_components(components):
_setup_component(hass, domain, config)

View File

@ -10,7 +10,7 @@ import logging
from datetime import timedelta
from homeassistant.components import sun
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.event import track_sunrise, track_sunset
import homeassistant.util.dt as dt_util
DEPENDENCIES = ['sun']
@ -47,9 +47,9 @@ def trigger(hass, config, action):
# Do something to call action
if event == EVENT_SUNRISE:
trigger_sunrise(hass, action, offset)
track_sunrise(hass, action, offset)
else:
trigger_sunset(hass, action, offset)
track_sunset(hass, action, offset)
return True
@ -125,44 +125,6 @@ def if_action(hass, config):
return time_if
def trigger_sunrise(hass, action, offset):
""" Trigger action at next sun rise. """
def next_rise():
""" Returns next sunrise. """
next_time = sun.next_rising_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunrise_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action()
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
def trigger_sunset(hass, action, offset):
""" Trigger action at next sun set. """
def next_set():
""" Returns next sunrise. """
next_time = sun.next_setting_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunset_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action()
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
def _parse_offset(raw_offset):
if raw_offset is None:
return timedelta(0)

View File

@ -62,10 +62,16 @@ def setup(hass, config):
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
group.setup_group(hass, 'living room', [lights[2], lights[1], switches[0],
media_players[1]])
group.setup_group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]])
group.Group(hass, 'living room', [
lights[2], lights[1], switches[0], media_players[1],
'scene.romantic_lights'])
group.Group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]])
group.Group(hass, 'Rooms', [
'group.living_room', 'group.bedroom',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window',
], view=True)
# Setup scripts
bootstrap.setup_component(

View File

@ -229,7 +229,7 @@ class DeviceTracker(object):
""" Initializes group for all tracked devices. """
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.setup_group(
self.group = group.Group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now):

View File

@ -19,7 +19,7 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3.1']
REQUIREMENTS = ['pynetgear==0.3.2']
def get_scanner(hass, config):

View File

@ -21,7 +21,9 @@ _LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo', '/devTemplate', '/states']
'/devEvent', '/devInfo', '/devTemplate',
re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'),
]
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "1003c31441ec44b3db84b49980f736a7"
VERSION = "d3490eb2c77bfe127e09c8c1ad148580"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 2ecd6a818443780dc5d0d981996d165218b2b094
Subproject commit 8524390ae5b6a28f9bf3472cd9643765de7a3425

View File

@ -13,13 +13,18 @@ from homeassistant.helpers.entity import (
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_NAME, CONF_ICON)
DOMAIN = "group"
DOMAIN = 'group'
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_AUTO = "auto"
CONF_ENTITIES = 'entities'
CONF_VIEW = 'view'
ATTR_AUTO = 'auto'
ATTR_ORDER = 'order'
ATTR_VIEW = 'view'
# List of ON/OFF state tuples for groupable states
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
@ -103,10 +108,20 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items():
for object_id, conf in config.get(DOMAIN, {}).items():
if not isinstance(conf, dict):
conf = {CONF_ENTITIES: conf}
name = conf.get(CONF_NAME, object_id)
entity_ids = conf.get(CONF_ENTITIES)
icon = conf.get(CONF_ICON)
view = conf.get(CONF_VIEW)
if isinstance(entity_ids, str):
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
setup_group(hass, name, entity_ids)
Group(hass, name, entity_ids, icon=icon, view=view,
object_id=object_id)
return True
@ -114,14 +129,19 @@ def setup(hass, config):
class Group(Entity):
""" Tracks a group of entity ids. """
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(self, hass, name, entity_ids=None, user_defined=True):
def __init__(self, hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, object_id=None):
self.hass = hass
self._name = name
self._state = STATE_UNKNOWN
self.user_defined = user_defined
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)
self._order = len(hass.states.entity_ids(DOMAIN))
self._user_defined = user_defined
self._icon = icon
self._view = view
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, object_id or name, hass=hass)
self.tracking = []
self.group_on = None
self.group_off = None
@ -143,12 +163,25 @@ class Group(Entity):
def state(self):
return self._state
@property
def icon(self):
return self._icon
@property
def hidden(self):
return not self._user_defined or self._view
@property
def state_attributes(self):
return {
data = {
ATTR_ENTITY_ID: self.tracking,
ATTR_AUTO: not self.user_defined,
ATTR_ORDER: self._order,
}
if not self._user_defined:
data[ATTR_AUTO] = True
if self._view:
data[ATTR_VIEW] = True
return data
def update_tracked_entity_ids(self, entity_ids):
""" Update the tracked entity IDs. """
@ -219,10 +252,3 @@ class Group(Entity):
for ent_id in self.tracking
if tr_state.entity_id != ent_id):
self._state = group_off
def setup_group(hass, name, entity_ids, user_defined=True):
""" Sets up a group state that is the combined state of
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
return Group(hass, name, entity_ids, user_defined)

View File

@ -0,0 +1,271 @@
"""
homeassistant.components.light.lifx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LIFX platform that implements lights
Configuration:
light:
# platform name
platform: lifx
# optional server address
# only needed if using more than one network interface
# (omit if you are unsure)
server: 192.168.1.3
# optional broadcast address, set to reach all LIFX bulbs
# (omit if you are unsure)
broadcast: 192.168.1.255
"""
# pylint: disable=missing-docstring
import logging
import colorsys
from homeassistant.helpers.event import track_time_change
from homeassistant.components.light import \
(Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.0']
DEPENDENCIES = []
CONF_SERVER = "server" # server address configuration item
CONF_BROADCAST = "broadcast" # broadcast address configuration item
SHORT_MAX = 65535 # short int maximum
BYTE_MAX = 255 # byte maximum
TEMP_MIN = 2500 # lifx minimum temperature
TEMP_MAX = 9000 # lifx maximum temperature
TEMP_MIN_HASS = 154 # home assistant minimum temperature
TEMP_MAX_HASS = 500 # home assistant maximum temperature
class LIFX():
def __init__(self, add_devices_callback,
server_addr=None, broadcast_addr=None):
import liffylights
self._devices = []
self._add_devices_callback = add_devices_callback
self._liffylights = liffylights.LiffyLights(
self.on_device,
self.on_power,
self.on_color,
server_addr,
broadcast_addr)
def find_bulb(self, ipaddr):
bulb = None
for device in self._devices:
if device.ipaddr == ipaddr:
bulb = device
break
return bulb
# pylint: disable=too-many-arguments
def on_device(self, ipaddr, name, power, hue, sat, bri, kel):
bulb = self.find_bulb(ipaddr)
if bulb is None:
bulb = LIFXLight(self._liffylights, ipaddr, name,
power, hue, sat, bri, kel)
self._devices.append(bulb)
self._add_devices_callback([bulb])
# pylint: disable=too-many-arguments
def on_color(self, ipaddr, hue, sat, bri, kel):
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
def on_power(self, ipaddr, power):
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_power(power)
bulb.update_ha_state()
# pylint: disable=unused-argument
def poll(self, now):
self.probe()
def probe(self, address=None):
self._liffylights.probe(address)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Set up platform. """
server_addr = config.get(CONF_SERVER, None)
broadcast_addr = config.get(CONF_BROADCAST, None)
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
# register our poll service
track_time_change(hass, lifx_library.poll, second=10)
lifx_library.probe()
def convert_rgb_to_hsv(rgb):
""" Convert HASS RGB values to HSV values. """
red, green, blue = [_ / BYTE_MAX for _ in rgb]
hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue)
return [int(hue * SHORT_MAX),
int(saturation * SHORT_MAX),
int(brightness * SHORT_MAX)]
# pylint: disable=too-many-instance-attributes
class LIFXLight(Light):
""" Provides LIFX light. """
# pylint: disable=too-many-arguments
def __init__(self, liffy, ipaddr, name, power, hue,
saturation, brightness, kelvin):
_LOGGER.debug("LIFXLight: %s %s",
ipaddr, name)
self._liffylights = liffy
self._ip = ipaddr
self.set_name(name)
self.set_power(power)
self.set_color(hue, saturation, brightness, kelvin)
@property
def should_poll(self):
""" No polling needed for LIFX light. """
return False
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def ipaddr(self):
""" Returns the ip of the device. """
return self._ip
@property
def rgb_color(self):
""" Returns RGB value. """
_LOGGER.debug("rgb_color: [%d %d %d]",
self._rgb[0], self._rgb[1], self._rgb[2])
return self._rgb
@property
def brightness(self):
""" Returns brightness of this light between 0..255. """
brightness = int(self._bri / (BYTE_MAX + 1))
_LOGGER.debug("brightness: %d",
brightness)
return brightness
@property
def color_temp(self):
""" Returns color temperature. """
temperature = int(TEMP_MIN_HASS + (TEMP_MAX_HASS - TEMP_MIN_HASS) *
(self._kel - TEMP_MIN) / (TEMP_MAX - TEMP_MIN))
_LOGGER.debug("color_temp: %d",
temperature)
return temperature
@property
def is_on(self):
""" True if device is on. """
_LOGGER.debug("is_on: %d",
self._power)
return self._power != 0
def turn_on(self, **kwargs):
""" Turn the device on. """
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
else:
fade = 0
if ATTR_RGB_COLOR in kwargs:
hue, saturation, brightness = \
convert_rgb_to_hsv(kwargs[ATTR_RGB_COLOR])
else:
hue = self._hue
saturation = self._sat
brightness = self._bri
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1)
else:
brightness = self._bri
if ATTR_COLOR_TEMP in kwargs:
kelvin = int(((TEMP_MAX - TEMP_MIN) *
(kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) /
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)
else:
kelvin = self._kel
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
self._ip, self._power,
hue, saturation, brightness, kelvin, fade)
if self._power == 0:
self._liffylights.set_power(self._ip, 65535, fade)
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, fade)
def turn_off(self, **kwargs):
""" Turn the device off. """
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
else:
fade = 0
_LOGGER.debug("turn_off: %s %d",
self._ip, fade)
self._liffylights.set_power(self._ip, 0, fade)
def set_name(self, name):
""" Set name. """
self._name = name
def set_power(self, power):
""" Set power state value. """
_LOGGER.debug("set_power: %d",
power)
self._power = (power != 0)
def set_color(self, hue, sat, bri, kel):
""" Set color state values. """
self._hue = hue
self._sat = sat
self._bri = bri
self._kel = kel
red, green, blue = colorsys.hsv_to_rgb(hue / SHORT_MAX,
sat / SHORT_MAX,
bri / SHORT_MAX)
red = int(red * BYTE_MAX)
green = int(green * BYTE_MAX)
blue = int(blue * BYTE_MAX)
_LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
hue, sat, bri, kel, red, green, blue)
self._rgb = [red, green, blue]

View File

@ -149,8 +149,8 @@ class RfxtrxLight(Light):
self._brightness = ((brightness + 4) * 100 // 255 - 1)
if hasattr(self, '_event') and self._event:
self._event.device.send_on(rfxtrx.RFXOBJECT.transport,
self._brightness)
self._event.device.send_dim(rfxtrx.RFXOBJECT.transport,
self._brightness)
self._brightness = (self._brightness * 255 // 100)
self._state = True

View File

@ -12,6 +12,7 @@ from threading import Timer
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS)
from homeassistant.util import slugify
import homeassistant.components.zwave as zwave
@ -91,13 +92,33 @@ class ZwaveDimmer(Light):
""" No polling needed for a light. """
return False
@property
def unique_id(self):
""" Returns a unique id. """
return "ZWAVE-{}-{}".format(self._node.node_id, self._value.object_id)
@property
def name(self):
""" Returns the name of the device if any. """
""" Returns the name of the device. """
name = self._node.name or "{} {}".format(
self._node.manufacturer_name, self._node.product_name)
return "{} {} {}".format(name, self._node.node_id, self._value.label)
return "{} {}".format(name, self._value.label)
@property
def entity_id(self):
""" Returns the entity_id of the device if any.
The entity_id contains node_id and value instance id
to not collide with other entity_ids"""
entity_id = "light.{}_{}".format(slugify(self.name),
self._node.node_id)
# Add the instance id if there is more than one instance for the value
if self._value.instance > 1:
return "{}_{}".format(entity_id, self._value.instance)
return entity_id
@property
def brightness(self):

View File

@ -202,9 +202,12 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" Image url of current playing media. """
if 'artwork_url' in self._status:
media_url = self._status['artwork_url']
else:
elif 'id' in self._status:
media_url = ('/music/{track_id}/cover.jpg').format(
track_id=self._status["id"])
track_id=self._status['id'])
else:
media_url = ('/music/current/cover.jpg?player={player}').format(
player=self._id)
base_url = 'http://{server}:{port}/'.format(
server=self._lms.host,

View File

@ -14,6 +14,7 @@ from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util
import homeassistant.components.zwave as zwave
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from homeassistant.const import (
ATTR_BATTERY_LEVEL, STATE_ON, STATE_OFF,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION)
@ -109,6 +110,21 @@ class ZWaveSensor(Entity):
return "{} {} {}".format(name, self._node.node_id, self._value.label)
@property
def entity_id(self):
""" Returns the entity_id of the device if any.
The entity_id contains node_id and value instance id
to not collide with other entity_ids"""
entity_id = "sensor.{}_{}".format(slugify(self.name),
self._node.node_id)
# Add the instance id if there is more than one instance for the value
if self._value.instance > 1:
return "{}_{}".format(entity_id, self._value.instance)
return entity_id
@property
def state(self):
""" Returns the state of the sensor. """

View File

@ -11,7 +11,7 @@ import logging
from homeassistant.components.switch import SwitchDevice
DEFAULT_NAME = "Orvibo S20 Switch"
REQUIREMENTS = ['orvibo==1.1.0']
REQUIREMENTS = ['orvibo==1.1.1']
_LOGGER = logging.getLogger(__name__)

View File

@ -9,6 +9,7 @@ Zwave platform that handles simple binary switches.
import homeassistant.components.zwave as zwave
from homeassistant.components.switch import SwitchDevice
from homeassistant.util import slugify
# pylint: disable=unused-argument
@ -55,13 +56,33 @@ class ZwaveSwitch(SwitchDevice):
""" No polling needed for a demo switch. """
return False
@property
def unique_id(self):
""" Returns a unique id. """
return "ZWAVE-{}-{}".format(self._node.node_id, self._value.object_id)
@property
def name(self):
""" Returns the name of the device if any. """
""" Returns the name of the device. """
name = self._node.name or "{} {}".format(
self._node.manufacturer_name, self._node.product_name)
return "{} {} {}".format(name, self._node.node_id, self._value.label)
return "{} {}".format(name, self._value.label)
@property
def entity_id(self):
""" Returns the entity_id of the device if any.
The entity_id contains node_id and value instance id
to not collide with other entity_ids"""
entity_id = "switch.{}_{}".format(slugify(self.name),
self._node.node_id)
# Add the instance id if there is more than one instance for the value
if self._value.instance > 1:
return "{}_{}".format(entity_id, self._value.instance)
return entity_id
@property
def is_on(self):

View File

@ -27,6 +27,7 @@ SCAN_INTERVAL = 60
SERVICE_SET_AWAY_MODE = "set_away_mode"
SERVICE_SET_TEMPERATURE = "set_temperature"
SERVICE_SET_FAN_MODE = "set_fan_mode"
STATE_HEAT = "heat"
STATE_COOL = "cool"
@ -34,6 +35,7 @@ STATE_IDLE = "idle"
ATTR_CURRENT_TEMPERATURE = "current_temperature"
ATTR_AWAY_MODE = "away_mode"
ATTR_FAN = "fan"
ATTR_MAX_TEMP = "max_temp"
ATTR_MIN_TEMP = "min_temp"
ATTR_TEMPERATURE_LOW = "target_temp_low"
@ -69,59 +71,100 @@ def set_temperature(hass, temperature, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, data)
def set_fan_mode(hass, fan_mode, entity_id=None):
""" Turn all or specified thermostat fan mode on. """
data = {
ATTR_FAN: fan_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
# pylint: disable=too-many-branches
def setup(hass, config):
""" Setup thermostats. """
component = EntityComponent(_LOGGER, DOMAIN, hass,
SCAN_INTERVAL, DISCOVERY_PLATFORMS)
component.setup(config)
def thermostat_service(service):
""" Handles calls to the services. """
# Convert the entity ids to valid light ids
target_thermostats = component.extract_from_service(service)
if service.service == SERVICE_SET_AWAY_MODE:
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
elif away_mode:
for thermostat in target_thermostats:
thermostat.turn_away_mode_on()
else:
for thermostat in target_thermostats:
thermostat.turn_away_mode_off()
elif service.service == SERVICE_SET_TEMPERATURE:
temperature = util.convert(
service.data.get(ATTR_TEMPERATURE), float)
if temperature is None:
return
for thermostat in target_thermostats:
thermostat.set_temperature(convert(
temperature, hass.config.temperature_unit,
thermostat.unit_of_measurement))
for thermostat in target_thermostats:
thermostat.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(
DOMAIN, SERVICE_SET_AWAY_MODE, thermostat_service,
descriptions.get(SERVICE_SET_AWAY_MODE))
def away_mode_set_service(service):
""" Set away mode on target thermostats """
target_thermostats = component.extract_from_service(service)
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
return
for thermostat in target_thermostats:
if away_mode:
thermostat.turn_away_mode_on()
else:
thermostat.turn_away_mode_off()
thermostat.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, thermostat_service,
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
descriptions.get(SERVICE_SET_AWAY_MODE))
def temperature_set_service(service):
""" Set temperature on the target thermostats """
target_thermostats = component.extract_from_service(service)
temperature = util.convert(
service.data.get(ATTR_TEMPERATURE), float)
if temperature is None:
return
for thermostat in target_thermostats:
thermostat.set_temperature(convert(
temperature, hass.config.temperature_unit,
thermostat.unit_of_measurement))
thermostat.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
descriptions.get(SERVICE_SET_TEMPERATURE))
def fan_mode_set_service(service):
""" Set fan mode on target thermostats """
target_thermostats = component.extract_from_service(service)
fan_mode = service.data.get(ATTR_FAN)
if fan_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_FAN_MODE, ATTR_FAN)
return
for thermostat in target_thermostats:
if fan_mode:
thermostat.turn_fan_on()
else:
thermostat.turn_fan_off()
thermostat.update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
descriptions.get(SERVICE_SET_FAN_MODE))
return True
@ -164,6 +207,10 @@ class ThermostatDevice(Entity):
if is_away is not None:
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
is_fan_on = self.is_fan_on
if is_fan_on is not None:
data[ATTR_FAN] = STATE_ON if is_fan_on else STATE_OFF
device_attr = self.device_state_attributes
if device_attr is not None:
@ -209,6 +256,14 @@ class ThermostatDevice(Entity):
"""
return None
@property
def is_fan_on(self):
"""
Returns if the fan is on
Return None if not available.
"""
return None
def set_temperate(self, temperature):
""" Set new target temperature. """
pass
@ -221,6 +276,14 @@ class ThermostatDevice(Entity):
""" Turns away mode off. """
pass
def turn_fan_on(self):
""" Turns fan on. """
pass
def turn_fan_off(self):
""" Turns fan off. """
pass
@property
def min_temp(self):
""" Return minimum temperature. """

View File

@ -66,7 +66,6 @@ class NestThermostat(ThermostatDevice):
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
"fan": self.device.fan,
"mode": self.device.mode
}
@ -143,6 +142,19 @@ class NestThermostat(ThermostatDevice):
""" Turns away off. """
self.structure.away = False
@property
def is_fan_on(self):
""" Returns whether the fan is on """
return self.device.fan
def turn_fan_on(self):
""" Turns fan on """
self.device.fan = True
def turn_fan_off(self):
""" Turns fan off """
self.device.fan = False
@property
def min_temp(self):
""" Identifies min_temp in Nest API or defaults if not available. """

View File

@ -22,3 +22,15 @@ set_temperature:
temperature:
description: New target temperature for thermostat
example: 25
set_fan_mode:
description: Turn fan on/off for a thermostat
fields:
entity_id:
description: Name(s) of entities to change
example: 'thermostat.nest'
fan:
description: New value of fan mode
example: true

View File

@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
DEPENDENCIES = ['alarm_control_panel']
REQUIREMENTS = ['vsure==0.4.5']
REQUIREMENTS = ['vsure==0.4.8']
_LOGGER = logging.getLogger(__name__)

View File

@ -10,6 +10,7 @@ MATCH_ALL = '*'
DEVICE_DEFAULT_NAME = "Unnamed Device"
# #### CONFIG ####
CONF_ICON = "icon"
CONF_LATITUDE = "latitude"
CONF_LONGITUDE = "longitude"
CONF_TEMPERATURE_UNIT = "temperature_unit"

View File

@ -1,6 +1,7 @@
"""
Helpers for listening to events
"""
from datetime import timedelta
import functools as ft
from ..util import dt as dt_util
@ -95,6 +96,54 @@ def track_point_in_utc_time(hass, action, point_in_time):
return point_in_time_listener
def track_sunrise(hass, action, offset=None):
"""
Adds a listener that will fire a specified offset from sunrise daily.
"""
from homeassistant.components import sun
offset = offset or timedelta()
def next_rise():
""" Returns next sunrise. """
next_time = sun.next_rising_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunrise_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action()
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
def track_sunset(hass, action, offset=None):
"""
Adds a listener that will fire a specified offset from sunset daily.
"""
from homeassistant.components import sun
offset = offset or timedelta()
def next_set():
""" Returns next sunrise. """
next_time = sun.next_setting_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunset_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action()
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
# pylint: disable=too-many-arguments
def track_utc_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None, local=False):

View File

@ -0,0 +1,76 @@
""" Event Decorators for custom components """
import functools
from homeassistant.helpers import event
HASS = None
def track_state_change(entity_ids, from_state=None, to_state=None):
""" Decorator factory to track state changes for entity id """
def track_state_change_decorator(action):
""" Decorator to track state changes """
event.track_state_change(HASS, entity_ids,
functools.partial(action, HASS),
from_state, to_state)
return action
return track_state_change_decorator
def track_sunrise(offset=None):
""" Decorator factory to track sunrise events """
def track_sunrise_decorator(action):
""" Decorator to track sunrise events """
event.track_sunrise(HASS,
functools.partial(action, HASS),
offset)
return action
return track_sunrise_decorator
def track_sunset(offset=None):
""" Decorator factory to track sunset events """
def track_sunset_decorator(action):
""" Decorator to track sunset events """
event.track_sunset(HASS,
functools.partial(action, HASS),
offset)
return action
return track_sunset_decorator
# pylint: disable=too-many-arguments
def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
second=None):
""" Decorator factory to track time changes """
def track_time_change_decorator(action):
""" Decorator to track time changes """
event.track_time_change(HASS,
functools.partial(action, HASS),
year, month, day, hour, minute, second)
return action
return track_time_change_decorator
# pylint: disable=too-many-arguments
def track_utc_time_change(year=None, month=None, day=None, hour=None,
minute=None, second=None):
""" Decorator factory to track time changes """
def track_utc_time_change_decorator(action):
""" Decorator to track time changes """
event.track_utc_time_change(HASS,
functools.partial(action, HASS),
year, month, day, hour, minute, second)
return action
return track_utc_time_change_decorator

View File

@ -1,10 +1,13 @@
"""Service calling related helpers."""
import functools
import logging
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.entity import split_entity_id
from homeassistant.loader import get_component
HASS = None
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
@ -12,6 +15,18 @@ CONF_SERVICE_DATA = 'data'
_LOGGER = logging.getLogger(__name__)
def service(domain, service_name):
""" Decorator factory to register a service """
def register_service_decorator(action):
""" Decorator to register a service """
HASS.services.register(domain, service_name,
functools.partial(action, HASS))
return action
return register_service_decorator
def call_from_config(hass, config, blocking=False):
"""Call a service based on a config hash."""
if not isinstance(config, dict) or CONF_SERVICE not in config:
@ -19,7 +34,7 @@ def call_from_config(hass, config, blocking=False):
return
try:
domain, service = split_entity_id(config[CONF_SERVICE])
domain, service_name = split_entity_id(config[CONF_SERVICE])
except ValueError:
_LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE])
return
@ -41,21 +56,21 @@ def call_from_config(hass, config, blocking=False):
elif entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(domain, service, service_data, blocking)
hass.services.call(domain, service_name, service_data, blocking)
def extract_entity_ids(hass, service):
def extract_entity_ids(hass, service_call):
"""
Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents.
"""
if not (service.data and ATTR_ENTITY_ID in service.data):
if not (service_call.data and ATTR_ENTITY_ID in service_call.data):
return []
group = get_component('group')
# Entity ID attr can be a list or a string
service_ent_id = service.data[ATTR_ENTITY_ID]
service_ent_id = service_call.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, str):
return group.expand_entity_ids(hass, [service_ent_id])

View File

@ -19,7 +19,7 @@ fuzzywuzzy==0.8.0
pyicloud==0.7.2
# homeassistant.components.device_tracker.netgear
pynetgear==0.3.1
pynetgear==0.3.2
# homeassistant.components.device_tracker.nmap_tracker
python-nmap==0.4.3
@ -54,6 +54,9 @@ blinkstick==1.1.7
# homeassistant.components.light.hue
phue==0.8
# homeassistant.components.light.lifx
liffylights==0.9.0
# homeassistant.components.light.limitlessled
limitlessled==1.0.0
@ -190,7 +193,7 @@ https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f
hikvision==0.4
# homeassistant.components.switch.orvibo
orvibo==1.1.0
orvibo==1.1.1
# homeassistant.components.switch.wemo
pywemo==0.3.8
@ -211,7 +214,7 @@ proliphix==0.1.0
radiotherm==1.2
# homeassistant.components.verisure
vsure==0.4.5
vsure==0.4.8
# homeassistant.components.zwave
pydispatcher==2.0.5

View File

@ -9,7 +9,8 @@ import unittest
import logging
import homeassistant.core as ha
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN)
import homeassistant.components.group as group
@ -39,7 +40,7 @@ class TestComponentsGroup(unittest.TestCase):
def test_setup_group_with_mixed_groupable_states(self):
""" Try to setup a group with mixed groupable states """
self.hass.states.set('device_tracker.Paulus', STATE_HOME)
group.setup_group(
group.Group(
self.hass, 'person_and_light',
['light.Bowl', 'device_tracker.Paulus'])
@ -50,7 +51,7 @@ class TestComponentsGroup(unittest.TestCase):
def test_setup_group_with_a_non_existing_state(self):
""" Try to setup a group with a non existing state """
grp = group.setup_group(
grp = group.Group(
self.hass, 'light_and_nothing',
['light.Bowl', 'non.existing'])
@ -60,7 +61,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass.states.set('cast.living_room', "Plex")
self.hass.states.set('cast.bedroom', "Netflix")
grp = group.setup_group(
grp = group.Group(
self.hass, 'chromecasts',
['cast.living_room', 'cast.bedroom'])
@ -68,7 +69,7 @@ class TestComponentsGroup(unittest.TestCase):
def test_setup_empty_group(self):
""" Try to setup an empty group. """
grp = group.setup_group(self.hass, 'nothing', [])
grp = group.Group(self.hass, 'nothing', [])
self.assertEqual(STATE_UNKNOWN, grp.state)
@ -80,7 +81,7 @@ class TestComponentsGroup(unittest.TestCase):
group_state = self.hass.states.get(self.group_entity_id)
self.assertEqual(STATE_ON, group_state.state)
self.assertTrue(group_state.attributes[group.ATTR_AUTO])
self.assertTrue(group_state.attributes.get(group.ATTR_AUTO))
def test_group_turns_off_if_all_off(self):
"""
@ -199,17 +200,35 @@ class TestComponentsGroup(unittest.TestCase):
self.hass,
{
group.DOMAIN: {
'second_group': 'light.Bowl, ' + self.group_entity_id
'second_group': {
'entities': 'light.Bowl, ' + self.group_entity_id,
'icon': 'mdi:work',
'view': True,
},
'test_group': 'hello.world,sensor.happy',
}
}))
group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('second_group'))
self.assertEqual(STATE_ON, group_state.state)
self.assertEqual(set((self.group_entity_id, 'light.bowl')),
set(group_state.attributes['entity_id']))
self.assertFalse(group_state.attributes[group.ATTR_AUTO])
self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO))
self.assertEqual('mdi:work',
group_state.attributes.get(ATTR_ICON))
self.assertTrue(group_state.attributes.get(group.ATTR_VIEW))
self.assertTrue(group_state.attributes.get(ATTR_HIDDEN))
group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('test_group'))
self.assertEqual(STATE_UNKNOWN, group_state.state)
self.assertEqual(set(('sensor.happy', 'hello.world')),
set(group_state.attributes['entity_id']))
self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO))
self.assertIsNone(group_state.attributes.get(ATTR_ICON))
self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW))
self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
def test_groups_get_unique_names(self):
""" Two groups with same name should both have a unique entity id. """

View File

@ -1,8 +1,8 @@
"""
tests.components.automation.test_location
±±±~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.test_zone
±±±~~~~~~~~~~~~~~~~~~~~~~~
Tests location automation.
Tests zone component.
"""
import unittest
@ -11,8 +11,8 @@ from homeassistant.components import zone
from tests.common import get_test_home_assistant
class TestAutomationZone(unittest.TestCase):
""" Test the event automation. """
class TestComponentZone(unittest.TestCase):
""" Test the zone component. """
def setUp(self): # pylint: disable=invalid-name
self.hass = get_test_home_assistant()

View File

@ -9,8 +9,11 @@ Tests event helpers.
import unittest
from datetime import datetime
from astral import Astral
import homeassistant.core as ha
from homeassistant.helpers.event import *
from homeassistant.components import sun
class TestEventHelpers(unittest.TestCase):
@ -121,6 +124,98 @@ class TestEventHelpers(unittest.TestCase):
self.assertEqual(1, len(specific_runs))
self.assertEqual(3, len(wildcard_runs))
def test_track_sunrise(self):
""" Test track sunrise """
latitude = 32.87336
longitude = 117.22743
# setup sun component
self.hass.config.latitude = latitude
self.hass.config.longitude = longitude
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
# get next sunrise/sunset
astral = Astral()
utc_now = dt_util.utcnow()
mod = -1
while True:
next_rising = (astral.sunrise_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_rising > utc_now:
break
mod += 1
# track sunrise
runs = []
track_sunrise(self.hass, lambda: runs.append(1))
offset_runs = []
offset = timedelta(minutes=30)
track_sunrise(self.hass, lambda: offset_runs.append(1), offset)
# run tests
self._send_time_changed(next_rising - offset)
self.hass.pool.block_till_done()
self.assertEqual(0, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_rising)
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_rising + offset)
self.hass.pool.block_till_done()
self.assertEqual(2, len(runs))
self.assertEqual(1, len(offset_runs))
def test_track_sunset(self):
""" Test track sunset """
latitude = 32.87336
longitude = 117.22743
# setup sun component
self.hass.config.latitude = latitude
self.hass.config.longitude = longitude
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
# get next sunrise/sunset
astral = Astral()
utc_now = dt_util.utcnow()
mod = -1
while True:
next_setting = (astral.sunset_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_setting > utc_now:
break
mod += 1
# track sunset
runs = []
track_sunset(self.hass, lambda: runs.append(1))
offset_runs = []
offset = timedelta(minutes=30)
track_sunset(self.hass, lambda: offset_runs.append(1), offset)
# run tests
self._send_time_changed(next_setting - offset)
self.hass.pool.block_till_done()
self.assertEqual(0, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_setting)
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_setting + offset)
self.hass.pool.block_till_done()
self.assertEqual(2, len(runs))
self.assertEqual(1, len(offset_runs))
def _send_time_changed(self, now):
""" Send a time changed event. """
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})

View File

@ -0,0 +1,200 @@
"""
tests.helpers.test_event_decorators
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests event decorator helpers.
"""
# pylint: disable=protected-access,too-many-public-methods
# pylint: disable=too-few-public-methods
import unittest
from datetime import datetime, timedelta
from astral import Astral
import homeassistant.core as ha
import homeassistant.util.dt as dt_util
from homeassistant.helpers import event_decorators
from homeassistant.helpers.event_decorators import (
track_time_change, track_utc_time_change, track_state_change,
track_sunrise, track_sunset)
from homeassistant.components import sun
class TestEventDecoratorHelpers(unittest.TestCase):
"""
Tests the Home Assistant event helpers.
"""
def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """
self.hass = ha.HomeAssistant()
self.hass.states.set("light.Bowl", "on")
self.hass.states.set("switch.AC", "off")
event_decorators.HASS = self.hass
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_track_sunrise(self):
""" Test track sunrise decorator """
latitude = 32.87336
longitude = 117.22743
# setup sun component
self.hass.config.latitude = latitude
self.hass.config.longitude = longitude
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
# get next sunrise/sunset
astral = Astral()
utc_now = dt_util.utcnow()
mod = -1
while True:
next_rising = (astral.sunrise_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_rising > utc_now:
break
mod += 1
# use decorator
runs = []
decor = track_sunrise()
decor(lambda x: runs.append(1))
offset_runs = []
offset = timedelta(minutes=30)
decor = track_sunrise(offset)
decor(lambda x: offset_runs.append(1))
# run tests
self._send_time_changed(next_rising - offset)
self.hass.pool.block_till_done()
self.assertEqual(0, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_rising)
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_rising + offset)
self.hass.pool.block_till_done()
self.assertEqual(2, len(runs))
self.assertEqual(1, len(offset_runs))
def test_track_sunset(self):
""" Test track sunset decorator """
latitude = 32.87336
longitude = 117.22743
# setup sun component
self.hass.config.latitude = latitude
self.hass.config.longitude = longitude
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
# get next sunrise/sunset
astral = Astral()
utc_now = dt_util.utcnow()
mod = -1
while True:
next_setting = (astral.sunset_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_setting > utc_now:
break
mod += 1
# use decorator
runs = []
decor = track_sunset()
decor(lambda x: runs.append(1))
offset_runs = []
offset = timedelta(minutes=30)
decor = track_sunset(offset)
decor(lambda x: offset_runs.append(1))
# run tests
self._send_time_changed(next_setting - offset)
self.hass.pool.block_till_done()
self.assertEqual(0, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_setting)
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
self.assertEqual(0, len(offset_runs))
self._send_time_changed(next_setting + offset)
self.hass.pool.block_till_done()
self.assertEqual(2, len(runs))
self.assertEqual(1, len(offset_runs))
def test_track_time_change(self):
""" Test tracking time change. """
wildcard_runs = []
specific_runs = []
decor = track_time_change()
decor(lambda x, y: wildcard_runs.append(1))
decor = track_utc_time_change(second=[0, 30])
decor(lambda x, y: specific_runs.append(1))
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(specific_runs))
self.assertEqual(1, len(wildcard_runs))
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15))
self.hass.pool.block_till_done()
self.assertEqual(1, len(specific_runs))
self.assertEqual(2, len(wildcard_runs))
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30))
self.hass.pool.block_till_done()
self.assertEqual(2, len(specific_runs))
self.assertEqual(3, len(wildcard_runs))
def test_track_state_change(self):
""" Test track_state_change. """
# 2 lists to track how often our callbacks get called
specific_runs = []
wildcard_runs = []
decor = track_state_change('light.Bowl', 'on', 'off')
decor(lambda a, b, c, d: specific_runs.append(1))
decor = track_state_change('light.Bowl', ha.MATCH_ALL, ha.MATCH_ALL)
decor(lambda a, b, c, d: wildcard_runs.append(1))
# Set same state should not trigger a state change/listener
self.hass.states.set('light.Bowl', 'on')
self.hass.pool.block_till_done()
self.assertEqual(0, len(specific_runs))
self.assertEqual(0, len(wildcard_runs))
# State change off -> on
self.hass.states.set('light.Bowl', 'off')
self.hass.pool.block_till_done()
self.assertEqual(1, len(specific_runs))
self.assertEqual(1, len(wildcard_runs))
# State change off -> off
self.hass.states.set('light.Bowl', 'off', {"some_attr": 1})
self.hass.pool.block_till_done()
self.assertEqual(1, len(specific_runs))
self.assertEqual(2, len(wildcard_runs))
# State change off -> on
self.hass.states.set('light.Bowl', 'on')
self.hass.pool.block_till_done()
self.assertEqual(1, len(specific_runs))
self.assertEqual(3, len(wildcard_runs))
def _send_time_changed(self, now):
""" Send a time changed event. """
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})

View File

@ -24,10 +24,23 @@ class TestServiceHelpers(unittest.TestCase):
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test_domain', 'test_service')
service.HASS = self.hass
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_service(self):
""" Test service registration decorator. """
runs = []
decor = service.service('test', 'test')
decor(lambda x, y: runs.append(1))
self.hass.services.call('test', 'test')
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
def test_split_entity_string(self):
service.call_from_config(self.hass, {
'service': 'test_domain.test_service',
@ -74,7 +87,7 @@ class TestServiceHelpers(unittest.TestCase):
self.hass.states.set('light.Ceiling', STATE_OFF)
self.hass.states.set('light.Kitchen', STATE_OFF)
loader.get_component('group').setup_group(
loader.get_component('group').Group(
self.hass, 'test', ['light.Ceiling', 'light.Kitchen'])
call = ha.ServiceCall('light', 'turn_on',