Merge pull request #1594 from balloob/dev

0.16
This commit is contained in:
Paulus Schoutsen 2016-03-26 01:02:12 -07:00
commit 763a9ce8c6
108 changed files with 3398 additions and 1965 deletions

View File

@ -26,11 +26,13 @@ omit =
homeassistant/components/modbus.py homeassistant/components/modbus.py
homeassistant/components/*/modbus.py homeassistant/components/*/modbus.py
homeassistant/components/tellstick.py
homeassistant/components/*/tellstick.py homeassistant/components/*/tellstick.py
homeassistant/components/tellduslive.py homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py homeassistant/components/*/tellduslive.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py homeassistant/components/*/vera.py
homeassistant/components/ecobee.py homeassistant/components/ecobee.py
@ -57,9 +59,6 @@ omit =
homeassistant/components/nest.py homeassistant/components/nest.py
homeassistant/components/*/nest.py homeassistant/components/*/nest.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/rpi_gpio.py homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py homeassistant/components/*/rpi_gpio.py
@ -108,9 +107,12 @@ omit =
homeassistant/components/media_player/snapcast.py homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/free_mobile.py homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/googlevoice.py homeassistant/components/notify/googlevoice.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/instapush.py homeassistant/components/notify/instapush.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushetta.py
@ -149,6 +151,7 @@ omit =
homeassistant/components/sensor/torque.py homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/worldclock.py homeassistant/components/sensor/worldclock.py
homeassistant/components/switch/arest.py homeassistant/components/switch/arest.py
homeassistant/components/switch/dlink.py homeassistant/components/switch/dlink.py
@ -156,8 +159,10 @@ omit =
homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rest.py homeassistant/components/switch/rest.py
homeassistant/components/switch/transmission.py homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/proliphix.py

View File

@ -10,16 +10,15 @@
**Checklist:** **Checklist:**
- [ ] Local tests with `tox` run successfully. If code communicates with devices:
- [ ] TravisCI does not fail. **Your PR cannot be merged unless CI is green!** - [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR.
- [ ] Commits have been [squashed][squash].
- If code communicates with devices:
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]). - [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]). - [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`. - [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`. - [ ] New files were added to `.coveragerc`.
- If the code does not interact with devices:
If the code does not interact with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] Tests have been added to verify that the new code works. - [ ] Tests have been added to verify that the new code works.
[fork]: http://stackoverflow.com/a/7244456 [fork]: http://stackoverflow.com/a/7244456

View File

@ -83,11 +83,13 @@ def setup(hass, config):
hass.http.register_path( hass.http.register_path(
'GET', URL_API_COMPONENTS, _handle_get_api_components) 'GET', URL_API_COMPONENTS, _handle_get_api_components)
# /error_log
hass.http.register_path('GET', URL_API_ERROR_LOG, hass.http.register_path('GET', URL_API_ERROR_LOG,
_handle_get_api_error_log) _handle_get_api_error_log)
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out) hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
# /template
hass.http.register_path('POST', URL_API_TEMPLATE, hass.http.register_path('POST', URL_API_TEMPLATE,
_handle_post_api_template) _handle_post_api_template)

View File

@ -9,8 +9,9 @@ import logging
from homeassistant.bootstrap import prepare_setup_platform from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config from homeassistant.helpers import extract_domain_configs
from homeassistant.helpers.service import validate_service_call from homeassistant.helpers.service import (call_from_config,
validate_service_call)
DOMAIN = 'automation' DOMAIN = 'automation'
@ -35,30 +36,17 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
"""Setup the automation.""" """Setup the automation."""
config_key = DOMAIN for config_key in extract_domain_configs(config, DOMAIN):
found = 1 conf = config[config_key]
while config_key in config: if not isinstance(conf, list):
# Check for one block syntax conf = [conf]
if isinstance(config[config_key], dict):
config_block = _migrate_old_config(config[config_key]) for list_no, config_block in enumerate(conf):
name = config_block.get(CONF_ALIAS, config_key) name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
list_no))
_setup_automation(hass, config_block, name, config) _setup_automation(hass, config_block, name, config)
# Check for multiple block syntax
elif isinstance(config[config_key], list):
for list_no, config_block in enumerate(config[config_key]):
name = config_block.get(CONF_ALIAS,
"{}, {}".format(config_key, list_no))
_setup_automation(hass, config_block, name, config)
# Any scalar value is incorrect
else:
_LOGGER.error('Error in config in section %s.', config_key)
found += 1
config_key = "{} {}".format(DOMAIN, found)
return True return True
@ -97,40 +85,6 @@ def _get_action(hass, config, name):
return action return action
def _migrate_old_config(config):
"""Migrate old configuration to new."""
if CONF_PLATFORM not in config:
return config
_LOGGER.warning(
'You are using an old configuration format. Please upgrade: '
'https://home-assistant.io/components/automation/')
new_conf = {
CONF_TRIGGER: dict(config),
CONF_CONDITION: config.get('if', []),
CONF_ACTION: dict(config),
}
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
('trigger', 'mqtt_payload', 'payload'),
('trigger', 'state_entity_id', 'entity_id'),
('trigger', 'state_before', 'before'),
('trigger', 'state_after', 'after'),
('trigger', 'state_to', 'to'),
('trigger', 'state_from', 'from'),
('trigger', 'state_hours', 'hours'),
('trigger', 'state_minutes', 'minutes'),
('trigger', 'state_seconds', 'seconds'),
('action', 'execute_service', 'service'),
('action', 'service_entity_id', 'entity_id'),
('action', 'service_data', 'data')):
if key in new_conf[cat]:
new_conf[cat][new_key] = new_conf[cat].pop(key)
return new_conf
def _process_if(hass, config, p_config, action): def _process_if(hass, config, p_config, action):
"""Process if checks.""" """Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE, cond_type = p_config.get(CONF_CONDITION_TYPE,

View File

@ -55,8 +55,13 @@ def _check_template(hass, value_template):
"""Check if result of template is true.""" """Check if result of template is true."""
try: try:
value = template.render(hass, value_template, {}) value = template.render(hass, value_template, {})
except TemplateError: except TemplateError as ex:
_LOGGER.exception('Error parsing template') if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning(ex)
else:
_LOGGER.error(ex)
return False return False
return value.lower() == 'true' return value.lower() == 'true'

View File

@ -9,7 +9,8 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink) from homeassistant.components import (
bloomsky, mysensors, zwave, vera, wemo, wink)
DOMAIN = 'binary_sensor' DOMAIN = 'binary_sensor'
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
@ -37,6 +38,7 @@ DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
zwave.DISCOVER_BINARY_SENSORS: 'zwave', zwave.DISCOVER_BINARY_SENSORS: 'zwave',
vera.DISCOVER_BINARY_SENSORS: 'vera',
wemo.DISCOVER_BINARY_SENSORS: 'wemo', wemo.DISCOVER_BINARY_SENSORS: 'wemo',
wink.DISCOVER_BINARY_SENSORS: 'wink' wink.DISCOVER_BINARY_SENSORS: 'wink'
} }

View File

@ -6,7 +6,8 @@ https://home-assistant.io/components/binary_sensor.rest/
""" """
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.components.sensor.rest import RestData from homeassistant.components.sensor.rest import RestData
from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template from homeassistant.helpers import template
@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET'
# pylint: disable=unused-variable # pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup REST binary sensors.""" """Setup the REST binary sensor."""
resource = config.get('resource', None) resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD) method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None) payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True) verify_ssl = config.get('verify_ssl', True)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
rest = RestData(method, resource, payload, verify_ssl) rest = RestData(method, resource, payload, verify_ssl)
rest.update() rest.update()
@ -33,19 +39,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False return False
add_devices([RestBinarySensor( add_devices([RestBinarySensor(
hass, rest, config.get('name', DEFAULT_NAME), hass,
rest,
config.get('name', DEFAULT_NAME),
sensor_class,
config.get(CONF_VALUE_TEMPLATE))]) config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice): class RestBinarySensor(BinarySensorDevice):
"""A REST binary sensor.""" """Representation of a REST binary sensor."""
def __init__(self, hass, rest, name, value_template): def __init__(self, hass, rest, name, sensor_class, value_template):
"""Initialize a REST binary sensor.""" """Initialize a REST binary sensor."""
self._hass = hass self._hass = hass
self.rest = rest self.rest = rest
self._name = name self._name = name
self._sensor_class = sensor_class
self._state = False self._state = False
self._value_template = value_template self._value_template = value_template
self.update() self.update()
@ -55,6 +65,11 @@ class RestBinarySensor(BinarySensorDevice):
"""Return the name of the binary sensor.""" """Return the name of the binary sensor."""
return self._name return self._name
@property
def sensor_class(self):
"""Return the class of this sensor."""
return self._sensor_class
@property @property
def is_on(self): def is_on(self):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.template/
import logging import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice, from homeassistant.components.binary_sensor import (BinarySensorDevice,
DOMAIN, ENTITY_ID_FORMAT,
SENSOR_CLASSES) SENSOR_CLASSES)
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.core import EVENT_STATE_CHANGED
@ -16,7 +16,6 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.util import slugify from homeassistant.util import slugify
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_SENSORS = 'sensors' CONF_SENSORS = 'sensors'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -76,34 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice):
def __init__(self, hass, device, friendly_name, sensor_class, def __init__(self, hass, device, friendly_name, sensor_class,
value_template): value_template):
"""Initialize the Template binary sensor.""" """Initialize the Template binary sensor."""
self._hass = hass self.hass = hass
self._device = device self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name self._name = friendly_name
self._sensor_class = sensor_class self._sensor_class = sensor_class
self._template = value_template self._template = value_template
self._state = None self._state = None
self.entity_id = generate_entity_id( self.update()
ENTITY_ID_FORMAT, device,
hass=hass)
_LOGGER.info('Started template sensor %s', device) def template_bsensor_event_listener(event):
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) """Called when the target device changes state."""
def _event_listener(self, event):
if not hasattr(self, 'hass'):
return
self.update_ha_state(True) self.update_ha_state(True)
@property hass.bus.listen(EVENT_STATE_CHANGED,
def should_poll(self): template_bsensor_event_listener)
"""No polling needed."""
return False
@property
def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
@property @property
def name(self): def name(self):
@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice):
"""Return true if sensor is on.""" """Return true if sensor is on."""
return self._state return self._state
@property
def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
@property
def should_poll(self):
"""No polling needed."""
return False
def update(self): def update(self):
"""Get the latest data and update the state.""" """Get the latest data and update the state."""
try: try:
value = template.render(self._hass, self._template) self._state = template.render(self.hass,
self._template).lower() == 'true'
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):
@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
_LOGGER.warning(ex) _LOGGER.warning(ex)
return return
_LOGGER.error(ex) _LOGGER.error(ex)
value = 'false' self._state = False
self._state = value.lower() == 'true'

View File

@ -0,0 +1,69 @@
"""
Support for Vera binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.vera/
"""
import logging
import homeassistant.util.dt as dt_util
from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED)
from homeassistant.components.binary_sensor import (
BinarySensorDevice)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_devices_callback(
VeraBinarySensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['binary_sensor'])
class VeraBinarySensor(VeraDevice, BinarySensorDevice):
"""Representation of a Vera Binary Sensor."""
def __init__(self, vera_device, controller):
"""Initialize the binary_sensor."""
self._state = False
VeraDevice.__init__(self, vera_device, controller)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = 'True' if armed else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.last_trip
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
def update(self):
"""Get the latest data and update the state."""
self._state = self.vera_device.is_tripped

View File

@ -45,6 +45,9 @@ class WemoBinarySensor(BinarySensorDevice):
_LOGGER.info( _LOGGER.info(
'Subscription update for %s', 'Subscription update for %s',
_device) _device)
if not hasattr(self, 'hass'):
self.update()
return
self.update_ha_state(True) self.update_ha_state(True)
@property @property

View File

@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
# These are the available sensors mapped to binary_sensor class # These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = { SENSOR_TYPES = {
@ -77,6 +77,11 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
"""Return the name of the sensor if any.""" """Return the name of the sensor if any."""
return self.wink.name() return self.wink.name()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def update(self): def update(self):
"""Update state of the sensor.""" """Update state of the sensor."""
self.wink.update_state() self.wink.update_state()

View File

@ -12,7 +12,7 @@ from homeassistant.helpers.event import track_utc_time_change
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyicloud==0.7.2'] REQUIREMENTS = ['pyicloud==0.8.1']
CONF_INTERVAL = 'interval' CONF_INTERVAL = 'interval'
DEFAULT_INTERVAL = 8 DEFAULT_INTERVAL = 8

View File

@ -89,4 +89,9 @@ class NetgearDeviceScanner(object):
with self.lock: with self.lock:
_LOGGER.info("Scanning") _LOGGER.info("Scanning")
self.last_results = self._api.get_attached_devices() or [] results = self._api.get_attached_devices()
if results is None:
_LOGGER.warning('Error scanning devices')
self.last_results = results or []

View File

@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
# interval in minutes to exclude devices from a scan while they are home # interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval" CONF_HOME_INTERVAL = "home_interval"
REQUIREMENTS = ['python-nmap==0.4.3'] REQUIREMENTS = ['python-nmap==0.6.0']
def get_scanner(hass, config): def get_scanner(hass, config):

View File

@ -99,14 +99,11 @@ def setup_scanner(hass, config, see):
_LOGGER.info("Added beacon %s", location) _LOGGER.info("Added beacon %s", location)
else: else:
# Normal region # Normal region
if not zone.attributes.get('passive'):
kwargs['location_name'] = location
regions = REGIONS_ENTERED[dev_id] regions = REGIONS_ENTERED[dev_id]
if location not in regions: if location not in regions:
regions.append(location) regions.append(location)
_LOGGER.info("Enter region %s", location) _LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, zone) _set_gps_from_zone(kwargs, location, zone)
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
@ -121,9 +118,7 @@ def setup_scanner(hass, config, see):
if new_region: if new_region:
# Exit to previous region # Exit to previous region
zone = hass.states.get("zone.{}".format(new_region)) zone = hass.states.get("zone.{}".format(new_region))
if not zone.attributes.get('passive'): _set_gps_from_zone(kwargs, new_region, zone)
kwargs['location_name'] = new_region
_set_gps_from_zone(kwargs, zone)
_LOGGER.info("Exit to %s", new_region) _LOGGER.info("Exit to %s", new_region)
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
@ -184,11 +179,12 @@ def _parse_see_args(topic, data):
return dev_id, kwargs return dev_id, kwargs
def _set_gps_from_zone(kwargs, zone): def _set_gps_from_zone(kwargs, location, zone):
"""Set the see parameters from the zone parameters.""" """Set the see parameters from the zone parameters."""
if zone is not None: if zone is not None:
kwargs['gps'] = ( kwargs['gps'] = (
zone.attributes['latitude'], zone.attributes['latitude'],
zone.attributes['longitude']) zone.attributes['longitude'])
kwargs['gps_accuracy'] = zone.attributes['radius'] kwargs['gps_accuracy'] = zone.attributes['radius']
kwargs['location_name'] = location
return kwargs return kwargs

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED) EVENT_PLATFORM_DISCOVERED)
DOMAIN = "discovery" DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.5.4'] REQUIREMENTS = ['netdisco==0.5.5']
SCAN_INTERVAL = 300 # seconds SCAN_INTERVAL = 300 # seconds
@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router' SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos' SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver' SERVICE_PLEX = 'plex_mediaserver'
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
SERVICE_HANDLERS = { SERVICE_HANDLERS = {
SERVICE_WEMO: "wemo", SERVICE_WEMO: "wemo",
@ -33,6 +34,7 @@ SERVICE_HANDLERS = {
SERVICE_NETGEAR: 'device_tracker', SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player', SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player', SERVICE_PLEX: 'media_player',
SERVICE_SQUEEZEBOX: 'media_player',
} }

View File

@ -1,2 +1,2 @@
"""DO NOT MODIFY. Auto-generated by update_mdi script.""" """DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "e85dc66e1a0730e44f79ed11501cd79a" VERSION = "df49e6b7c930eb39b42ff1909712e95e"

View File

@ -1,2 +1,2 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script.""" """DO NOT MODIFY. Auto-generated by build_frontend script."""
VERSION = "30bcc0eacc13a2317000824741dc9ac0" VERSION = "49974cb3bb443751f7548e4e3b353304"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 0fed700045d6faba8eda8ec713ee9e6bc763507c Subproject commit a4217e0f620366e47dde82f7ce2d8f7b2bb6a079

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -57,6 +57,11 @@ class WinkGarageDoorDevice(GarageDoorDevice):
"""Return true if door is closed.""" """Return true if door is closed."""
return self.wink.state() == 0 return self.wink.state() == 0
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def close_door(self): def close_door(self):
"""Close the door.""" """Close the door."""
self.wink.set_state(0) self.wink.set_state(0)

View File

@ -74,7 +74,8 @@ def setup(hass, config):
hass.bus.listen_once( hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_START, ha.EVENT_HOMEASSISTANT_START,
lambda event: lambda event:
threading.Thread(target=server.start, daemon=True).start()) threading.Thread(target=server.start, daemon=True,
name='HTTP-server').start())
hass.http = server hass.http = server
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port, hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
@ -236,9 +237,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
# Did we find a handler for the incoming request? # Did we find a handler for the incoming request?
if handle_request_method: if handle_request_method:
# For some calls we need a valid password # For some calls we need a valid password
msg = "API password missing or incorrect."
if require_auth and not self.authenticated: if require_auth and not self.authenticated:
self.write_json_message( self.write_json_message(msg, HTTP_UNAUTHORIZED)
"API password missing or incorrect.", HTTP_UNAUTHORIZED) _LOGGER.warning(msg)
return return
handle_request_method(self, path_match, data) handle_request_method(self, path_match, data)
@ -277,8 +279,11 @@ class RequestHandler(SimpleHTTPRequestHandler):
def write_json(self, data=None, status_code=HTTP_OK, location=None): def write_json(self, data=None, status_code=HTTP_OK, location=None):
"""Helper method to return JSON to the caller.""" """Helper method to return JSON to the caller."""
json_data = json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode('UTF-8')
self.send_response(status_code) self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data)))
if location: if location:
self.send_header('Location', location) self.send_header('Location', location)
@ -288,20 +293,20 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.end_headers() self.end_headers()
if data is not None: if data is not None:
self.wfile.write( self.wfile.write(json_data)
json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8"))
def write_text(self, message, status_code=HTTP_OK): def write_text(self, message, status_code=HTTP_OK):
"""Helper method to return a text message to the caller.""" """Helper method to return a text message to the caller."""
msg_data = message.encode('UTF-8')
self.send_response(status_code) self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(msg_data)))
self.set_session_cookie_header() self.set_session_cookie_header()
self.end_headers() self.end_headers()
self.wfile.write(message.encode("UTF-8")) self.wfile.write(msg_data)
def write_file(self, path, cache_headers=True): def write_file(self, path, cache_headers=True):
"""Return a file to the user.""" """Return a file to the user."""

View File

@ -0,0 +1,145 @@
"""
Component to offer a way to select a value from a slider.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_slider/
"""
import logging
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
DOMAIN = 'input_slider'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
CONF_INITIAL = 'initial'
CONF_MIN = 'min'
CONF_MAX = 'max'
CONF_ICON = 'icon'
CONF_STEP = 'step'
ATTR_VALUE = 'value'
ATTR_MIN = 'min'
ATTR_MAX = 'max'
ATTR_STEP = 'step'
SERVICE_SELECT_VALUE = 'select_value'
def select_value(hass, entity_id, value):
"""Set input_slider to value."""
hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, {
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: value,
})
def setup(hass, config):
"""Set up input slider."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = []
for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue
name = cfg.get(CONF_NAME)
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
state = cfg.get(CONF_INITIAL)
step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON)
if state < minimum:
state = minimum
if state > maximum:
state = maximum
entities.append(
InputSlider(object_id, name, state, minimum, maximum, step, icon)
)
if not entities:
return False
def select_value_service(call):
"""Handle a calls to the input slider services."""
target_inputs = component.extract_from_service(call)
for input_slider in target_inputs:
input_slider.select_value(call.data.get(ATTR_VALUE))
hass.services.register(DOMAIN, SERVICE_SELECT_VALUE,
select_value_service)
component.add_entities(entities)
return True
class InputSlider(Entity):
"""Represent an slider."""
# pylint: disable=too-many-arguments
def __init__(self, object_id, name, state, minimum, maximum, step, icon):
"""Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._current_value = state
self._minimum = minimum
self._maximum = maximum
self._step = step
self._icon = icon
@property
def should_poll(self):
"""If entity should be polled."""
return False
@property
def name(self):
"""Name of the select input."""
return self._name
@property
def icon(self):
"""Icon to be used for this entity."""
return self._icon
@property
def state(self):
"""State of the component."""
return self._current_value
@property
def state_attributes(self):
"""State attributes."""
return {
ATTR_MIN: self._minimum,
ATTR_MAX: self._maximum,
ATTR_STEP: self._step
}
def select_value(self, value):
"""Select new value."""
num_value = int(value)
if num_value < self._minimum or num_value > self._maximum:
_LOGGER.warning('Invalid value: %s (range %s - %s)',
num_value, self._minimum, self._maximum)
return
self._current_value = num_value
self.update_ha_state()

View File

@ -9,7 +9,8 @@ import os
import csv import csv
from homeassistant.components import ( from homeassistant.components import (
group, discovery, wemo, wink, isy994, zwave, insteon_hub, mysensors) group, discovery, wemo, wink, isy994,
zwave, insteon_hub, mysensors, tellstick, vera)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
@ -64,6 +65,8 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_HUE: 'hue', discovery.SERVICE_HUE: 'hue',
zwave.DISCOVER_LIGHTS: 'zwave', zwave.DISCOVER_LIGHTS: 'zwave',
mysensors.DISCOVER_LIGHTS: 'mysensors', mysensors.DISCOVER_LIGHTS: 'mysensors',
tellstick.DISCOVER_LIGHTS: 'tellstick',
vera.DISCOVER_LIGHTS: 'vera',
} }
PROP_TO_ATTR = { PROP_TO_ATTR = {
@ -224,7 +227,7 @@ def setup(hass, config):
pass pass
if ATTR_COLOR_TEMP in dat: if ATTR_COLOR_TEMP in dat:
# color_temp should be an int of mirads value # color_temp should be an int of mireds value
colortemp = dat.get(ATTR_COLOR_TEMP) colortemp = dat.get(ATTR_COLOR_TEMP)
# Without this check, a ctcolor with value '99' would work # Without this check, a ctcolor with value '99' would work
@ -295,7 +298,7 @@ class Light(ToggleEntity):
@property @property
def color_temp(self): def color_temp(self):
"""Return the CT color value in mirads.""" """Return the CT color value in mireds."""
return None return None
@property @property

View File

@ -53,6 +53,8 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Hue lights.""" """Setup the Hue lights."""
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE) filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
allow_unreachable = config.get('allow_unreachable', False)
if discovery_info is not None: if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname host = urlparse(discovery_info[1]).hostname
else: else:
@ -69,10 +71,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
if host in _CONFIGURING: if host in _CONFIGURING:
return return
setup_bridge(host, hass, add_devices_callback, filename) setup_bridge(host, hass, add_devices_callback, filename, allow_unreachable)
def setup_bridge(host, hass, add_devices_callback, filename): def setup_bridge(host, hass, add_devices_callback, filename,
allow_unreachable):
"""Setup a phue bridge based on host parameter.""" """Setup a phue bridge based on host parameter."""
import phue import phue
@ -88,7 +91,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
except phue.PhueRegistrationException: except phue.PhueRegistrationException:
_LOGGER.warning("Connected to Hue at %s but not registered.", host) _LOGGER.warning("Connected to Hue at %s but not registered.", host)
request_configuration(host, hass, add_devices_callback, filename) request_configuration(host, hass, add_devices_callback, filename,
allow_unreachable)
return return
@ -130,7 +134,7 @@ def setup_bridge(host, hass, add_devices_callback, filename):
if light_id not in lights: if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info, lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights, bridge, update_lights,
bridge_type=bridge_type) bridge_type, allow_unreachable)
new_lights.append(lights[light_id]) new_lights.append(lights[light_id])
else: else:
lights[light_id].info = info lights[light_id].info = info
@ -141,7 +145,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
update_lights() update_lights()
def request_configuration(host, hass, add_devices_callback, filename): def request_configuration(host, hass, add_devices_callback, filename,
allow_unreachable):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = get_component('configurator')
@ -155,7 +160,8 @@ def request_configuration(host, hass, add_devices_callback, filename):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def hue_configuration_callback(data): def hue_configuration_callback(data):
"""The actions to do when our configuration callback is called.""" """The actions to do when our configuration callback is called."""
setup_bridge(host, hass, add_devices_callback, filename) setup_bridge(host, hass, add_devices_callback, filename,
allow_unreachable)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback, hass, "Philips Hue", hue_configuration_callback,
@ -171,7 +177,7 @@ class HueLight(Light):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, light_id, info, bridge, update_lights, def __init__(self, light_id, info, bridge, update_lights,
bridge_type='hue'): bridge_type, allow_unreachable):
"""Initialize the light.""" """Initialize the light."""
self.light_id = light_id self.light_id = light_id
self.info = info self.info = info
@ -179,6 +185,8 @@ class HueLight(Light):
self.update_lights = update_lights self.update_lights = update_lights
self.bridge_type = bridge_type self.bridge_type = bridge_type
self.allow_unreachable = allow_unreachable
@property @property
def unique_id(self): def unique_id(self):
"""Return the ID of this Hue light.""" """Return the ID of this Hue light."""
@ -209,6 +217,10 @@ class HueLight(Light):
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
self.update_lights() self.update_lights()
if self.allow_unreachable:
return self.info['state']['on']
else:
return self.info['state']['reachable'] and self.info['state']['on'] return self.info['state']['reachable'] and self.info['state']['on']
def turn_on(self, **kwargs): def turn_on(self, **kwargs):

View File

@ -4,127 +4,80 @@ Support for Tellstick lights.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.tellstick/ https://home-assistant.io/components/light.tellstick/
""" """
from homeassistant.components import tellstick
from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.components.light import ATTR_BRIGHTNESS, Light
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS,
ATTR_DISCOVER_DEVICES,
REQUIREMENTS = ['tellcore-py==1.1.2'] ATTR_DISCOVER_CONFIG)
SIGNAL_REPETITIONS = 1
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Tellstick lights.""" """Setup Tellstick lights."""
import tellcore.telldus as telldus if (discovery_info is None or
from tellcore.library import DirectCallbackDispatcher discovery_info[ATTR_DISCOVER_DEVICES] is None or
import tellcore.constants as tellcore_constants tellstick.TELLCORE_REGISTRY is None):
return
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher()) signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG,
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) DEFAULT_SIGNAL_REPETITIONS)
switches_and_lights = core.devices() add_devices(TellstickLight(
lights = [] tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
for switch in switches_and_lights:
if switch.methods(tellcore_constants.TELLSTICK_DIM):
lights.append(TellstickLight(switch, signal_repetitions))
def _device_event_callback(id_, method, data, cid):
"""Called from the TelldusCore library to update one device."""
for light_device in lights:
if light_device.tellstick_device.id == id_:
# Execute the update in another thread
light_device.update_ha_state(True)
break
callback_id = core.register_device_event(_device_event_callback)
def unload_telldus_lib(event):
"""Un-register the callback bindings."""
if callback_id is not None:
core.unregister_callback(callback_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
add_devices_callback(lights)
class TellstickLight(Light): class TellstickLight(tellstick.TellstickDevice, Light):
"""Representation of a Tellstick light.""" """Representation of a Tellstick light."""
def __init__(self, tellstick_device, signal_repetitions): def __init__(self, tellstick_device, signal_repetitions):
"""Initialize the light.""" """Initialize the light."""
import tellcore.constants as tellcore_constants self._brightness = 255
tellstick.TellstickDevice.__init__(self,
self.tellstick_device = tellstick_device tellstick_device,
self.signal_repetitions = signal_repetitions signal_repetitions)
self._brightness = 0
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
tellcore_constants.TELLSTICK_TURNOFF |
tellcore_constants.TELLSTICK_DIM |
tellcore_constants.TELLSTICK_UP |
tellcore_constants.TELLSTICK_DOWN)
self.update()
@property
def name(self):
"""Return the name of the switch if any."""
return self.tellstick_device.name
@property @property
def is_on(self): def is_on(self):
"""Return true if switch is on.""" """Return true if switch is on."""
return self._brightness > 0 return self._state
@property @property
def brightness(self): def brightness(self):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return self._brightness return self._brightness
def turn_off(self, **kwargs): def set_tellstick_state(self, last_command_sent, last_data_sent):
"""Turn the switch off.""" """Update the internal representation of the switch."""
for _ in range(self.signal_repetitions): from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_DIM
if last_command_sent == TELLSTICK_DIM:
if last_data_sent is not None:
self._brightness = int(last_data_sent)
self._state = self._brightness > 0
else:
self._state = last_command_sent == TELLSTICK_TURNON
def _send_tellstick_command(self, command, data):
"""Handle the turn_on / turn_off commands."""
from tellcore.constants import (TELLSTICK_TURNOFF, TELLSTICK_DIM)
if command == TELLSTICK_TURNOFF:
self.tellstick_device.turn_off() self.tellstick_device.turn_off()
self._brightness = 0 elif command == TELLSTICK_DIM:
self.update_ha_state() self.tellstick_device.dim(self._brightness)
else:
raise NotImplementedError(
"Command not implemented: {}".format(command))
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""
from tellcore.constants import TELLSTICK_DIM
brightness = kwargs.get(ATTR_BRIGHTNESS) brightness = kwargs.get(ATTR_BRIGHTNESS)
if brightness is not None:
if brightness is None:
self._brightness = 255
else:
self._brightness = brightness self._brightness = brightness
for _ in range(self.signal_repetitions): self.call_tellstick(TELLSTICK_DIM, self._brightness)
self.tellstick_device.dim(self._brightness)
self.update_ha_state()
def update(self): def turn_off(self, **kwargs):
"""Update state of the light.""" """Turn the switch off."""
import tellcore.constants as tellcore_constants from tellcore.constants import TELLSTICK_TURNOFF
self.call_tellstick(TELLSTICK_TURNOFF)
last_command = self.tellstick_device.last_sent_command(
self.last_sent_command_mask)
if last_command == tellcore_constants.TELLSTICK_TURNON:
self._brightness = 255
elif last_command == tellcore_constants.TELLSTICK_TURNOFF:
self._brightness = 0
elif (last_command == tellcore_constants.TELLSTICK_DIM or
last_command == tellcore_constants.TELLSTICK_UP or
last_command == tellcore_constants.TELLSTICK_DOWN):
last_sent_value = self.tellstick_device.last_sent_value()
if last_sent_value is not None:
self._brightness = last_sent_value
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def assumed_state(self):
"""Tellstick devices are always assumed state."""
return True

View File

@ -6,15 +6,15 @@ https://home-assistant.io/components/light.vera/
""" """
import logging import logging
from requests.exceptions import RequestException
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.components.light import ATTR_BRIGHTNESS, Light
from homeassistant.const import ( from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) STATE_OFF, STATE_ON)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
REQUIREMENTS = ['pyvera==0.2.8'] DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -22,74 +22,17 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup Vera lights.""" """Setup Vera lights."""
import pyvera as veraApi add_devices_callback(
VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light'])
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
devices = []
try:
devices = vera_controller.get_devices([
'Switch',
'On/Off Switch',
'Dimmable Switch'])
except RequestException:
# There was a network related error connecting to the vera controller.
_LOGGER.exception("Error communicating with Vera API")
return False
lights = []
for device in devices:
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
lights.append(VeraLight(device, vera_controller, extra_data))
add_devices_callback(lights)
class VeraLight(Light): class VeraLight(VeraDevice, Light):
"""Representation of a Vera Light, including dimmable.""" """Representation of a Vera Light, including dimmable."""
def __init__(self, vera_device, controller, extra_data=None): def __init__(self, vera_device, controller):
"""Initialize the light.""" """Initialize the light."""
self.vera_device = vera_device self._state = False
self.extra_data = extra_data VeraDevice.__init__(self, vera_device, controller)
self.controller = controller
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self._state = STATE_OFF
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
self.update_ha_state(True)
@property
def name(self):
"""Return the name of the light."""
return self._name
@property @property
def brightness(self): def brightness(self):
@ -137,20 +80,13 @@ class VeraLight(Light):
attr[ATTR_TRIPPED] = 'True' if tripped else 'False' attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
@property
def should_poll(self):
"""No polling needed."""
return False
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
return self._state == STATE_ON return self._state
def update(self): def update(self):
"""Called by the vera device callback to update state.""" """Called by the vera device callback to update state."""
if self.vera_device.is_switched_on(): self._state = self.vera_device.is_switched_on()
self._state = STATE_ON
else:
self._state = STATE_OFF

View File

@ -8,8 +8,10 @@ import logging
from datetime import timedelta from datetime import timedelta
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.color as color_util
from homeassistant.components.light import ( from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS) Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
ATTR_XY_COLOR)
DEPENDENCIES = ['wemo'] DEPENDENCIES = ['wemo']
@ -39,17 +41,14 @@ def setup_bridge(bridge, add_devices_callback):
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights(): def update_lights():
"""Update the WeMo led objects with latest info from the bridge.""" """Update the WeMo led objects with latest info from the bridge."""
bridge.bridge_get_lights() bridge.bridge_update()
new_lights = [] new_lights = []
for light_id, info in bridge.Lights.items(): for light_id, device in bridge.Lights.items():
if light_id not in lights: if light_id not in lights:
lights[light_id] = WemoLight(bridge, light_id, info, lights[light_id] = WemoLight(device, update_lights)
update_lights)
new_lights.append(lights[light_id]) new_lights.append(lights[light_id])
else:
lights[light_id].info = info
if new_lights: if new_lights:
add_devices_callback(new_lights) add_devices_callback(new_lights)
@ -60,44 +59,73 @@ def setup_bridge(bridge, add_devices_callback):
class WemoLight(Light): class WemoLight(Light):
"""Representation of a WeMo light.""" """Representation of a WeMo light."""
def __init__(self, bridge, light_id, info, update_lights): def __init__(self, device, update_lights):
"""Initialize the light.""" """Initialize the light."""
self.bridge = bridge self.light_id = device.name
self.light_id = light_id self.device = device
self.info = info
self.update_lights = update_lights self.update_lights = update_lights
@property @property
def unique_id(self): def unique_id(self):
"""Return the ID of this light.""" """Return the ID of this light."""
deviceid = self.bridge.light_get_id(self.info) deviceid = self.device.uniqueID
return "{}.{}".format(self.__class__, deviceid) return "{}.{}".format(self.__class__, deviceid)
@property @property
def name(self): def name(self):
"""Return the name of the light.""" """Return the name of the light."""
return self.bridge.light_name(self.info) return self.device.name
@property @property
def brightness(self): def brightness(self):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
state = self.bridge.light_get_state(self.info) return self.device.state.get('level', 255)
return int(state['dim'])
@property
def xy_color(self):
"""Return the XY color values of this light."""
return self.device.state.get('color_xy')
@property
def color_temp(self):
"""Return the color temperature of this light in mireds."""
return self.device.state.get('temperature_mireds')
@property @property
def is_on(self): def is_on(self):
"""True if device is on.""" """True if device is on."""
state = self.bridge.light_get_state(self.info) return self.device.state['onoff'] != 0
return int(state['state'])
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the light on.""" """Turn the light on."""
dim = kwargs.get(ATTR_BRIGHTNESS, self.brightness) transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
self.bridge.light_set_state(self.info, state=1, dim=dim)
if ATTR_XY_COLOR in kwargs:
xycolor = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
xycolor = color_util.color_RGB_to_xy(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
else:
xycolor = None
if xycolor is not None:
self.device.set_color(xycolor, transition=transitiontime)
if ATTR_COLOR_TEMP in kwargs:
colortemp = kwargs[ATTR_COLOR_TEMP]
self.device.set_temperature(mireds=colortemp,
transition=transitiontime)
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
self.device.turn_on(level=brightness, transition=transitiontime)
else:
self.device.turn_on(transition=transitiontime)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the light off.""" """Turn the light off."""
self.bridge.light_set_state(self.info, state=0, dim=0) transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
self.device.turn_off(transition=transitiontime)
def update(self): def update(self):
"""Synchronize state with bridge.""" """Synchronize state with bridge."""

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.components.light import ATTR_BRIGHTNESS, Light
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@ -58,6 +58,11 @@ class WinkLight(Light):
"""Return the brightness of the light.""" """Return the brightness of the light."""
return int(self.wink.brightness() * 255) return int(self.wink.brightness() * 255)
@property
def available(self):
"""True if connection == True."""
return self.wink.available
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""

View File

@ -101,7 +101,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light):
# Zwave multilevel switches use a range of [0, 99] to control # Zwave multilevel switches use a range of [0, 99] to control
# brightness. # brightness.
brightness = (self._brightness / 255) * 99 brightness = int((self._brightness / 255) * 99)
if self._value.node.set_dimmer(self._value.value_id, brightness): if self._value.node.set_dimmer(self._value.value_id, brightness):
self._state = STATE_ON self._state = STATE_ON

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -56,6 +56,11 @@ class WinkLockDevice(LockDevice):
"""Return true if device is locked.""" """Return true if device is locked."""
return self.wink.state() return self.wink.state()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def lock(self, **kwargs): def lock(self, **kwargs):
"""Lock the device.""" """Lock the device."""
self.wink.set_state(True) self.wink.set_state(True)

View File

@ -19,6 +19,8 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'media_player' DOMAIN = 'media_player'
SCAN_INTERVAL = 10 SCAN_INTERVAL = 10
@ -28,6 +30,7 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast', discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos', discovery.SERVICE_SONOS: 'sonos',
discovery.SERVICE_PLEX: 'plex', discovery.SERVICE_PLEX: 'plex',
discovery.SERVICE_SQUEEZEBOX: 'squeezebox',
} }
SERVICE_PLAY_MEDIA = 'play_media' SERVICE_PLAY_MEDIA = 'play_media'
@ -229,11 +232,9 @@ def setup(hass, config):
def media_player_service_handler(service): def media_player_service_handler(service):
"""Map services to methods on MediaPlayerDevice.""" """Map services to methods on MediaPlayerDevice."""
target_players = component.extract_from_service(service)
method = SERVICE_TO_METHOD[service.service] method = SERVICE_TO_METHOD[service.service]
for player in target_players: for player in component.extract_from_service(service):
getattr(player, method)() getattr(player, method)()
if player.should_poll: if player.should_poll:
@ -245,14 +246,15 @@ def setup(hass, config):
def volume_set_service(service): def volume_set_service(service):
"""Set specified volume on the media player.""" """Set specified volume on the media player."""
target_players = component.extract_from_service(service) volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL)
if ATTR_MEDIA_VOLUME_LEVEL not in service.data: if volume is None:
_LOGGER.error(
'Received call to %s without attribute %s',
service.service, ATTR_MEDIA_VOLUME_LEVEL)
return return
volume = service.data[ATTR_MEDIA_VOLUME_LEVEL] for player in component.extract_from_service(service):
for player in target_players:
player.set_volume_level(volume) player.set_volume_level(volume)
if player.should_poll: if player.should_poll:
@ -263,14 +265,15 @@ def setup(hass, config):
def volume_mute_service(service): def volume_mute_service(service):
"""Mute (true) or unmute (false) the media player.""" """Mute (true) or unmute (false) the media player."""
target_players = component.extract_from_service(service) mute = service.data.get(ATTR_MEDIA_VOLUME_MUTED)
if ATTR_MEDIA_VOLUME_MUTED not in service.data: if mute is None:
_LOGGER.error(
'Received call to %s without attribute %s',
service.service, ATTR_MEDIA_VOLUME_MUTED)
return return
mute = service.data[ATTR_MEDIA_VOLUME_MUTED] for player in component.extract_from_service(service):
for player in target_players:
player.mute_volume(mute) player.mute_volume(mute)
if player.should_poll: if player.should_poll:
@ -281,14 +284,15 @@ def setup(hass, config):
def media_seek_service(service): def media_seek_service(service):
"""Seek to a position.""" """Seek to a position."""
target_players = component.extract_from_service(service) position = service.data.get(ATTR_MEDIA_SEEK_POSITION)
if ATTR_MEDIA_SEEK_POSITION not in service.data: if position is None:
_LOGGER.error(
'Received call to %s without attribute %s',
service.service, ATTR_MEDIA_SEEK_POSITION)
return return
position = service.data[ATTR_MEDIA_SEEK_POSITION] for player in component.extract_from_service(service):
for player in target_players:
player.media_seek(position) player.media_seek(position)
if player.should_poll: if player.should_poll:
@ -302,10 +306,12 @@ def setup(hass, config):
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
media_id = service.data.get(ATTR_MEDIA_CONTENT_ID) media_id = service.data.get(ATTR_MEDIA_CONTENT_ID)
if media_type is None: if media_type is None or media_id is None:
return missing_attr = (ATTR_MEDIA_CONTENT_TYPE if media_type is None
else ATTR_MEDIA_CONTENT_ID)
if media_id is None: _LOGGER.error(
'Received call to %s without attribute %s',
service.service, missing_attr)
return return
for player in component.extract_from_service(service): for player in component.extract_from_service(service):

View File

@ -239,7 +239,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
if self._cur_track > 0: if self._cur_track > 0:
support |= SUPPORT_PREVIOUS_TRACK support |= SUPPORT_PREVIOUS_TRACK
if self._cur_track < len(self.tracks)-1: if self._cur_track < len(self.tracks) - 1:
support |= SUPPORT_NEXT_TRACK support |= SUPPORT_NEXT_TRACK
return support return support
@ -252,7 +252,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
def media_next_track(self): def media_next_track(self):
"""Send next track command.""" """Send next track command."""
if self._cur_track < len(self.tracks)-1: if self._cur_track < len(self.tracks) - 1:
self._cur_track += 1 self._cur_track += 1
self.update_ha_state() self.update_ha_state()

View File

@ -50,7 +50,7 @@ class KodiDevice(MediaPlayerDevice):
self._server = jsonrpc_requests.Server( self._server = jsonrpc_requests.Server(
'{}/jsonrpc'.format(self._url), '{}/jsonrpc'.format(self._url),
auth=auth) auth=auth)
self._players = None self._players = list()
self._properties = None self._properties = None
self._item = None self._item = None
self._app_properties = None self._app_properties = None
@ -67,6 +67,7 @@ class KodiDevice(MediaPlayerDevice):
try: try:
return self._server.Player.GetActivePlayers() return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError: except jsonrpc_requests.jsonrpc.TransportError:
if self._players is not None:
_LOGGER.warning('Unable to fetch kodi data') _LOGGER.warning('Unable to fetch kodi data')
_LOGGER.debug('Unable to fetch kodi data', exc_info=True) _LOGGER.debug('Unable to fetch kodi data', exc_info=True)
return None return None

View File

@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-mpd2==0.5.4'] REQUIREMENTS = ['python-mpd2==0.5.5']
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK

View File

@ -0,0 +1,128 @@
# Describes the format for available media_player services
turn_on:
description: Turn a media player power on
fields:
entity_id:
description: Name(s) of entities to turn on
example: 'media_player.living_room_chromecast'
turn_off:
description: Turn a media player power off
fields:
entity_id:
description: Name(s) of entities to turn off
example: 'media_player.living_room_chromecast'
toggle:
description: Toggles a media player power state
fields:
entity_id:
description: Name(s) of entities to toggle
example: 'media_player.living_room_chromecast'
volume_up:
description: Turn a media player volume up
fields:
entity_id:
description: Name(s) of entities to turn volume up on
example: 'media_player.living_room_sonos'
volume_down:
description: Turn a media player volume down
fields:
entity_id:
description: Name(s) of entities to turn volume down on
example: 'media_player.living_room_sonos'
mute_volume:
description: Mute a media player's volume
fields:
entity_id:
description: Name(s) of entities to mute
example: 'media_player.living_room_sonos'
mute:
description: True/false for mute/unmute
example: true
set_volume_level:
description: Set a media player's volume level
fields:
entity_id:
description: Name(s) of entities to set volume level on
example: 'media_player.living_room_sonos'
volume:
description: Volume level to set
example: 60
media_play_pause:
description: Toggle media player play/pause state
fields:
entity_id:
description: Name(s) of entities to toggle play/pause state on
example: 'media_player.living_room_sonos'
media_play:
description: Send the media player the command for play.
fields:
entity_id:
description: Name(s) of entities to play on
example: 'media_player.living_room_sonos'
media_pause:
description: Send the media player the command for pause.
fields:
entity_id:
description: Name(s) of entities to pause on
example: 'media_player.living_room_sonos'
media_next_track:
description: Send the media player the command for next track.
fields:
entity_id:
description: Name(s) of entities to send next track command to
example: 'media_player.living_room_sonos'
media_previous_track:
description: Send the media player the command for previous track.
fields:
entity_id:
description: Name(s) of entities to send previous track command to
example: 'media_player.living_room_sonos'
media_seek:
description: Send the media player the command to seek in current playing media.
fields:
entity_id:
description: Name(s) of entities to seek media on
example: 'media_player.living_room_chromecast'
position:
description: Position to seek to. The format is platform dependent.
example: 100
play_media:
description: Send the media player the command for playing media.
fields:
entity_id:
description: Name(s) of entities to seek media on
example: 'media_player.living_room_chromecast'
media_content_id:
description: The ID of the content to play. Platform dependent.
example: 'https://home-assistant.io/images/cast/splash.png'
media_content_type:
description: The type of the content to play. Must be one of MUSIC, TVSHOW, VIDEO, EPISODE, CHANNEL or PLAYLIST
example: 'MUSIC'

View File

@ -9,8 +9,9 @@ import logging
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_MUTE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_VOLUME_SET, MediaPlayerDevice) SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import ( from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
@ -27,7 +28,8 @@ _REQUESTS_LOGGER = logging.getLogger('requests')
_REQUESTS_LOGGER.setLevel(logging.ERROR) _REQUESTS_LOGGER.setLevel(logging.ERROR)
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA |\
SUPPORT_SEEK
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -222,7 +224,7 @@ class SonosDevice(MediaPlayerDevice):
@only_if_coordinator @only_if_coordinator
def media_play(self): def media_play(self):
"""Send paly command.""" """Send play command."""
self._player.play() self._player.play()
@only_if_coordinator @only_if_coordinator
@ -249,3 +251,8 @@ class SonosDevice(MediaPlayerDevice):
def turn_on(self): def turn_on(self):
"""Turn the media player on.""" """Turn the media player on."""
self._player.play() self._player.play()
@only_if_coordinator
def play_media(self, media_type, media_id):
"""Send the play_media command to the media player."""
self._player.play_uri(media_id)

View File

@ -22,19 +22,32 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
KNOWN_DEVICES = []
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the squeezebox platform.""" """Setup the squeezebox platform."""
if not config.get(CONF_HOST): if discovery_info is not None:
host = discovery_info[0]
port = 9090
else:
host = config.get(CONF_HOST)
port = int(config.get('port', 9090))
if not host:
_LOGGER.error( _LOGGER.error(
"Missing required configuration items in %s: %s", "Missing required configuration items in %s: %s",
DOMAIN, DOMAIN,
CONF_HOST) CONF_HOST)
return False return False
# Only add a media server once
if host in KNOWN_DEVICES:
return False
KNOWN_DEVICES.append(host)
lms = LogitechMediaServer( lms = LogitechMediaServer(
config.get(CONF_HOST), host, port,
config.get('port', '9090'),
config.get(CONF_USERNAME), config.get(CONF_USERNAME),
config.get(CONF_PASSWORD)) config.get(CONF_PASSWORD))

View File

@ -0,0 +1,91 @@
"""
Support for Yamaha Receivers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.yamaha/
"""
import logging
from homeassistant.components.media_player import (
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_ON
REQUIREMENTS = ['rxv==0.1.9']
_LOGGER = logging.getLogger(__name__)
SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yamaha platform."""
import rxv
add_devices(YamahaDevice(config.get("name"), receiver)
for receiver in rxv.find())
class YamahaDevice(MediaPlayerDevice):
"""Representation of a Yamaha device."""
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, receiver):
"""Initialize the Yamaha Receiver."""
self._receiver = receiver
self._muted = False
self._volume = 0
self._pwstate = STATE_OFF
self.update()
self._name = name
def update(self):
"""Get the latest details from the device."""
if self._receiver.on:
self._pwstate = STATE_ON
else:
self._pwstate = STATE_OFF
self._muted = self._receiver.mute
self._volume = (self._receiver.volume/100) + 1
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._pwstate
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_YAMAHA
def turn_off(self):
"""Turn off media player."""
self._receiver.on = False
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
receiver_vol = 100-(volume * 100)
negative_receiver_vol = -receiver_vol
self._receiver.volume = negative_receiver_vol
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
self._receiver.mute = mute
def turn_on(self):
"""Turn the media player on."""
self._receiver.on = True
self._volume = (self._receiver.volume/100) + 1

View File

@ -10,11 +10,11 @@ import socket
import time import time
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.util as util import homeassistant.util as util
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.helpers import validate_config
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
@ -29,6 +29,7 @@ EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
REQUIREMENTS = ['paho-mqtt==1.1'] REQUIREMENTS = ['paho-mqtt==1.1']
CONF_EMBEDDED = 'embedded'
CONF_BROKER = 'broker' CONF_BROKER = 'broker'
CONF_PORT = 'port' CONF_PORT = 'port'
CONF_CLIENT_ID = 'client_id' CONF_CLIENT_ID = 'client_id'
@ -92,17 +93,46 @@ def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
MQTT_CLIENT.subscribe(topic, qos) MQTT_CLIENT.subscribe(topic, qos)
def _setup_server(hass, config):
"""Try to start embedded MQTT broker."""
conf = config.get(DOMAIN, {})
# Only setup if embedded config passed in or no broker specified
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
return None
server = prepare_setup_platform(hass, config, DOMAIN, 'server')
if server is None:
_LOGGER.error('Unable to load embedded server.')
return None
success, broker_config = server.start(hass, conf.get(CONF_EMBEDDED))
return success and broker_config
def setup(hass, config): def setup(hass, config):
"""Start the MQTT protocol service.""" """Start the MQTT protocol service."""
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER): # pylint: disable=too-many-locals
return False conf = config.get(DOMAIN, {})
conf = config[DOMAIN]
broker = conf[CONF_BROKER]
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
client_id = util.convert(conf.get(CONF_CLIENT_ID), str) client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE) keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
broker_config = _setup_server(hass, config)
# Only auto config if no server config was passed in
if broker_config and CONF_EMBEDDED not in conf:
broker, port, username, password, certificate, protocol = broker_config
elif not broker_config and (CONF_EMBEDDED in conf or
CONF_BROKER not in conf):
_LOGGER.error('Unable to start broker and auto-configure MQTT.')
return False
if CONF_BROKER in conf:
broker = conf[CONF_BROKER]
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
username = util.convert(conf.get(CONF_USERNAME), str) username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str) password = util.convert(conf.get(CONF_PASSWORD), str)
certificate = util.convert(conf.get(CONF_CERTIFICATE), str) certificate = util.convert(conf.get(CONF_CERTIFICATE), str)

View File

@ -0,0 +1,114 @@
"""MQTT server."""
import asyncio
import logging
import tempfile
import threading
from homeassistant.components.mqtt import PROTOCOL_311
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['hbmqtt==0.6.3']
DEPENDENCIES = ['http']
@asyncio.coroutine
def broker_coro(loop, config):
"""Start broker coroutine."""
from hbmqtt.broker import Broker
broker = Broker(config, loop)
yield from broker.start()
return broker
def loop_run(loop, broker, shutdown_complete):
"""Run broker and clean up when done."""
loop.run_forever()
# run_forever ends when stop is called because we're shutting down
loop.run_until_complete(broker.shutdown())
loop.close()
shutdown_complete.set()
def start(hass, server_config):
"""Initialize MQTT Server."""
from hbmqtt.broker import BrokerException
loop = asyncio.new_event_loop()
try:
passwd = tempfile.NamedTemporaryFile()
if server_config is None:
server_config, client_config = generate_config(hass, passwd)
else:
client_config = None
start_server = asyncio.gather(broker_coro(loop, server_config),
loop=loop)
loop.run_until_complete(start_server)
# Result raises exception if one was raised during startup
broker = start_server.result()[0]
except BrokerException:
logging.getLogger(__name__).exception('Error initializing MQTT server')
loop.close()
return False, None
finally:
passwd.close()
shutdown_complete = threading.Event()
def shutdown(event):
"""Gracefully shutdown MQTT broker."""
loop.call_soon_threadsafe(loop.stop)
shutdown_complete.wait()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)
threading.Thread(target=loop_run, args=(loop, broker, shutdown_complete),
name="MQTT-server").start()
return True, client_config
def generate_config(hass, passwd):
"""Generate a configuration based on current Home Assistant instance."""
config = {
'listeners': {
'default': {
'max-connections': 50000,
'bind': '0.0.0.0:1883',
'type': 'tcp',
},
'ws-1': {
'bind': '0.0.0.0:8080',
'type': 'ws',
},
},
'auth': {
'allow-anonymous': hass.config.api.api_password is None
},
'plugins': ['auth_anonymous'],
}
if hass.config.api.api_password:
username = 'homeassistant'
password = hass.config.api.api_password
# Encrypt with what hbmqtt uses to verify
from passlib.apps import custom_app_context
passwd.write(
'homeassistant:{}\n'.format(
custom_app_context.encrypt(
hass.config.api.api_password)).encode('utf-8'))
passwd.flush()
config['auth']['password-file'] = passwd.name
config['plugins'].append('auth_file')
else:
username = None
password = None
client_config = ('localhost', 1883, username, password, None, PROTOCOL_311)
return config, client_config

View File

@ -71,6 +71,9 @@ def setup(hass, config):
message = call.data.get(ATTR_MESSAGE) message = call.data.get(ATTR_MESSAGE)
if message is None: if message is None:
_LOGGER.error(
'Received call to %s without attribute %s',
call.service, ATTR_MESSAGE)
return return
title = template.render( title = template.render(

View File

@ -45,7 +45,7 @@ class FileNotificationService(BaseNotificationService):
title = '{} notifications (Log started: {})\n{}\n'.format( title = '{} notifications (Log started: {})\n{}\n'.format(
kwargs.get(ATTR_TITLE), kwargs.get(ATTR_TITLE),
dt_util.strip_microseconds(dt_util.utcnow()), dt_util.strip_microseconds(dt_util.utcnow()),
'-'*80) '-' * 80)
file.write(title) file.write(title)
if self.add_timestamp == 1: if self.add_timestamp == 1:

View File

@ -0,0 +1,58 @@
"""
GNTP (aka Growl) notification service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.gntp/
"""
import logging
import os
from homeassistant.components.notify import (
ATTR_TITLE, BaseNotificationService)
REQUIREMENTS = ['gntp==1.0.3']
_LOGGER = logging.getLogger(__name__)
_GNTP_LOGGER = logging.getLogger('gntp')
_GNTP_LOGGER.setLevel(logging.ERROR)
def get_service(hass, config):
"""Get the GNTP notification service."""
if config.get('app_icon') is None:
icon_file = os.path.join(os.path.dirname(__file__), "..", "frontend",
"www_static", "favicon-192x192.png")
app_icon = open(icon_file, 'rb').read()
else:
app_icon = config.get('app_icon')
return GNTPNotificationService(config.get('app_name', 'HomeAssistant'),
config.get('app_icon', app_icon),
config.get('hostname', 'localhost'),
config.get('password'),
config.get('port', 23053))
# pylint: disable=too-few-public-methods
class GNTPNotificationService(BaseNotificationService):
"""Implement the notification service for GNTP."""
# pylint: disable=too-many-arguments
def __init__(self, app_name, app_icon, hostname, password, port):
"""Initialize the service."""
from gntp import notifier
self.gntp = notifier.GrowlNotifier(
applicationName=app_name,
notifications=["Notification"],
applicationIcon=app_icon,
hostname=hostname,
password=password,
port=port
)
self.gntp.register()
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
self.gntp.notify(noteType="Notification", title=kwargs.get(ATTR_TITLE),
description=message)

View File

@ -0,0 +1,87 @@
"""
MessageBird platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.message_bird/
"""
import logging
from homeassistant.components.notify import (
ATTR_TARGET, DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
CONF_SENDER = 'sender'
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['messagebird==1.1.1']
def is_valid_sender(sender):
"""Test if the sender config option is valid."""
length = len(sender)
if length > 1:
if sender[0] == '+':
return sender[1:].isdigit()
elif length <= 11:
return sender.isalpha()
return False
# pylint: disable=unused-argument
def get_service(hass, config):
"""Get the MessageBird notification service."""
import messagebird
if not validate_config({DOMAIN: config},
{DOMAIN: [CONF_API_KEY]},
_LOGGER):
return None
sender = config.get(CONF_SENDER, 'HA')
if not is_valid_sender(sender):
_LOGGER.error('Sender is invalid: It must be a phone number or '
'a string not longer than 11 characters.')
return None
client = messagebird.Client(config[CONF_API_KEY])
try:
# validates the api key
client.balance()
except messagebird.client.ErrorException:
_LOGGER.error('The specified MessageBird API key is invalid.')
return None
return MessageBirdNotificationService(sender, client)
# pylint: disable=too-few-public-methods
class MessageBirdNotificationService(BaseNotificationService):
"""Implement the notification service for MessageBird."""
def __init__(self, sender, client):
"""Initialize the service."""
self.sender = sender
self.client = client
def send_message(self, message=None, **kwargs):
"""Send a message to a specified target."""
from messagebird.client import ErrorException
targets = kwargs.get(ATTR_TARGET)
if not targets:
_LOGGER.error('No target specified.')
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
try:
self.client.message_create(self.sender,
target,
message,
{'reference': 'HA'})
except ErrorException as exception:
_LOGGER.error('Failed to notify %s: %s', target, exception)
continue

View File

@ -14,7 +14,7 @@ from homeassistant.helpers import validate_config
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-telegram-bot==3.2.0'] REQUIREMENTS = ['python-telegram-bot==3.4']
def get_service(hass, config): def get_service(hass, config):

View File

@ -7,9 +7,9 @@ https://home-assistant.io/components/rfxtrx/
import logging import logging
from homeassistant.util import slugify from homeassistant.util import slugify
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/' + REQUIREMENTS = ['pyRFXtrx==0.6.5']
'archive/0.5.zip#pyRFXtrx==0.5']
DOMAIN = "rfxtrx" DOMAIN = "rfxtrx"
@ -72,6 +72,10 @@ def setup(hass, config):
else: else:
RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug) RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug)
def _shutdown_rfxtrx(event):
RFXOBJECT.close_connection()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown_rfxtrx)
return True return True

View File

@ -19,7 +19,8 @@ from homeassistant.const import (
from homeassistant.helpers.entity import ToggleEntity, split_entity_id from homeassistant.helpers.entity import ToggleEntity, split_entity_id
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.service import call_from_config from homeassistant.helpers.service import (call_from_config,
validate_service_call)
from homeassistant.util import slugify from homeassistant.util import slugify
DOMAIN = "script" DOMAIN = "script"
@ -30,9 +31,7 @@ STATE_NOT_RUNNING = 'Not Running'
CONF_ALIAS = "alias" CONF_ALIAS = "alias"
CONF_SERVICE = "service" CONF_SERVICE = "service"
CONF_SERVICE_OLD = "execute_service"
CONF_SERVICE_DATA = "data" CONF_SERVICE_DATA = "data"
CONF_SERVICE_DATA_OLD = "service_data"
CONF_SEQUENCE = "sequence" CONF_SEQUENCE = "sequence"
CONF_EVENT = "event" CONF_EVENT = "event"
CONF_EVENT_DATA = "event_data" CONF_EVENT_DATA = "event_data"
@ -174,7 +173,7 @@ class Script(ToggleEntity):
for cur, action in islice(enumerate(self.sequence), self._cur, for cur, action in islice(enumerate(self.sequence), self._cur,
None): None):
if CONF_SERVICE in action or CONF_SERVICE_OLD in action: if validate_service_call(action) is None:
self._call_service(action) self._call_service(action)
elif CONF_EVENT in action: elif CONF_EVENT in action:
@ -211,14 +210,7 @@ class Script(ToggleEntity):
def _call_service(self, action): def _call_service(self, action):
"""Call the service specified in the action.""" """Call the service specified in the action."""
# Backwards compatibility self._last_action = action.get(CONF_ALIAS, 'call service')
if CONF_SERVICE not in action and CONF_SERVICE_OLD in action:
action[CONF_SERVICE] = action[CONF_SERVICE_OLD]
if CONF_SERVICE_DATA not in action and CONF_SERVICE_DATA_OLD in action:
action[CONF_SERVICE_DATA] = action[CONF_SERVICE_DATA_OLD]
self._last_action = action.get(CONF_ALIAS, action[CONF_SERVICE])
_LOGGER.info("Executing script %s step %s", self._name, _LOGGER.info("Executing script %s step %s", self._name,
self._last_action) self._last_action)
call_from_config(self.hass, action, True) call_from_config(self.hass, action, True)

View File

@ -8,7 +8,8 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import ( from homeassistant.components import (
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, bloomsky) wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors,
bloomsky, vera)
DOMAIN = 'sensor' DOMAIN = 'sensor'
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
@ -25,6 +26,7 @@ DISCOVERY_PLATFORMS = {
ecobee.DISCOVER_SENSORS: 'ecobee', ecobee.DISCOVER_SENSORS: 'ecobee',
tellduslive.DISCOVER_SENSORS: 'tellduslive', tellduslive.DISCOVER_SENSORS: 'tellduslive',
mysensors.DISCOVER_SENSORS: 'mysensors', mysensors.DISCOVER_SENSORS: 'mysensors',
vera.DISCOVER_SENSORS: 'vera',
} }

View File

@ -1,5 +1,5 @@
""" """
Bitcoin information service that uses blockchain.info and its online wallet. Bitcoin information service that uses blockchain.info.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bitcoin/ https://home-assistant.io/components/sensor.bitcoin/
@ -10,10 +10,9 @@ from datetime import timedelta
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['blockchain==1.2.1'] REQUIREMENTS = ['blockchain==1.3.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
OPTION_TYPES = { OPTION_TYPES = {
'wallet': ['Wallet balance', 'BTC'],
'exchangerate': ['Exchange rate (1 BTC)', None], 'exchangerate': ['Exchange rate (1 BTC)', None],
'trade_volume_btc': ['Trade volume', 'BTC'], 'trade_volume_btc': ['Trade volume', 'BTC'],
'miners_revenue_usd': ['Miners revenue', 'USD'], 'miners_revenue_usd': ['Miners revenue', 'USD'],
@ -43,33 +42,17 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Bitcoin sensor.""" """Setup the Bitcoin sensors."""
from blockchain.wallet import Wallet from blockchain import exchangerates
from blockchain import exchangerates, exceptions
wallet_id = config.get('wallet', None)
password = config.get('password', None)
currency = config.get('currency', 'USD') currency = config.get('currency', 'USD')
if currency not in exchangerates.get_ticker(): if currency not in exchangerates.get_ticker():
_LOGGER.error('Currency "%s" is not available. Using "USD".', currency) _LOGGER.error('Currency "%s" is not available. Using "USD"', currency)
currency = 'USD' currency = 'USD'
if wallet_id is not None and password is not None:
wallet = Wallet(wallet_id, password)
try:
wallet.get_balance()
except exceptions.APIException as error:
_LOGGER.error(error)
wallet = None
else:
wallet = None
data = BitcoinData() data = BitcoinData()
dev = [] dev = []
if wallet is not None and password is not None:
dev.append(BitcoinSensor(data, 'wallet', currency, wallet))
for variable in config['display_options']: for variable in config['display_options']:
if variable not in OPTION_TYPES: if variable not in OPTION_TYPES:
_LOGGER.error('Option type: "%s" does not exist', variable) _LOGGER.error('Option type: "%s" does not exist', variable)
@ -83,13 +66,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class BitcoinSensor(Entity): class BitcoinSensor(Entity):
"""Representation of a Bitcoin sensor.""" """Representation of a Bitcoin sensor."""
def __init__(self, data, option_type, currency, wallet=''): def __init__(self, data, option_type, currency):
"""Initialize the sensor.""" """Initialize the sensor."""
self.data = data self.data = data
self._name = OPTION_TYPES[option_type][0] self._name = OPTION_TYPES[option_type][0]
self._unit_of_measurement = OPTION_TYPES[option_type][1] self._unit_of_measurement = OPTION_TYPES[option_type][1]
self._currency = currency self._currency = currency
self._wallet = wallet
self.type = option_type self.type = option_type
self._state = None self._state = None
self.update() self.update()
@ -122,10 +104,7 @@ class BitcoinSensor(Entity):
ticker = self.data.ticker ticker = self.data.ticker
# pylint: disable=no-member # pylint: disable=no-member
if self.type == 'wallet' and self._wallet is not None: if self.type == 'exchangerate':
self._state = '{0:.8f}'.format(self._wallet.get_balance() *
0.00000001)
elif self.type == 'exchangerate':
self._state = ticker[self._currency].p15min self._state = ticker[self._currency].p15min
self._unit_of_measurement = self._currency self._unit_of_measurement = self._currency
elif self.type == 'trade_volume_btc': elif self.type == 'trade_volume_btc':

View File

@ -11,7 +11,7 @@ from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['python-forecastio==1.3.3'] REQUIREMENTS = ['python-forecastio==1.3.4']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Sensor types are defined like so: # Sensor types are defined like so:

View File

@ -61,12 +61,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False return False
except requests.exceptions.MissingSchema: except requests.exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. " _LOGGER.error("Missing resource or schema in configuration. "
"Please check the details in the configuration file.") "Please check the details in the configuration file")
return False return False
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: '%s'. " _LOGGER.error("No route to resource/endpoint: %s", url)
"Please check the details in the configuration file.",
url)
return False return False
rest = GlancesData(url) rest = GlancesData(url)
@ -167,6 +165,5 @@ class GlancesData(object):
response = requests.get(self._resource, timeout=10) response = requests.get(self._resource, timeout=10)
self.data = response.json() self.data = response.json()
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error("No route to host/endpoint '%s'. Is device offline?", _LOGGER.error("No route to host/endpoint: %s", self._resource)
self._resource)
self.data = None self.data = None

View File

@ -32,10 +32,7 @@ JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity',
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V', SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
'kph': 'kph', 'temperature': '°C'} 'kph': 'kph', 'temperature': '°C'}
SENSOR_TEMP_TYPES = ['temperature', SENSOR_TEMP_TYPES = ['temperature', 'target']
'target',
'away_temperature[0]',
'away_temperature[1]']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['psutil==4.0.0'] REQUIREMENTS = ['psutil==4.1.0']
SENSOR_TYPES = { SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'], 'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'], 'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
@ -38,7 +38,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the sensors.""" """Setup the System sensors."""
dev = [] dev = []
for resource in config['resources']: for resource in config['resources']:
if 'arg' not in resource: if 'arg' not in resource:

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.template/
""" """
import logging import logging
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.core import EVENT_STATE_CHANGED
@ -15,11 +15,8 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.util import slugify from homeassistant.util import slugify
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_SENSORS = 'sensors' CONF_SENSORS = 'sensors'
STATE_ERROR = 'error'
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -70,22 +67,22 @@ class SensorTemplate(Entity):
def __init__(self, hass, device_id, friendly_name, unit_of_measurement, def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
state_template): state_template):
"""Initialize the sensor.""" """Initialize the sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass) hass=hass)
self.hass = hass
self._name = friendly_name self._name = friendly_name
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._template = state_template self._template = state_template
self.update() self._state = None
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
def _event_listener(self, event): self.update()
def template_sensor_event_listener(event):
"""Called when the target device changes state.""" """Called when the target device changes state."""
if not hasattr(self, 'hass'):
return
self.update_ha_state(True) self.update_ha_state(True)
hass.bus.listen(EVENT_STATE_CHANGED, template_sensor_event_listener)
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
@ -111,10 +108,10 @@ class SensorTemplate(Entity):
try: try:
self._state = template.render(self.hass, self._template) self._state = template.render(self.hass, self._template)
except TemplateError as ex: except TemplateError as ex:
self._state = STATE_ERROR
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning # Common during HA startup - so just a warning
_LOGGER.warning(ex) _LOGGER.warning(ex)
return return
self._state = None
_LOGGER.error(ex) _LOGGER.error(ex)

View File

@ -0,0 +1,208 @@
"""
Support for the Uber API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.uber/
"""
import logging
from datetime import timedelta
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/denismakogon/rides-python-sdk/archive/'
'py3-support.zip#'
'uber_rides==0.1.2-dev']
ICON = 'mdi:taxi'
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Uber sensor."""
if None in (config.get('start_latitude'), config.get('start_longitude')):
_LOGGER.error(
"You must set start latitude and longitude to use the Uber sensor!"
)
return False
if config.get('server_token') is None:
_LOGGER.error("You must set a server_token to use the Uber sensor!")
return False
from uber_rides.session import Session
session = Session(server_token=config.get('server_token'))
wanted_product_ids = config.get('product_ids')
dev = []
timeandpriceest = UberEstimate(session, config['start_latitude'],
config['start_longitude'],
config.get('end_latitude'),
config.get('end_longitude'))
for product_id, product in timeandpriceest.products.items():
if (wanted_product_ids is not None) and \
(product_id not in wanted_product_ids):
continue
dev.append(UberSensor('time', timeandpriceest, product_id, product))
dev.append(UberSensor('price', timeandpriceest, product_id, product))
add_devices(dev)
# pylint: disable=too-few-public-methods
class UberSensor(Entity):
"""Implementation of an Uber sensor."""
def __init__(self, sensorType, products, product_id, product):
"""Initialize the Uber sensor."""
self.data = products
self._product_id = product_id
self._product = product
self._sensortype = sensorType
self._name = "{} {}".format(self._product['display_name'],
self._sensortype)
if self._sensortype == "time":
self._unit_of_measurement = "min"
time_estimate = self._product.get('time_estimate_seconds', 0)
self._state = int(time_estimate / 60)
elif self._sensortype == "price":
price_details = self._product['price_details']
if price_details['low_estimate'] is None:
self._unit_of_measurement = price_details['currency_code']
self._state = int(price_details['minimum'])
else:
self._unit_of_measurement = price_details['currency_code']
self._state = int(price_details['low_estimate'])
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def device_state_attributes(self):
"""Return the state attributes."""
price_details = self._product['price_details']
distance_key = 'Trip distance (in {}s)'.format(price_details[
'distance_unit'])
distance_val = self._product.get('distance')
if (price_details.get('distance_unit') is None) or \
(self._product.get('distance') is None):
distance_key = 'Trip distance'
distance_val = 'N/A'
time_estimate = self._product['time_estimate_seconds']
return {
'Product ID': self._product['product_id'],
'Product short description': self._product['short_description'],
'Product display name': self._product['display_name'],
'Product description': self._product['description'],
'Pickup time estimate (in seconds)': time_estimate,
'Trip duration (in seconds)': self._product.get('duration', 'N/A'),
distance_key: distance_val,
'Vehicle Capacity': self._product['capacity'],
'Minimum price': price_details['minimum'],
'Cost per minute': price_details['cost_per_minute'],
'Distance units': price_details['distance_unit'],
'Cancellation fee': price_details['cancellation_fee'],
'Cost per distance unit': price_details['cost_per_distance'],
'Base price': price_details['base'],
'Price estimate': price_details.get('estimate', 'N/A'),
'Price currency code': price_details.get('currency_code'),
'High price estimate': price_details.get('high_estimate', 'N/A'),
'Low price estimate': price_details.get('low_estimate', 'N/A'),
'Surge multiplier': price_details.get('surge_multiplier', 'N/A')
}
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return ICON
# pylint: disable=too-many-branches
def update(self):
"""Get the latest data from the Uber API and update the states."""
self.data.update()
self._product = self.data.products[self._product_id]
if self._sensortype == "time":
time_estimate = self._product.get('time_estimate_seconds', 0)
self._state = int(time_estimate / 60)
elif self._sensortype == "price":
price_details = self._product['price_details']
min_price = price_details['minimum']
self._state = int(price_details.get('low_estimate', min_price))
# pylint: disable=too-few-public-methods
class UberEstimate(object):
"""The class for handling the time and price estimate."""
# pylint: disable=too-many-arguments
def __init__(self, session, start_latitude, start_longitude,
end_latitude=None, end_longitude=None):
"""Initialize the UberEstimate object."""
self._session = session
self.start_latitude = start_latitude
self.start_longitude = start_longitude
self.end_latitude = end_latitude
self.end_longitude = end_longitude
self.products = None
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest product info and estimates from the Uber API."""
from uber_rides.client import UberRidesClient
client = UberRidesClient(self._session)
self.products = {}
products_response = client.get_products(
self.start_latitude, self.start_longitude)
products = products_response.json.get('products')
for product in products:
self.products[product['product_id']] = product
if self.end_latitude is not None and self.end_longitude is not None:
price_response = client.get_price_estimates(
self.start_latitude,
self.start_longitude,
self.end_latitude,
self.end_longitude)
prices = price_response.json.get('prices')
for price in prices:
product = self.products[price['product_id']]
price_details = product["price_details"]
product["duration"] = price['duration']
product["distance"] = price['distance']
price_details["estimate"] = price['estimate']
price_details["high_estimate"] = price['high_estimate']
price_details["low_estimate"] = price['low_estimate']
price_details["surge_multiplier"] = price['surge_multiplier']
estimate_response = client.get_pickup_time_estimates(
self.start_latitude, self.start_longitude)
estimates = estimate_response.json.get('times')
for estimate in estimates:
self.products[estimate['product_id']][
"time_estimate_seconds"] = estimate.get('estimate', '0')

View File

@ -6,109 +6,40 @@ https://home-assistant.io/components/sensor.vera/
""" """
import logging import logging
from requests.exceptions import RequestException
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import ( from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
EVENT_HOMEASSISTANT_STOP, TEMP_CELCIUS, TEMP_FAHRENHEIT) TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
REQUIREMENTS = ['pyvera==0.2.8'] DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def get_devices(hass, config):
"""Setup the Vera Sensors."""
import pyvera as veraApi
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
categories = ['Temperature Sensor',
'Light Sensor',
'Humidity Sensor',
'Sensor']
devices = []
try:
devices = vera_controller.get_devices(categories)
except RequestException:
# There was a network related error connecting to the vera controller.
_LOGGER.exception("Error communicating with Vera API")
return False
vera_sensors = []
for device in devices:
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_sensors.append(
VeraSensor(device, vera_controller, extra_data))
return vera_sensors
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Vera controller devices.""" """Perform the setup for Vera controller devices."""
add_devices(get_devices(hass, config)) add_devices_callback(
VeraSensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['sensor'])
class VeraSensor(Entity): class VeraSensor(VeraDevice, Entity):
"""Representation of a Vera Sensor.""" """Representation of a Vera Sensor."""
def __init__(self, vera_device, controller, extra_data=None): def __init__(self, vera_device, controller):
"""Initialize the sensor.""" """Initialize the sensor."""
self.vera_device = vera_device self.current_value = None
self.controller = controller
self.extra_data = extra_data
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self.current_value = ''
self._temperature_units = None self._temperature_units = None
VeraDevice.__init__(self, vera_device, controller)
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
"""Called by the vera device callback to update state."""
self.update_ha_state(True)
def __str__(self):
"""String representation of sensor."""
return "%s %s %s" % (self.name, self.vera_device.device_id, self.state)
@property @property
def state(self): def state(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return self.current_value return self.current_value
@property
def name(self):
"""Return the mame of the sensor."""
return self._name
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
@ -144,11 +75,6 @@ class VeraSensor(Entity):
attr['Vera Device Id'] = self.vera_device.vera_device_id attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr return attr
@property
def should_poll(self):
"""No polling needed."""
return False
def update(self): def update(self):
"""Update the state.""" """Update the state."""
if self.vera_device.category == "Temperature Sensor": if self.vera_device.category == "Temperature Sensor":

View File

@ -10,7 +10,7 @@ from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
STATE_OPEN, TEMP_CELCIUS) STATE_OPEN, TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
SENSOR_TYPES = ['temperature', 'humidity'] SENSOR_TYPES = ['temperature', 'humidity']
@ -74,6 +74,11 @@ class WinkSensorDevice(Entity):
"""Return the name of the sensor if any.""" """Return the name of the sensor if any."""
return self.wink.name() return self.wink.name()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def update(self): def update(self):
"""Update state of the sensor.""" """Update state of the sensor."""
self.wink.update_state() self.wink.update_state()

View File

@ -16,7 +16,8 @@ from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
ATTR_ENTITY_ID) ATTR_ENTITY_ID)
from homeassistant.components import ( from homeassistant.components import (
group, wemo, wink, isy994, verisure, zwave, tellduslive, mysensors) group, wemo, wink, isy994, verisure,
zwave, tellduslive, tellstick, mysensors, vera)
DOMAIN = 'switch' DOMAIN = 'switch'
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
@ -40,6 +41,8 @@ DISCOVERY_PLATFORMS = {
zwave.DISCOVER_SWITCHES: 'zwave', zwave.DISCOVER_SWITCHES: 'zwave',
tellduslive.DISCOVER_SWITCHES: 'tellduslive', tellduslive.DISCOVER_SWITCHES: 'tellduslive',
mysensors.DISCOVER_SWITCHES: 'mysensors', mysensors.DISCOVER_SWITCHES: 'mysensors',
tellstick.DISCOVER_SWITCHES: 'tellstick',
vera.DISCOVER_SWITCHES: 'vera',
} }
PROP_TO_ATTR = { PROP_TO_ATTR = {

View File

@ -28,24 +28,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pins = config.get('pins') pins = config.get('pins')
for pinnum, pin in pins.items(): for pinnum, pin in pins.items():
if pin.get('name'): if pin.get('name'):
switches.append(ArduinoSwitch(pin.get('name'), switches.append(ArduinoSwitch(pinnum, pin))
pinnum,
pin.get('type')))
add_devices(switches) add_devices(switches)
class ArduinoSwitch(SwitchDevice): class ArduinoSwitch(SwitchDevice):
"""Representation of an Arduino switch.""" """Representation of an Arduino switch."""
def __init__(self, name, pin, pin_type): def __init__(self, pin, options):
"""Initialize the Pin.""" """Initialize the Pin."""
self._pin = pin self._pin = pin
self._name = name or DEVICE_DEFAULT_NAME self._name = options.get('name') or DEVICE_DEFAULT_NAME
self.pin_type = pin_type self.pin_type = options.get('type')
self.direction = 'out' self.direction = 'out'
self._state = False
self._state = options.get('initial', False)
if options.get('negate', False):
self.turn_on_handler = arduino.BOARD.set_digital_out_low
self.turn_off_handler = arduino.BOARD.set_digital_out_high
else:
self.turn_on_handler = arduino.BOARD.set_digital_out_high
self.turn_off_handler = arduino.BOARD.set_digital_out_low
arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type)
(self.turn_on_handler if self._state else self.turn_off_handler)(pin)
@property @property
def name(self): def name(self):
@ -60,9 +67,9 @@ class ArduinoSwitch(SwitchDevice):
def turn_on(self): def turn_on(self):
"""Turn the pin to high/on.""" """Turn the pin to high/on."""
self._state = True self._state = True
arduino.BOARD.set_digital_out_high(self._pin) self.turn_on_handler(self._pin)
def turn_off(self): def turn_off(self):
"""Turn the pin to low/off.""" """Turn the pin to low/off."""
self._state = False self._state = False
arduino.BOARD.set_digital_out_low(self._pin) self.turn_off_handler(self._pin)

View File

@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
add_devices_callback(devices) add_devices_callback(devices)
# pylint: disable=too-many-instance-attributes
class CommandSwitch(SwitchDevice): class CommandSwitch(SwitchDevice):
"""Representation a switch that can be toggled using shell commands.""" """Representation a switch that can be toggled using shell commands."""
@ -92,6 +93,11 @@ class CommandSwitch(SwitchDevice):
"""Return true if device is on.""" """Return true if device is on."""
return self._state return self._state
@property
def assumed_state(self):
"""Return true if we do optimistic updates."""
return self._command_state is False
def _query_state(self): def _query_state(self):
"""Query for state.""" """Query for state."""
if not self._command_state: if not self._command_state:

View File

@ -0,0 +1,131 @@
"""
Switch logic for loading/unloading pulseaudio loopback modules.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.pulseaudio_loopback/
"""
import logging
import re
import socket
from homeassistant.components.switch import SwitchDevice
from homeassistant.util import convert
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "paloopback"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 4712
DEFAULT_BUFFER_SIZE = 1024
DEFAULT_TCP_TIMEOUT = 3
LOAD_CMD = "load-module module-loopback sink={0} source={1}"
UNLOAD_CMD = "unload-module {0}"
MOD_REGEX = r"index: ([0-9]+)\s+name: <module-loopback>" \
r"\s+argument: <sink={0} source={1}>"
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Read in all of our configuration, and initialize the loopback switch."""
if config.get('sink_name') is None:
_LOGGER.error("Missing required variable: sink_name")
return False
if config.get('source_name') is None:
_LOGGER.error("Missing required variable: source_name")
return False
add_devices_callback([PALoopbackSwitch(
hass,
convert(config.get('name'), str, DEFAULT_NAME),
convert(config.get('host'), str, DEFAULT_HOST),
convert(config.get('port'), int, DEFAULT_PORT),
convert(config.get('buffer_size'), int, DEFAULT_BUFFER_SIZE),
convert(config.get('tcp_timeout'), int, DEFAULT_TCP_TIMEOUT),
config.get('sink_name'),
config.get('source_name')
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class PALoopbackSwitch(SwitchDevice):
"""Represents the presence or absence of a pa loopback module."""
def __init__(self, hass, name, pa_host, pa_port, buff_sz,
tcp_timeout, sink_name, source_name):
"""Initialize the switch."""
self._module_idx = -1
self._hass = hass
self._name = name
self._pa_host = pa_host
self._pa_port = int(pa_port)
self._sink_name = sink_name
self._source_name = source_name
self._buffer_size = int(buff_sz)
self._tcp_timeout = int(tcp_timeout)
@property
def name(self):
"""Return the name of the switch."""
return self._name
@property
def is_on(self):
"""Tell the core logic if device is on."""
return self._module_idx > 0
def _send_command(self, cmd, response_expected):
"""Send a command to the pa server using a socket."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self._tcp_timeout)
try:
sock.connect((self._pa_host, self._pa_port))
_LOGGER.info("Calling pulseaudio:" + cmd)
sock.send((cmd + "\n").encode("utf-8"))
if response_expected:
return_data = self._get_full_response(sock)
_LOGGER.debug("Data received from pulseaudio: " + return_data)
else:
return_data = ""
finally:
sock.close()
return return_data
def _get_full_response(self, sock):
"""Helper method to get the full response back from pulseaudio."""
result = ""
rcv_buffer = sock.recv(self._buffer_size)
result += rcv_buffer.decode("utf-8")
while len(rcv_buffer) == self._buffer_size:
rcv_buffer = sock.recv(self._buffer_size)
result += rcv_buffer.decode("utf-8")
return result
def turn_on(self, **kwargs):
"""Turn the device on."""
self._send_command(str.format(LOAD_CMD,
self._sink_name,
self._source_name),
False)
self.update()
self.update_ha_state()
def turn_off(self, **kwargs):
"""Turn the device off."""
self._send_command(str.format(UNLOAD_CMD, self._module_idx), False)
self.update()
self.update_ha_state()
def update(self):
"""Refresh state in case an alternate process modified this data."""
return_data = self._send_command("list-modules", True)
result = re.search(str.format(MOD_REGEX,
re.escape(self._sink_name),
re.escape(self._source_name)),
return_data)
if result and result.group(1).isdigit():
self._module_idx = int(result.group(1))
else:
self._module_idx = -1

View File

@ -4,98 +4,56 @@ Support for Tellstick switches.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.tellstick/ https://home-assistant.io/components/switch.tellstick/
""" """
import logging from homeassistant.components import tellstick
from homeassistant.components.tellstick import (ATTR_DISCOVER_DEVICES,
from homeassistant.const import EVENT_HOMEASSISTANT_STOP ATTR_DISCOVER_CONFIG)
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
SIGNAL_REPETITIONS = 1
REQUIREMENTS = ['tellcore-py==1.1.2']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Tellstick switches.""" """Setup Tellstick switches."""
import tellcore.telldus as telldus if (discovery_info is None or
import tellcore.constants as tellcore_constants discovery_info[ATTR_DISCOVER_DEVICES] is None or
from tellcore.library import DirectCallbackDispatcher tellstick.TELLCORE_REGISTRY is None):
return
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher()) # Allow platform level override, fallback to module config
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS) signal_repetitions = discovery_info.get(
switches_and_lights = core.devices() ATTR_DISCOVER_CONFIG, tellstick.DEFAULT_SIGNAL_REPETITIONS)
switches = [] add_devices(TellstickSwitchDevice(
for switch in switches_and_lights: tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
if not switch.methods(tellcore_constants.TELLSTICK_DIM): for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
switches.append(
TellstickSwitchDevice(switch, signal_repetitions))
def _device_event_callback(id_, method, data, cid):
"""Called from the TelldusCore library to update one device."""
for switch_device in switches:
if switch_device.tellstick_device.id == id_:
switch_device.update_ha_state()
break
callback_id = core.register_device_event(_device_event_callback)
def unload_telldus_lib(event):
"""Un-register the callback bindings."""
if callback_id is not None:
core.unregister_callback(callback_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
add_devices_callback(switches)
class TellstickSwitchDevice(ToggleEntity): class TellstickSwitchDevice(tellstick.TellstickDevice, ToggleEntity):
"""Representation of a Tellstick switch.""" """Representation of a Tellstick switch."""
def __init__(self, tellstick_device, signal_repetitions):
"""Initialize the Tellstick switch."""
import tellcore.constants as tellcore_constants
self.tellstick_device = tellstick_device
self.signal_repetitions = signal_repetitions
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
tellcore_constants.TELLSTICK_TURNOFF)
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def assumed_state(self):
"""The Tellstick devices are always assumed state."""
return True
@property
def name(self):
"""Return the name of the switch if any."""
return self.tellstick_device.name
@property @property
def is_on(self): def is_on(self):
"""Return true if switch is on.""" """Return true if switch is on."""
import tellcore.constants as tellcore_constants return self._state
last_command = self.tellstick_device.last_sent_command( def set_tellstick_state(self, last_command_sent, last_data_sent):
self.last_sent_command_mask) """Update the internal representation of the switch."""
from tellcore.constants import TELLSTICK_TURNON
self._state = last_command_sent == TELLSTICK_TURNON
return last_command == tellcore_constants.TELLSTICK_TURNON def _send_tellstick_command(self, command, data):
"""Handle the turn_on / turn_off commands."""
from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_TURNOFF
if command == TELLSTICK_TURNON:
self.tellstick_device.turn_on()
elif command == TELLSTICK_TURNOFF:
self.tellstick_device.turn_off()
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""
for _ in range(self.signal_repetitions): from tellcore.constants import TELLSTICK_TURNON
self.tellstick_device.turn_on() self.call_tellstick(TELLSTICK_TURNON)
self.update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the switch off.""" """Turn the switch off."""
for _ in range(self.signal_repetitions): from tellcore.constants import TELLSTICK_TURNOFF
self.tellstick_device.turn_off() self.call_tellstick(TELLSTICK_TURNOFF)
self.update_ha_state()

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.template/
""" """
import logging import logging
from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON) ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON)
from homeassistant.core import EVENT_STATE_CHANGED from homeassistant.core import EVENT_STATE_CHANGED
@ -16,17 +16,14 @@ from homeassistant.helpers.service import call_from_config
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.util import slugify from homeassistant.util import slugify
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_SWITCHES = 'switches' CONF_SWITCHES = 'switches'
STATE_ERROR = 'error'
ON_ACTION = 'turn_on' ON_ACTION = 'turn_on'
OFF_ACTION = 'turn_off' OFF_ACTION = 'turn_off'
_LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false']
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -84,32 +81,44 @@ class SwitchTemplate(SwitchDevice):
def __init__(self, hass, device_id, friendly_name, state_template, def __init__(self, hass, device_id, friendly_name, state_template,
on_action, off_action): on_action, off_action):
"""Initialize the Template switch.""" """Initialize the Template switch."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
hass=hass) hass=hass)
self.hass = hass
self._name = friendly_name self._name = friendly_name
self._template = state_template self._template = state_template
self._on_action = on_action self._on_action = on_action
self._off_action = off_action self._off_action = off_action
self.update() self._state = False
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
def _event_listener(self, event): self.update()
def template_switch_event_listener(event):
"""Called when the target device changes state.""" """Called when the target device changes state."""
if not hasattr(self, 'hass'):
return
self.update_ha_state(True) self.update_ha_state(True)
hass.bus.listen(EVENT_STATE_CHANGED,
template_switch_event_listener)
@property @property
def name(self): def name(self):
"""Return the name of the switch.""" """Return the name of the switch."""
return self._name return self._name
@property
def is_on(self):
"""Return true if device is on."""
return self._state
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed.""" """No polling needed."""
return False return False
@property
def available(self):
"""If switch is available."""
return self._state is not None
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Fire the on action.""" """Fire the on action."""
call_from_config(self.hass, self._on_action, True) call_from_config(self.hass, self._on_action, True)
@ -118,30 +127,19 @@ class SwitchTemplate(SwitchDevice):
"""Fire the off action.""" """Fire the off action."""
call_from_config(self.hass, self._off_action, True) call_from_config(self.hass, self._off_action, True)
@property
def is_on(self):
"""Return true if device is on."""
return self._value.lower() == 'true' or self._value == STATE_ON
@property
def is_off(self):
"""Return true if device is off."""
return self._value.lower() == 'false' or self._value == STATE_OFF
@property
def available(self):
"""Return true if entity is available."""
return self.is_on or self.is_off
def update(self): def update(self):
"""Update the state from the template.""" """Update the state from the template."""
try: try:
self._value = template.render(self.hass, self._template) state = template.render(self.hass, self._template).lower()
if not self.available:
if state in _VALID_STATES:
self._state = state in ('true', STATE_ON)
else:
_LOGGER.error( _LOGGER.error(
"`%s` is not a switch state, setting %s to unavailable", 'Received invalid switch is_on state: %s. Expected: %s',
self._value, self.entity_id) state, ', '.join(_VALID_STATES))
self._state = None
except TemplateError as ex: except TemplateError as ex:
self._value = STATE_ERROR
_LOGGER.error(ex) _LOGGER.error(ex)
self._state = None

View File

@ -6,94 +6,33 @@ https://home-assistant.io/components/switch.vera/
""" """
import logging import logging
from requests.exceptions import RequestException
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ( from homeassistant.const import (
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) STATE_OFF, STATE_ON)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
REQUIREMENTS = ['pyvera==0.2.8'] DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def get_devices(hass, config):
"""Find and return Vera switches.""" """Find and return Vera switches."""
import pyvera as veraApi add_devices_callback(
VeraSwitch(device, VERA_CONTROLLER) for
base_url = config.get('vera_controller_url') device in VERA_DEVICES['switch'])
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
device_data = config.get('device_data', {})
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
devices = []
try:
devices = vera_controller.get_devices([
'Switch', 'Armable Sensor', 'On/Off Switch'])
except RequestException:
# There was a network related error connecting to the vera controller.
_LOGGER.exception("Error communicating with Vera API")
return False
vera_switches = []
for device in devices:
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_switches.append(
VeraSwitch(device, vera_controller, extra_data))
return vera_switches
def setup_platform(hass, config, add_devices, discovery_info=None): class VeraSwitch(VeraDevice, SwitchDevice):
"""Find and return Vera lights."""
add_devices(get_devices(hass, config))
class VeraSwitch(SwitchDevice):
"""Representation of a Vera Switch.""" """Representation of a Vera Switch."""
def __init__(self, vera_device, controller, extra_data=None): def __init__(self, vera_device, controller):
"""Initialize the Vera device.""" """Initialize the Vera device."""
self.vera_device = vera_device self._state = False
self.extra_data = extra_data VeraDevice.__init__(self, vera_device, controller)
self.controller = controller
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self._state = STATE_OFF
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
self.update_ha_state(True)
@property
def name(self):
"""Return the mame of the switch."""
return self._name
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -134,19 +73,11 @@ class VeraSwitch(SwitchDevice):
self._state = STATE_OFF self._state = STATE_OFF
self.update_ha_state() self.update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
return self._state == STATE_ON return self._state
def update(self): def update(self):
"""Called by the vera device callback to update state.""" """Called by the vera device callback to update state."""
if self.vera_device.is_switched_on(): self._state = self.vera_device.is_switched_on()
self._state = STATE_ON
else:
self._state = STATE_OFF

View File

@ -0,0 +1,79 @@
"""
Support for wake on lan.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.wake_on_lan/
"""
import logging
import platform
import subprocess as sp
from homeassistant.components.switch import SwitchDevice
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['wakeonlan==0.2.2']
DEFAULT_NAME = "Wake on LAN"
DEFAULT_PING_TIMEOUT = 1
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add wake on lan switch."""
if config.get('mac_address') is None:
_LOGGER.error("Missing required variable: mac_address")
return False
add_devices_callback([WOLSwitch(
hass,
config.get('name', DEFAULT_NAME),
config.get('host'),
config.get('mac_address'),
)])
class WOLSwitch(SwitchDevice):
"""Representation of a wake on lan switch."""
def __init__(self, hass, name, host, mac_address):
"""Initialize the WOL switch."""
from wakeonlan import wol
self._hass = hass
self._name = name
self._host = host
self._mac_address = mac_address
self._state = False
self._wol = wol
self.update()
@property
def should_poll(self):
"""Poll for status regularly."""
return True
@property
def is_on(self):
"""True if switch is on."""
return self._state
@property
def name(self):
"""The name of the switch."""
return self._name
def turn_on(self):
"""Turn the device on."""
self._wol.send_magic_packet(self._mac_address)
self.update_ha_state()
def update(self):
"""Check if device is on and update the state."""
if platform.system().lower() == "windows":
ping_cmd = "ping -n 1 -w {} {}"\
.format(DEFAULT_PING_TIMEOUT * 1000, self._host)
else:
ping_cmd = "ping -c 1 -W {} {}"\
.format(DEFAULT_PING_TIMEOUT, self._host)
status = sp.getstatusoutput(ping_cmd)[0]
self._state = not bool(status)

View File

@ -63,6 +63,9 @@ class WemoSwitch(SwitchDevice):
_LOGGER.info( _LOGGER.info(
'Subscription update for %s', 'Subscription update for %s',
_device) _device)
if not hasattr(self, 'hass'):
self.update()
return
self.update_ha_state(True) self.update_ha_state(True)
@property @property

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components.wink import WinkToggleDevice from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -0,0 +1,217 @@
"""
Tellstick Component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/Tellstick/
"""
import logging
import threading
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE,
EVENT_PLATFORM_DISCOVERED, EVENT_HOMEASSISTANT_STOP)
from homeassistant.loader import get_component
from homeassistant.helpers.entity import Entity
DOMAIN = "tellstick"
REQUIREMENTS = ['tellcore-py==1.1.2']
_LOGGER = logging.getLogger(__name__)
ATTR_SIGNAL_REPETITIONS = "signal_repetitions"
DEFAULT_SIGNAL_REPETITIONS = 1
DISCOVER_SWITCHES = "tellstick.switches"
DISCOVER_LIGHTS = "tellstick.lights"
DISCOVERY_TYPES = {"switch": DISCOVER_SWITCHES,
"light": DISCOVER_LIGHTS}
ATTR_DISCOVER_DEVICES = "devices"
ATTR_DISCOVER_CONFIG = "config"
# Use a global tellstick domain lock to handle
# tellcore errors then calling to concurrently
TELLSTICK_LOCK = threading.Lock()
# Keep a reference the the callback registry
# Used from entities that register callback listeners
TELLCORE_REGISTRY = None
def _discover(hass, config, found_devices, component_name):
"""Setup and send the discovery event."""
if not len(found_devices):
return
_LOGGER.info("discovered %d new %s devices",
len(found_devices), component_name)
component = get_component(component_name)
bootstrap.setup_component(hass, component.DOMAIN,
config)
signal_repetitions = config[DOMAIN].get(
ATTR_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS)
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: DISCOVERY_TYPES[component_name],
ATTR_DISCOVERED: {ATTR_DISCOVER_DEVICES: found_devices,
ATTR_DISCOVER_CONFIG:
signal_repetitions}})
def setup(hass, config):
"""Setup the Tellstick component."""
# pylint: disable=global-statement, import-error
global TELLCORE_REGISTRY
import tellcore.telldus as telldus
import tellcore.constants as tellcore_constants
from tellcore.library import DirectCallbackDispatcher
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
TELLCORE_REGISTRY = TellstickRegistry(hass, core)
devices = core.devices()
# Register devices
TELLCORE_REGISTRY.register_devices(devices)
# Discover the switches
_discover(hass, config, [switch.id for switch in
devices if not switch.methods(
tellcore_constants.TELLSTICK_DIM)],
"switch")
# Discover the lights
_discover(hass, config, [light.id for light in
devices if light.methods(
tellcore_constants.TELLSTICK_DIM)],
"light")
return True
class TellstickRegistry:
"""Handle everything around tellstick callbacks.
Keeps a map device ids to home-assistant entities.
Also responsible for registering / cleanup of callbacks.
All device specific logic should be elsewhere (Entities).
"""
def __init__(self, hass, tellcore_lib):
"""Init the tellstick mappings and callbacks."""
self._core_lib = tellcore_lib
# used when map callback device id to ha entities.
self._id_to_entity_map = {}
self._id_to_device_map = {}
self._setup_device_callback(hass, tellcore_lib)
def _device_callback(self, tellstick_id, method, data, cid):
"""Handle the actual callback from tellcore."""
entity = self._id_to_entity_map.get(tellstick_id, None)
if entity is not None:
entity.set_tellstick_state(method, data)
entity.update_ha_state()
def _setup_device_callback(self, hass, tellcore_lib):
"""Register the callback handler."""
callback_id = tellcore_lib.register_device_event(
self._device_callback)
def clean_up_callback(event):
"""Unregister the callback bindings."""
if callback_id is not None:
tellcore_lib.unregister_callback(callback_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, clean_up_callback)
def register_entity(self, tellcore_id, entity):
"""Register a new entity to receive callback updates."""
self._id_to_entity_map[tellcore_id] = entity
def register_devices(self, devices):
"""Register a list of devices."""
self._id_to_device_map.update({device.id:
device for device in devices})
def get_device(self, tellcore_id):
"""Return a device by tellcore_id."""
return self._id_to_device_map.get(tellcore_id, None)
class TellstickDevice(Entity):
"""Represents a Tellstick device.
Contains the common logic for all Tellstick devices.
"""
def __init__(self, tellstick_device, signal_repetitions):
"""Init the tellstick device."""
self.signal_repetitions = signal_repetitions
self._state = None
self.tellstick_device = tellstick_device
# add to id to entity mapping
TELLCORE_REGISTRY.register_entity(tellstick_device.id, self)
# Query tellcore for the current state
self.update()
@property
def should_poll(self):
"""Tell Home Assistant not to poll this entity."""
return False
@property
def assumed_state(self):
"""Tellstick devices are always assumed state."""
return True
@property
def name(self):
"""Return the name of the switch if any."""
return self.tellstick_device.name
def set_tellstick_state(self, last_command_sent, last_data_sent):
"""Set the private switch state."""
raise NotImplementedError(
"set_tellstick_state needs to be implemented.")
def _send_tellstick_command(self, command, data):
"""Do the actual call to the tellstick device."""
raise NotImplementedError(
"_call_tellstick needs to be implemented.")
def call_tellstick(self, command, data=None):
"""Send a command to the device."""
from tellcore.library import TelldusError
with TELLSTICK_LOCK:
try:
for _ in range(self.signal_repetitions):
self._send_tellstick_command(command, data)
# Update the internal state
self.set_tellstick_state(command, data)
self.update_ha_state()
except TelldusError:
_LOGGER.error(TelldusError)
def update(self):
"""Poll the current state of the device."""
import tellcore.constants as tellcore_constants
from tellcore.library import TelldusError
try:
last_command = self.tellstick_device.last_sent_command(
tellcore_constants.TELLSTICK_TURNON |
tellcore_constants.TELLSTICK_TURNOFF |
tellcore_constants.TELLSTICK_DIM
)
last_value = self.tellstick_device.last_sent_value()
self.set_tellstick_state(last_command, last_value)
except TelldusError:
_LOGGER.error(TelldusError)

View File

@ -123,6 +123,9 @@ def setup(hass, config):
service.data.get(ATTR_TEMPERATURE), float) service.data.get(ATTR_TEMPERATURE), float)
if temperature is None: if temperature is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
return return
for thermostat in target_thermostats: for thermostat in target_thermostats:

View File

@ -11,8 +11,8 @@ from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Demo thermostats.""" """Setup the Demo thermostats."""
add_devices([ add_devices([
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19), DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19, False),
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77), DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77, True),
]) ])
@ -21,13 +21,14 @@ class DemoThermostat(ThermostatDevice):
"""Representation of a demo thermostat.""" """Representation of a demo thermostat."""
def __init__(self, name, target_temperature, unit_of_measurement, def __init__(self, name, target_temperature, unit_of_measurement,
away, current_temperature): away, current_temperature, is_fan_on):
"""Initialize the thermostat.""" """Initialize the thermostat."""
self._name = name self._name = name
self._target_temperature = target_temperature self._target_temperature = target_temperature
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._away = away self._away = away
self._current_temperature = current_temperature self._current_temperature = current_temperature
self._is_fan_on = is_fan_on
@property @property
def should_poll(self): def should_poll(self):
@ -59,6 +60,11 @@ class DemoThermostat(ThermostatDevice):
"""Return if away mode is on.""" """Return if away mode is on."""
return self._away return self._away
@property
def is_fan_on(self):
"""Return true if the fan is on."""
return self._is_fan_on
def set_temperature(self, temperature): def set_temperature(self, temperature):
"""Set new target temperature.""" """Set new target temperature."""
self._target_temperature = temperature self._target_temperature = temperature
@ -70,3 +76,11 @@ class DemoThermostat(ThermostatDevice):
def turn_away_mode_off(self): def turn_away_mode_off(self):
"""Turn away mode off.""" """Turn away mode off."""
self._away = False self._away = False
def turn_fan_on(self):
"""Turn fan on."""
self._is_fan_on = True
def turn_fan_off(self):
"""Turn fan off."""
self._is_fan_on = False

View File

@ -0,0 +1,142 @@
"""
Support for Vera devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/vera/
"""
import logging
from collections import defaultdict
from requests.exceptions import RequestException
from homeassistant import bootstrap
from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED,
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED)
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['pyvera==0.2.8']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'vera'
VERA_CONTROLLER = None
CONF_EXCLUDE = 'exclude'
CONF_LIGHTS = 'lights'
BINARY_SENSOR = 'binary_sensor'
SENSOR = 'sensor'
LIGHT = 'light'
SWITCH = 'switch'
DEVICE_CATEGORIES = {
'Sensor': BINARY_SENSOR,
'Temperature Sensor': SENSOR,
'Light Sensor': SENSOR,
'Humidity Sensor': SENSOR,
'Dimmable Switch': LIGHT,
'Switch': SWITCH,
'Armable Sensor': SWITCH,
'On/Off Switch': SWITCH,
# 'Window Covering': NOT SUPPORTED YET
}
DISCOVER_BINARY_SENSORS = 'vera.binary_sensors'
DISCOVER_SENSORS = 'vera.sensors'
DISCOVER_LIGHTS = 'vera.lights'
DISCOVER_SWITCHES = 'vera.switchs'
VERA_DEVICES = defaultdict(list)
# pylint: disable=unused-argument, too-many-function-args
def setup(hass, base_config):
"""Common setup for Vera devices."""
global VERA_CONTROLLER
import pyvera as veraApi
config = base_config.get(DOMAIN)
base_url = config.get('vera_controller_url')
if not base_url:
_LOGGER.error(
"The required parameter 'vera_controller_url'"
" was not found in config"
)
return False
VERA_CONTROLLER, _ = veraApi.init_controller(base_url)
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions.")
VERA_CONTROLLER.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
try:
all_devices = VERA_CONTROLLER.get_devices(
list(DEVICE_CATEGORIES.keys()))
except RequestException:
# There was a network related error connecting to the vera controller.
_LOGGER.exception("Error communicating with Vera API")
return False
exclude = config.get(CONF_EXCLUDE, [])
if not isinstance(exclude, list):
_LOGGER.error("'exclude' must be a list of device_ids")
return False
lights_ids = config.get(CONF_LIGHTS, [])
if not isinstance(lights_ids, list):
_LOGGER.error("'lights' must be a list of device_ids")
return False
for device in all_devices:
if device.device_id in exclude:
continue
dev_type = DEVICE_CATEGORIES.get(device.category)
if dev_type is None:
continue
if dev_type == SWITCH and device.device_id in lights_ids:
dev_type = LIGHT
VERA_DEVICES[dev_type].append(device)
for comp_name, discovery in (((BINARY_SENSOR, DISCOVER_BINARY_SENSORS),
(SENSOR, DISCOVER_SENSORS),
(LIGHT, DISCOVER_LIGHTS),
(SWITCH, DISCOVER_SWITCHES))):
component = get_component(comp_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery,
ATTR_DISCOVERED: {}})
return True
class VeraDevice(Entity):
"""Representation of a Vera devicetity."""
def __init__(self, vera_device, controller):
"""Initialize the device."""
self.vera_device = vera_device
self.controller = controller
self._name = self.vera_device.name
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
self.update_ha_state(True)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False

View File

@ -23,7 +23,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
DISCOVER_ALARMS = 'verisure.alarm_control_panel' DISCOVER_ALARMS = 'verisure.alarm_control_panel'
DISCOVER_LOCKS = 'verisure.lock' DISCOVER_LOCKS = 'verisure.lock'
REQUIREMENTS = ['vsure==0.6.1'] REQUIREMENTS = ['vsure==0.7.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -9,7 +9,7 @@ import logging
from homeassistant.components import discovery from homeassistant.components import discovery
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['pywemo==0.3.12'] REQUIREMENTS = ['pywemo==0.3.19']
DOMAIN = 'wemo' DOMAIN = 'wemo'
DISCOVER_LIGHTS = 'wemo.light' DISCOVER_LIGHTS = 'wemo.light'

View File

@ -15,7 +15,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.loader import get_component from homeassistant.loader import get_component
DOMAIN = "wink" DOMAIN = "wink"
REQUIREMENTS = ['python-wink==0.6.2'] REQUIREMENTS = ['python-wink==0.6.4']
DISCOVER_LIGHTS = "wink.lights" DISCOVER_LIGHTS = "wink.lights"
DISCOVER_SWITCHES = "wink.switches" DISCOVER_SWITCHES = "wink.switches"
@ -84,6 +84,11 @@ class WinkToggleDevice(ToggleEntity):
"""Return true if device is on.""" """Return true if device is on."""
return self.wink.state() return self.wink.state()
@property
def available(self):
"""True if connection == True."""
return self.wink.available
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
self.wink.set_state(True) self.wink.set_state(True)

View File

@ -28,6 +28,8 @@ DEFAULT_ZWAVE_CONFIG_PATH = os.path.join(sys.prefix, 'share',
SERVICE_ADD_NODE = "add_node" SERVICE_ADD_NODE = "add_node"
SERVICE_REMOVE_NODE = "remove_node" SERVICE_REMOVE_NODE = "remove_node"
SERVICE_HEAL_NETWORK = "heal_network"
SERVICE_SOFT_RESET = "soft_reset"
DISCOVER_SENSORS = "zwave.sensors" DISCOVER_SENSORS = "zwave.sensors"
DISCOVER_SWITCHES = "zwave.switch" DISCOVER_SWITCHES = "zwave.switch"
@ -149,6 +151,7 @@ def get_config_value(node, value_index):
return get_config_value(node, value_index) return get_config_value(node, value_index)
# pylint: disable=R0914
def setup(hass, config): def setup(hass, config):
"""Setup Z-Wave. """Setup Z-Wave.
@ -249,6 +252,14 @@ def setup(hass, config):
"""Switch into exclusion mode.""" """Switch into exclusion mode."""
NETWORK.controller.begin_command_remove_device() NETWORK.controller.begin_command_remove_device()
def heal_network(event):
"""Heal the network."""
NETWORK.heal()
def soft_reset(event):
"""Soft reset the controller."""
NETWORK.controller.soft_reset()
def stop_zwave(event): def stop_zwave(event):
"""Stop Z-Wave.""" """Stop Z-Wave."""
NETWORK.stop() NETWORK.stop()
@ -268,6 +279,8 @@ def setup(hass, config):
# hardware inclusion button # hardware inclusion button
hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_node) hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_node)
hass.services.register(DOMAIN, SERVICE_REMOVE_NODE, remove_node) hass.services.register(DOMAIN, SERVICE_REMOVE_NODE, remove_node)
hass.services.register(DOMAIN, SERVICE_HEAL_NETWORK, heal_network)
hass.services.register(DOMAIN, SERVICE_SOFT_RESET, soft_reset)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)

View File

@ -1,7 +1,7 @@
# coding: utf-8 # coding: utf-8
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
__version__ = "0.15.0" __version__ = "0.16.0"
REQUIRED_PYTHON_VER = (3, 4) REQUIRED_PYTHON_VER = (3, 4)
# Can be used to specify a catch all when registering state or event listeners. # Can be used to specify a catch all when registering state or event listeners.

View File

@ -775,7 +775,7 @@ def create_timer(hass, interval=TIMER_INTERVAL):
def start_timer(event): def start_timer(event):
"""Start the timer.""" """Start the timer."""
thread = threading.Thread(target=timer) thread = threading.Thread(target=timer, name='Timer')
thread.daemon = True thread.daemon = True
thread.start() thread.start()

View File

@ -18,7 +18,7 @@ ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None): def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
"""Generate a unique entity ID based on given entity IDs or used ids.""" """Generate a unique entity ID based on given entity IDs or used IDs."""
name = (name or DEVICE_DEFAULT_NAME).lower() name = (name or DEVICE_DEFAULT_NAME).lower()
if current_ids is None: if current_ids is None:
if hass is None: if hass is None:
@ -41,12 +41,12 @@ def valid_entity_id(entity_id):
class Entity(object): class Entity(object):
"""ABC for Home Assistant entities.""" """An abstract class for Home Assistant entities."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
# SAFE TO OVERWRITE # SAFE TO OVERWRITE
# The properties and methods here are safe to overwrite when inherting this # The properties and methods here are safe to overwrite when inheriting
# class. These may be used to customize the behavior of the entity. # this class. These may be used to customize the behavior of the entity.
@property @property
def should_poll(self): def should_poll(self):
"""Return True if entity has to be polled for state. """Return True if entity has to be polled for state.
@ -57,7 +57,7 @@ class Entity(object):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique id.""" """Return an unique ID."""
return "{}.{}".format(self.__class__, id(self)) return "{}.{}".format(self.__class__, id(self))
@property @property
@ -113,7 +113,7 @@ class Entity(object):
@property @property
def assumed_state(self): def assumed_state(self):
"""Return True if unable to access real state of entity.""" """Return True if unable to access real state of the entity."""
return False return False
def update(self): def update(self):
@ -223,7 +223,7 @@ class Entity(object):
class ToggleEntity(Entity): class ToggleEntity(Entity):
"""ABC for entities that can be turned on and off.""" """An abstract class for entities that can be turned on and off."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
@property @property

View File

@ -331,7 +331,9 @@ class ThreadPool(object):
if not self.running: if not self.running:
raise RuntimeError("ThreadPool not running") raise RuntimeError("ThreadPool not running")
worker = threading.Thread(target=self._worker) worker = threading.Thread(
target=self._worker,
name='ThreadPool Worker {}'.format(self.worker_count))
worker.daemon = True worker.daemon = True
worker.start() worker.start()

View File

@ -31,7 +31,7 @@ astral==0.9
blinkstick==1.1.7 blinkstick==1.1.7
# homeassistant.components.sensor.bitcoin # homeassistant.components.sensor.bitcoin
blockchain==1.2.1 blockchain==1.3.1
# homeassistant.components.notify.xmpp # homeassistant.components.notify.xmpp
dnspython3==1.12.0 dnspython3==1.12.0
@ -54,6 +54,12 @@ freesms==0.1.0
# homeassistant.components.conversation # homeassistant.components.conversation
fuzzywuzzy==0.8.0 fuzzywuzzy==0.8.0
# homeassistant.components.notify.gntp
gntp==1.0.3
# homeassistant.components.mqtt.server
hbmqtt==0.6.3
# homeassistant.components.thermostat.heatmiser # homeassistant.components.thermostat.heatmiser
heatmiserV3==0.9.1 heatmiserV3==0.9.1
@ -63,9 +69,6 @@ hikvision==0.4
# homeassistant.components.sensor.dht # homeassistant.components.sensor.dht
# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0 # http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0
# homeassistant.components.rfxtrx
https://github.com/Danielhiversen/pyRFXtrx/archive/0.5.zip#pyRFXtrx==0.5
# homeassistant.components.sensor.netatmo # homeassistant.components.sensor.netatmo
https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0 https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0
@ -78,6 +81,9 @@ https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1
# homeassistant.components.modbus # homeassistant.components.modbus
https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0 https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0
# homeassistant.components.sensor.uber
https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_rides==0.1.2-dev
# homeassistant.components.sensor.sabnzbd # homeassistant.components.sensor.sabnzbd
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
@ -114,12 +120,15 @@ liffylights==0.9.4
# homeassistant.components.light.limitlessled # homeassistant.components.light.limitlessled
limitlessled==1.0.0 limitlessled==1.0.0
# homeassistant.components.notify.message_bird
messagebird==1.1.1
# homeassistant.components.sensor.mfi # homeassistant.components.sensor.mfi
# homeassistant.components.switch.mfi # homeassistant.components.switch.mfi
mficlient==0.3.0 mficlient==0.3.0
# homeassistant.components.discovery # homeassistant.components.discovery
netdisco==0.5.4 netdisco==0.5.5
# homeassistant.components.sensor.neurio_energy # homeassistant.components.sensor.neurio_energy
neurio==0.2.10 neurio==0.2.10
@ -143,7 +152,7 @@ plexapi==1.1.0
proliphix==0.1.0 proliphix==0.1.0
# homeassistant.components.sensor.systemmonitor # homeassistant.components.sensor.systemmonitor
psutil==4.0.0 psutil==4.1.0
# homeassistant.components.notify.pushbullet # homeassistant.components.notify.pushbullet
pushbullet.py==0.9.0 pushbullet.py==0.9.0
@ -154,6 +163,9 @@ pushetta==1.0.15
# homeassistant.components.sensor.cpuspeed # homeassistant.components.sensor.cpuspeed
py-cpuinfo==0.2.3 py-cpuinfo==0.2.3
# homeassistant.components.rfxtrx
pyRFXtrx==0.6.5
# homeassistant.components.media_player.cast # homeassistant.components.media_player.cast
pychromecast==0.7.2 pychromecast==0.7.2
@ -164,7 +176,7 @@ pydispatcher==2.0.5
pyfttt==0.3 pyfttt==0.3
# homeassistant.components.device_tracker.icloud # homeassistant.components.device_tracker.icloud
pyicloud==0.7.2 pyicloud==0.8.1
# homeassistant.components.device_tracker.netgear # homeassistant.components.device_tracker.netgear
pynetgear==0.3.2 pynetgear==0.3.2
@ -180,16 +192,16 @@ pyowm==2.3.0
pysnmp==4.2.5 pysnmp==4.2.5
# homeassistant.components.sensor.forecast # homeassistant.components.sensor.forecast
python-forecastio==1.3.3 python-forecastio==1.3.4
# homeassistant.components.media_player.mpd # homeassistant.components.media_player.mpd
python-mpd2==0.5.4 python-mpd2==0.5.5
# homeassistant.components.nest # homeassistant.components.nest
python-nest==2.6.0 python-nest==2.6.0
# homeassistant.components.device_tracker.nmap_tracker # homeassistant.components.device_tracker.nmap_tracker
python-nmap==0.4.3 python-nmap==0.6.0
# homeassistant.components.notify.pushover # homeassistant.components.notify.pushover
python-pushover==0.2 python-pushover==0.2
@ -198,7 +210,7 @@ python-pushover==0.2
python-statsd==1.7.2 python-statsd==1.7.2
# homeassistant.components.notify.telegram # homeassistant.components.notify.telegram
python-telegram-bot==3.2.0 python-telegram-bot==3.4
# homeassistant.components.sensor.twitch # homeassistant.components.sensor.twitch
python-twitch==1.2.0 python-twitch==1.2.0
@ -210,22 +222,23 @@ python-twitch==1.2.0
# homeassistant.components.lock.wink # homeassistant.components.lock.wink
# homeassistant.components.sensor.wink # homeassistant.components.sensor.wink
# homeassistant.components.switch.wink # homeassistant.components.switch.wink
python-wink==0.6.2 python-wink==0.6.4
# homeassistant.components.keyboard # homeassistant.components.keyboard
pyuserinput==0.1.9 pyuserinput==0.1.9
# homeassistant.components.light.vera # homeassistant.components.vera
# homeassistant.components.sensor.vera
# homeassistant.components.switch.vera
pyvera==0.2.8 pyvera==0.2.8
# homeassistant.components.wemo # homeassistant.components.wemo
pywemo==0.3.12 pywemo==0.3.19
# homeassistant.components.thermostat.radiotherm # homeassistant.components.thermostat.radiotherm
radiotherm==1.2 radiotherm==1.2
# homeassistant.components.media_player.yamaha
rxv==0.1.9
# homeassistant.components.media_player.samsungtv # homeassistant.components.media_player.samsungtv
samsungctl==0.5.1 samsungctl==0.5.1
@ -256,9 +269,8 @@ speedtest-cli==0.3.4
# homeassistant.components.sensor.steam_online # homeassistant.components.sensor.steam_online
steamodd==4.21 steamodd==4.21
# homeassistant.components.light.tellstick # homeassistant.components.tellstick
# homeassistant.components.sensor.tellstick # homeassistant.components.sensor.tellstick
# homeassistant.components.switch.tellstick
tellcore-py==1.1.2 tellcore-py==1.1.2
# homeassistant.components.tellduslive # homeassistant.components.tellduslive
@ -278,7 +290,10 @@ urllib3
uvcclient==0.8 uvcclient==0.8
# homeassistant.components.verisure # homeassistant.components.verisure
vsure==0.6.1 vsure==0.7.1
# homeassistant.components.switch.wake_on_lan
wakeonlan==0.2.2
# homeassistant.components.zigbee # homeassistant.components.zigbee
xbee-helper==0.0.6 xbee-helper==0.0.6

View File

@ -24,50 +24,6 @@ class TestAutomationEvent(unittest.TestCase):
""""Stop everything that was started.""" """"Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_old_config_if_fires_on_event(self):
"""."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_event_with_data(self):
"""Test old configuration ."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'},
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event', {'some_attr': 'some_value'})
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_not_fires_if_event_data_not_matches(self):
"""test old configuration."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'},
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_event(self): def test_if_fires_on_event(self):
"""Test the firing of events.""" """Test the firing of events."""
self.assertTrue(automation.setup(self.hass, { self.assertTrue(automation.setup(self.hass, {

View File

@ -24,71 +24,6 @@ class TestAutomation(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_old_config_service_data_not_a_dict(self):
"""Test old configuration service data."""
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_data': 100
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_service_specify_data(self):
"""Test old configuration service data."""
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_data': {'some': 'data'}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual('data', self.calls[0].data['some'])
def test_old_config_service_specify_entity_id(self):
"""Test old configuration service data."""
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_entity_id': 'hello.world'
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_old_config_service_specify_entity_id_list(self):
"""Test old configuration service data."""
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_entity_id': ['hello.world', 'hello.world2']
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world', 'hello.world2'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_service_data_not_a_dict(self): def test_service_data_not_a_dict(self):
"""Test service data not dict.""" """Test service data not dict."""
automation.setup(self.hass, { automation.setup(self.hass, {

View File

@ -24,50 +24,6 @@ class TestAutomationMQTT(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_old_config_if_fires_on_topic_match(self):
"""Test if message is fired on topic match."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', '')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_topic_and_payload_match(self):
"""Test if message is fired on topic and payload match."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'mqtt_payload': 'hello',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', 'hello')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_not_fires_on_topic_but_no_payload_match(self):
"""Test if message is not fired on topic but no payload."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'mqtt_payload': 'hello',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', 'no-hello')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_topic_match(self): def test_if_fires_on_topic_match(self):
"""Test if message is fired on topic match.""" """Test if message is fired on topic match."""
self.assertTrue(automation.setup(self.hass, { self.assertTrue(automation.setup(self.hass, {

View File

@ -28,143 +28,6 @@ class TestAutomationState(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_old_config_if_fires_on_entity_change(self):
"""Test for firing if entity change ."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_entity_change_with_from_filter(self):
"""Test for firing on entity change with filter."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_entity_change_with_to_filter(self):
"""Test for firing on entity change no filter."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_entity_change_with_both_filters(self):
"""Test for firing on entity change with both filters."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_not_fires_if_to_filter_not_match(self):
"""Test for not firing if no match."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'moon')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_old_config_if_not_fires_if_from_filter_not_match(self):
"""Test for no firing if no match."""
self.hass.states.set('test.entity', 'bye')
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_old_config_if_not_fires_if_entity_not_match(self):
"""Test for not firing if no match."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'state',
'state_entity_id': 'test.another_entity',
'execute_service': 'test.automation'
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_old_config_if_action(self):
"""Test for if action."""
entity_id = 'domain.test_entity'
test_state = 'new_state'
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'if': [{
'platform': 'state',
'entity_id': entity_id,
'state': test_state,
}]
}
})
self.hass.states.set(entity_id, test_state)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, test_state + 'something')
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change(self): def test_if_fires_on_entity_change(self):
"""Test for firing on entity change.""" """Test for firing on entity change."""
self.assertTrue(automation.setup(self.hass, { self.assertTrue(automation.setup(self.hass, {

View File

@ -5,8 +5,6 @@ from unittest.mock import patch
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.components.automation as automation import homeassistant.components.automation as automation
from homeassistant.components.automation import time, event
from homeassistant.const import CONF_PLATFORM
from tests.common import fire_time_changed, get_test_home_assistant from tests.common import fire_time_changed, get_test_home_assistant
@ -28,207 +26,6 @@ class TestAutomationTime(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_old_config_if_fires_when_hour_matches(self):
"""Test for firing if hours are matching."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'time',
time.CONF_HOURS: 0,
'execute_service': 'test.automation'
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_when_minute_matches(self):
"""Test for firing if minutes are matching."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'time',
time.CONF_MINUTES: 0,
'execute_service': 'test.automation'
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_when_second_matches(self):
"""Test for firing if seconds are matching."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'time',
time.CONF_SECONDS: 0,
'execute_service': 'test.automation'
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(second=0))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_when_all_matches(self):
"""Test for firing if everything matches."""
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'time',
time.CONF_HOURS: 0,
time.CONF_MINUTES: 0,
time.CONF_SECONDS: 0,
'execute_service': 'test.automation'
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(
hour=0, minute=0, second=0))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_action_before(self):
"""Test for action before."""
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
'execute_service': 'test.automation',
'if': {
CONF_PLATFORM: 'time',
time.CONF_BEFORE: '10:00'
}
}
})
before_10 = dt_util.now().replace(hour=8)
after_10 = dt_util.now().replace(hour=14)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=before_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=after_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_action_after(self):
"""Test for action after."""
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
'execute_service': 'test.automation',
'if': {
CONF_PLATFORM: 'time',
time.CONF_AFTER: '10:00'
}
}
})
before_10 = dt_util.now().replace(hour=8)
after_10 = dt_util.now().replace(hour=14)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=before_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=after_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_action_one_weekday(self):
"""Test for action with one weekday."""
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
'execute_service': 'test.automation',
'if': {
CONF_PLATFORM: 'time',
time.CONF_WEEKDAY: 'mon',
}
}
})
days_past_monday = dt_util.now().weekday()
monday = dt_util.now() - timedelta(days=days_past_monday)
tuesday = monday + timedelta(days=1)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=monday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=tuesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_action_list_weekday(self):
"""Test for action with a list of weekdays."""
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
'execute_service': 'test.automation',
'if': {
CONF_PLATFORM: 'time',
time.CONF_WEEKDAY: ['mon', 'tue'],
}
}
})
days_past_monday = dt_util.now().weekday()
monday = dt_util.now() - timedelta(days=days_past_monday)
tuesday = monday + timedelta(days=1)
wednesday = tuesday + timedelta(days=1)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=monday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=tuesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=wednesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
def test_if_fires_when_hour_matches(self): def test_if_fires_when_hour_matches(self):
"""Test for firing if hour is matching.""" """Test for firing if hour is matching."""
self.assertTrue(automation.setup(self.hass, { self.assertTrue(automation.setup(self.hass, {

View File

@ -2,9 +2,12 @@
import unittest import unittest
from unittest import mock from unittest import mock
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.components.binary_sensor import template from homeassistant.components.binary_sensor import template
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from tests.common import get_test_home_assistant
class TestBinarySensorTemplate(unittest.TestCase): class TestBinarySensorTemplate(unittest.TestCase):
"""Test for Binary sensor template platform.""" """Test for Binary sensor template platform."""
@ -88,21 +91,19 @@ class TestBinarySensorTemplate(unittest.TestCase):
def test_event(self): def test_event(self):
""""Test the event.""" """"Test the event."""
hass = mock.MagicMock() hass = get_test_home_assistant()
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
'motion', '{{ 1 > 1 }}') 'motion', '{{ 1 > 1 }}')
with mock.patch.object(vs, 'update_ha_state') as mock_update: vs.update_ha_state()
vs._event_listener(None) hass.pool.block_till_done()
mock_update.assert_called_once_with(True)
def test_update(self): with mock.patch.object(vs, 'update') as mock_update:
""""Test the update.""" hass.bus.fire(EVENT_STATE_CHANGED)
hass = mock.MagicMock() hass.pool.block_till_done()
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', try:
'motion', '{{ 2 > 1 }}') assert mock_update.call_count == 1
self.assertEqual(None, vs._state) finally:
vs.update() hass.stop()
self.assertTrue(vs._state)
@mock.patch('homeassistant.helpers.template.render') @mock.patch('homeassistant.helpers.template.render')
def test_update_template_error(self, mock_render): def test_update_template_error(self, mock_render):

View File

@ -133,15 +133,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
'radius': 100000 'radius': 100000
}) })
self.hass.states.set(
'zone.passive', 'zoning',
{
'name': 'zone',
'latitude': 3.0,
'longitude': 1.0,
'radius': 10,
'passive': True
})
# Clear state between teste # Clear state between teste
self.hass.states.set(DEVICE_TRACKER_STATE, None) self.hass.states.set(DEVICE_TRACKER_STATE, None)
owntracks.REGIONS_ENTERED = defaultdict(list) owntracks.REGIONS_ENTERED = defaultdict(list)
@ -325,43 +316,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
self.send_message(EVENT_TOPIC, message) self.send_message(EVENT_TOPIC, message)
self.assert_location_state('outer') self.assert_location_state('outer')
def test_event_entry_exit_passive_zone(self):
"""Test the event for passive zone exits."""
# Enter passive zone
message = REGION_ENTER_MESSAGE.copy()
message['desc'] = "passive"
self.send_message(EVENT_TOPIC, message)
# Should pick up gps put not zone
self.assert_location_state('not_home')
self.assert_location_latitude(3.0)
self.assert_location_accuracy(10.0)
# Enter inner2 zone
message = REGION_ENTER_MESSAGE.copy()
message['desc'] = "inner_2"
self.send_message(EVENT_TOPIC, message)
self.assert_location_state('inner_2')
self.assert_location_latitude(2.1)
self.assert_location_accuracy(10.0)
# Exit inner_2 - should be in 'passive'
# ie gps co-ords - but not zone
message = REGION_LEAVE_MESSAGE.copy()
message['desc'] = "inner_2"
self.send_message(EVENT_TOPIC, message)
self.assert_location_state('not_home')
self.assert_location_latitude(3.0)
self.assert_location_accuracy(10.0)
# Exit passive - should be in 'outer'
message = REGION_LEAVE_MESSAGE.copy()
message['desc'] = "passive"
self.send_message(EVENT_TOPIC, message)
self.assert_location_state('outer')
self.assert_location_latitude(2.0)
self.assert_location_accuracy(60.0)
def test_event_entry_unknown_zone(self): def test_event_entry_unknown_zone(self):
"""Test the event for unknown zone.""" """Test the event for unknown zone."""
# Just treat as location update # Just treat as location update

View File

@ -5,12 +5,9 @@ from homeassistant.components import rfxtrx as rfxtrx_core
from homeassistant.components.light import rfxtrx from homeassistant.components.light import rfxtrx
from unittest.mock import patch from unittest.mock import patch
import pytest
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
@pytest.mark.skipif(True, reason='Does not clean up properly, takes 100% CPU')
class TestLightRfxtrx(unittest.TestCase): class TestLightRfxtrx(unittest.TestCase):
"""Test the Rfxtrx light platform.""" """Test the Rfxtrx light platform."""
@ -22,6 +19,8 @@ class TestLightRfxtrx(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS = [] rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS = []
rfxtrx_core.RFX_DEVICES = {} rfxtrx_core.RFX_DEVICES = {}
if rfxtrx_core.RFXOBJECT:
rfxtrx_core.RFXOBJECT.close_connection()
self.hass.stop() self.hass.stop()
def test_default_config(self): def test_default_config(self):

View File

@ -25,6 +25,11 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level') assert 1.0 == state.attributes.get('volume_level')
mp.set_volume_level(self.hass, None, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level')
mp.set_volume_level(self.hass, 0.5, entity_id) mp.set_volume_level(self.hass, 0.5, entity_id)
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
@ -41,6 +46,12 @@ class TestDemoMediaPlayer(unittest.TestCase):
assert 0.5 == state.attributes.get('volume_level') assert 0.5 == state.attributes.get('volume_level')
assert False is state.attributes.get('is_volume_muted') assert False is state.attributes.get('is_volume_muted')
mp.mute_volume(self.hass, None, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert False is state.attributes.get('is_volume_muted')
mp.mute_volume(self.hass, True, entity_id) mp.mute_volume(self.hass, True, entity_id)
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
@ -87,7 +98,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
assert self.hass.states.is_state(entity_id, 'playing') assert self.hass.states.is_state(entity_id, 'playing')
def test_prev_next_track(self): def test_prev_next_track(self):
"""Test media_next_track and media_prevoius_track .""" """Test media_next_track and media_previous_track ."""
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 1 == state.attributes.get('media_track') assert 1 == state.attributes.get('media_track')
@ -115,6 +126,27 @@ class TestDemoMediaPlayer(unittest.TestCase):
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands')) state.attributes.get('supported_media_commands'))
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
ent_id = 'media_player.lounge_room'
state = self.hass.states.get(ent_id)
assert 1 == state.attributes.get('media_episode')
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
mp.media_next_track(self.hass, ent_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(ent_id)
assert 2 == state.attributes.get('media_episode')
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
mp.media_previous_track(self.hass, ent_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(ent_id)
assert 1 == state.attributes.get('media_episode')
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.' @patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.'
'media_seek') 'media_seek')
def test_play_media(self, mock_seek): def test_play_media(self, mock_seek):
@ -126,6 +158,13 @@ class TestDemoMediaPlayer(unittest.TestCase):
state.attributes.get('supported_media_commands')) state.attributes.get('supported_media_commands'))
assert state.attributes.get('media_content_id') is not None assert state.attributes.get('media_content_id') is not None
mp.play_media(self.hass, None, 'some_id', ent_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
state.attributes.get('supported_media_commands'))
assert not 'some_id' == state.attributes.get('media_content_id')
mp.play_media(self.hass, 'youtube', 'some_id', ent_id) mp.play_media(self.hass, 'youtube', 'some_id', ent_id)
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
state = self.hass.states.get(ent_id) state = self.hass.states.get(ent_id)
@ -133,6 +172,9 @@ class TestDemoMediaPlayer(unittest.TestCase):
state.attributes.get('supported_media_commands')) state.attributes.get('supported_media_commands'))
assert 'some_id' == state.attributes.get('media_content_id') assert 'some_id' == state.attributes.get('media_content_id')
assert not mock_seek.called
mp.media_seek(self.hass, None, ent_id)
self.hass.pool.block_till_done()
assert not mock_seek.called assert not mock_seek.called
mp.media_seek(self.hass, 100, ent_id) mp.media_seek(self.hass, 100, ent_id)
self.hass.pool.block_till_done() self.hass.pool.block_till_done()

View File

@ -25,8 +25,37 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
self._media_title = None self._media_title = None
self._supported_media_commands = 0 self._supported_media_commands = 0
self.turn_off_service_calls = mock_service( self.service_calls = {
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF) 'turn_on': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_TURN_ON),
'turn_off': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF),
'mute_volume': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_MUTE),
'set_volume_level': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET),
'media_play': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY),
'media_pause': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE),
'media_previous_track': mock_service(
hass, media_player.DOMAIN,
media_player.SERVICE_MEDIA_PREVIOUS_TRACK),
'media_next_track': mock_service(
hass, media_player.DOMAIN,
media_player.SERVICE_MEDIA_NEXT_TRACK),
'media_seek': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK),
'play_media': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_PLAY_MEDIA),
'volume_up': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_UP),
'volume_down': mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_DOWN),
'media_play_pause': mock_service(
hass, media_player.DOMAIN,
media_player.SERVICE_MEDIA_PLAY_PAUSE),
}
@property @property
def name(self): def name(self):
@ -97,23 +126,26 @@ class TestMediaPlayer(unittest.TestCase):
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state') self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state')
self.hass.states.set(self.mock_state_switch_id, STATE_OFF) self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
self.config_children_only = \ self.config_children_only = {
{'name': 'test', 'platform': 'universal', 'name': 'test', 'platform': 'universal',
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
media_player.ENTITY_ID_FORMAT.format('mock2')]} media_player.ENTITY_ID_FORMAT.format('mock2')]
self.config_children_and_attr = \ }
{'name': 'test', 'platform': 'universal', self.config_children_and_attr = {
'name': 'test', 'platform': 'universal',
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
media_player.ENTITY_ID_FORMAT.format('mock2')], media_player.ENTITY_ID_FORMAT.format('mock2')],
'attributes': { 'attributes': {
'is_volume_muted': self.mock_mute_switch_id, 'is_volume_muted': self.mock_mute_switch_id,
'state': self.mock_state_switch_id}} 'state': self.mock_state_switch_id
}
}
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_check_config_children_only(self): def test_config_children_only(self):
"""Check config with only children.""" """Check config with only children."""
config_start = copy(self.config_children_only) config_start = copy(self.config_children_only)
del config_start['platform'] del config_start['platform']
@ -125,7 +157,7 @@ class TestMediaPlayer(unittest.TestCase):
self.assertTrue(response) self.assertTrue(response)
self.assertEqual(config_start, self.config_children_only) self.assertEqual(config_start, self.config_children_only)
def test_check_config_children_and_attr(self): def test_config_children_and_attr(self):
"""Check config with children and attributes.""" """Check config with children and attributes."""
config_start = copy(self.config_children_and_attr) config_start = copy(self.config_children_and_attr)
del config_start['platform'] del config_start['platform']
@ -136,13 +168,13 @@ class TestMediaPlayer(unittest.TestCase):
self.assertTrue(response) self.assertTrue(response)
self.assertEqual(config_start, self.config_children_and_attr) self.assertEqual(config_start, self.config_children_and_attr)
def test_check_config_no_name(self): def test_config_no_name(self):
"""Check config with no Name entry.""" """Check config with no Name entry."""
response = universal.validate_config({'platform': 'universal'}) response = universal.validate_config({'platform': 'universal'})
self.assertFalse(response) self.assertFalse(response)
def test_check_config_bad_children(self): def test_config_bad_children(self):
"""Check config with bad children entry.""" """Check config with bad children entry."""
config_no_children = {'name': 'test', 'platform': 'universal'} config_no_children = {'name': 'test', 'platform': 'universal'}
config_bad_children = {'name': 'test', 'children': {}, config_bad_children = {'name': 'test', 'children': {},
@ -156,7 +188,7 @@ class TestMediaPlayer(unittest.TestCase):
self.assertTrue(response) self.assertTrue(response)
self.assertEqual([], config_bad_children['children']) self.assertEqual([], config_bad_children['children'])
def test_check_config_bad_commands(self): def test_config_bad_commands(self):
"""Check config with bad commands entry.""" """Check config with bad commands entry."""
config = {'name': 'test', 'commands': [], 'platform': 'universal'} config = {'name': 'test', 'commands': [], 'platform': 'universal'}
@ -164,7 +196,7 @@ class TestMediaPlayer(unittest.TestCase):
self.assertTrue(response) self.assertTrue(response)
self.assertEqual({}, config['commands']) self.assertEqual({}, config['commands'])
def test_check_config_bad_attributes(self): def test_config_bad_attributes(self):
"""Check config with bad attributes.""" """Check config with bad attributes."""
config = {'name': 'test', 'attributes': [], 'platform': 'universal'} config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
@ -172,7 +204,7 @@ class TestMediaPlayer(unittest.TestCase):
self.assertTrue(response) self.assertTrue(response)
self.assertEqual({}, config['attributes']) self.assertEqual({}, config['attributes'])
def test_check_config_bad_key(self): def test_config_bad_key(self):
"""Check config with bad key.""" """Check config with bad key."""
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'} config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
@ -183,6 +215,7 @@ class TestMediaPlayer(unittest.TestCase):
def test_platform_setup(self): def test_platform_setup(self):
"""Test platform setup.""" """Test platform setup."""
config = {'name': 'test', 'platform': 'universal'} config = {'name': 'test', 'platform': 'universal'}
bad_config = {'platform': 'universal'}
entities = [] entities = []
def add_devices(new_entities): def add_devices(new_entities):
@ -190,8 +223,10 @@ class TestMediaPlayer(unittest.TestCase):
for dev in new_entities: for dev in new_entities:
entities.append(dev) entities.append(dev)
universal.setup_platform(self.hass, config, add_devices) universal.setup_platform(self.hass, bad_config, add_devices)
self.assertEqual(0, len(entities))
universal.setup_platform(self.hass, config, add_devices)
self.assertEqual(1, len(entities)) self.assertEqual(1, len(entities))
self.assertEqual('test', entities[0].name) self.assertEqual('test', entities[0].name)
@ -263,6 +298,15 @@ class TestMediaPlayer(unittest.TestCase):
self.assertEqual(config['name'], ump.name) self.assertEqual(config['name'], ump.name)
def test_polling(self):
"""Test should_poll property."""
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertEqual(False, ump.should_poll)
def test_state_children_only(self): def test_state_children_only(self):
"""Test media player state with only children.""" """Test media player state with only children."""
config = self.config_children_only config = self.config_children_only
@ -388,8 +432,7 @@ class TestMediaPlayer(unittest.TestCase):
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update() ump.update()
self.mock_mp_1._supported_media_commands = \ self.mock_mp_1._supported_media_commands = universal.SUPPORT_VOLUME_SET
universal.SUPPORT_VOLUME_SET
self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state() self.mock_mp_1.update_ha_state()
ump.update() ump.update()
@ -400,7 +443,7 @@ class TestMediaPlayer(unittest.TestCase):
self.assertEqual(check_flags, ump.supported_media_commands) self.assertEqual(check_flags, ump.supported_media_commands)
def test_service_call_to_child(self): def test_service_call_to_child(self):
"""Test a service call that should be routed to a child.""" """Test service calls that should be routed to a child."""
config = self.config_children_only config = self.config_children_only
universal.validate_config(config) universal.validate_config(config)
@ -413,13 +456,53 @@ class TestMediaPlayer(unittest.TestCase):
ump.update() ump.update()
ump.turn_off() ump.turn_off()
self.assertEqual(1, len(self.mock_mp_2.turn_off_service_calls)) self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_off']))
ump.turn_on()
self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_on']))
ump.mute_volume(True)
self.assertEqual(1, len(self.mock_mp_2.service_calls['mute_volume']))
ump.set_volume_level(0.5)
self.assertEqual(
1, len(self.mock_mp_2.service_calls['set_volume_level']))
ump.media_play()
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_play']))
ump.media_pause()
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_pause']))
ump.media_previous_track()
self.assertEqual(
1, len(self.mock_mp_2.service_calls['media_previous_track']))
ump.media_next_track()
self.assertEqual(
1, len(self.mock_mp_2.service_calls['media_next_track']))
ump.media_seek(100)
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_seek']))
ump.play_media('movie', 'batman')
self.assertEqual(1, len(self.mock_mp_2.service_calls['play_media']))
ump.volume_up()
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_up']))
ump.volume_down()
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_down']))
ump.media_play_pause()
self.assertEqual(
1, len(self.mock_mp_2.service_calls['media_play_pause']))
def test_service_call_to_command(self): def test_service_call_to_command(self):
"""Test service call to command.""" """Test service call to command."""
config = self.config_children_only config = self.config_children_only
config['commands'] = \ config['commands'] = {'turn_off': {
{'turn_off': {'service': 'test.turn_off', 'data': {}}} 'service': 'test.turn_off', 'data': {}}}
universal.validate_config(config) universal.validate_config(config)
service = mock_service(self.hass, 'test', 'turn_off') service = mock_service(self.hass, 'test', 'turn_off')

View File

@ -44,10 +44,6 @@ class TestMQTT(unittest.TestCase):
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertTrue(mqtt.MQTT_CLIENT.stop.called) self.assertTrue(mqtt.MQTT_CLIENT.stop.called)
def test_setup_fails_if_no_broker_config(self):
"""Test for setup failure if broker configuration is missing."""
self.assertFalse(mqtt.setup(self.hass, {mqtt.DOMAIN: {}}))
def test_setup_fails_if_no_connect_broker(self): def test_setup_fails_if_no_connect_broker(self):
"""Test for setup failure if connection to broker is missing.""" """Test for setup failure if connection to broker is missing."""
with mock.patch('homeassistant.components.mqtt.MQTT', with mock.patch('homeassistant.components.mqtt.MQTT',

View File

@ -0,0 +1,55 @@
"""The tests for the MQTT component embedded server."""
from unittest.mock import MagicMock, patch
import homeassistant.components.mqtt as mqtt
from tests.common import get_test_home_assistant
class TestMQTT:
"""Test the MQTT component."""
def setup_method(self, method):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
"""Stop everything that was started."""
self.hass.stop()
@patch('homeassistant.components.mqtt.MQTT')
@patch('asyncio.gather')
@patch('asyncio.new_event_loop')
def test_creating_config_with_http_pass(self, mock_new_loop, mock_gather,
mock_mqtt):
"""Test if the MQTT server gets started and subscribe/publish msg."""
self.hass.config.components.append('http')
password = 'super_secret'
self.hass.config.api = MagicMock(api_password=password)
assert mqtt.setup(self.hass, {})
assert mock_mqtt.called
assert mock_mqtt.mock_calls[0][1][5] == 'homeassistant'
assert mock_mqtt.mock_calls[0][1][6] == password
mock_mqtt.reset_mock()
self.hass.config.api = MagicMock(api_password=None)
assert mqtt.setup(self.hass, {})
assert mock_mqtt.called
assert mock_mqtt.mock_calls[0][1][5] is None
assert mock_mqtt.mock_calls[0][1][6] is None
@patch('asyncio.gather')
@patch('asyncio.new_event_loop')
def test_broker_config_fails(self, mock_new_loop, mock_gather):
"""Test if the MQTT component fails if server fails."""
self.hass.config.components.append('http')
from hbmqtt.broker import BrokerException
mock_gather.side_effect = BrokerException
self.hass.config.api = MagicMock(api_password=None)
assert not mqtt.setup(self.hass, {
'mqtt': {'embedded': {}}
})

View File

@ -3,8 +3,10 @@ import os
import tempfile import tempfile
import unittest import unittest
from homeassistant import core
import homeassistant.components.notify as notify import homeassistant.components.notify as notify
from tests.common import get_test_home_assistant
from unittest.mock import patch from unittest.mock import patch
@ -13,12 +15,27 @@ class TestCommandLine(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started.""" """Setup things to be run when tests are started."""
self.hass = core.HomeAssistant() self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() self.hass.stop()
def test_bad_config(self):
"""Test set up the platform with bad/missing config."""
self.assertFalse(notify.setup(self.hass, {
'notify': {
'name': 'test',
'platform': 'bad_platform',
}
}))
self.assertFalse(notify.setup(self.hass, {
'notify': {
'name': 'test',
'platform': 'command_line',
}
}))
def test_command_line_output(self): def test_command_line_output(self):
"""Test the command line output.""" """Test the command line output."""
with tempfile.TemporaryDirectory() as tempdirname: with tempfile.TemporaryDirectory() as tempdirname:
@ -41,7 +58,7 @@ class TestCommandLine(unittest.TestCase):
@patch('homeassistant.components.notify.command_line._LOGGER.error') @patch('homeassistant.components.notify.command_line._LOGGER.error')
def test_error_for_none_zero_exit_code(self, mock_error): def test_error_for_none_zero_exit_code(self, mock_error):
"""Test if an error if logged for non zero exit codes.""" """Test if an error is logged for non zero exit codes."""
self.assertTrue(notify.setup(self.hass, { self.assertTrue(notify.setup(self.hass, {
'notify': { 'notify': {
'name': 'test', 'name': 'test',

View File

@ -30,6 +30,12 @@ class TestNotifyDemo(unittest.TestCase):
""""Stop down everything that was started.""" """"Stop down everything that was started."""
self.hass.stop() self.hass.stop()
def test_sending_none_message(self):
"""Test send with None as message."""
notify.send_message(self.hass, None)
self.hass.pool.block_till_done()
self.assertTrue(len(self.events) == 0)
def test_sending_templated_message(self): def test_sending_templated_message(self):
"""Send a templated message.""" """Send a templated message."""
self.hass.states.set('sensor.temperature', 10) self.hass.states.set('sensor.temperature', 10)

View File

@ -0,0 +1,56 @@
"""The tests for the notify file platform."""
import os
import unittest
import tempfile
import homeassistant.components.notify as notify
from homeassistant.components.notify import (
ATTR_TITLE_DEFAULT)
import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant
class TestNotifyFile(unittest.TestCase):
"""Test the file notify."""
def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
""""Stop down everything that was started."""
self.hass.stop()
def test_bad_config(self):
"""Test set up the platform with bad/missing config."""
self.assertFalse(notify.setup(self.hass, {
'notify': {
'name': 'test',
'platform': 'file',
}
}))
def test_notify_file(self):
"""Test the notify file output."""
with tempfile.TemporaryDirectory() as tempdirname:
filename = os.path.join(tempdirname, 'notify.txt')
message = 'one, two, testing, testing'
self.assertTrue(notify.setup(self.hass, {
'notify': {
'name': 'test',
'platform': 'file',
'filename': filename,
'timestamp': 0
}
}))
title = '{} notifications (Log started: {})\n{}\n'.format(
ATTR_TITLE_DEFAULT,
dt_util.strip_microseconds(dt_util.utcnow()),
'-' * 80)
self.hass.services.call('notify', 'test', {'message': message},
blocking=True)
result = open(filename).read()
self.assertEqual(result, "{}{}\n".format(title, message))

View File

@ -54,7 +54,7 @@ class TestTemplateSensor:
self.hass.states.set('sensor.test_state', 'Works') self.hass.states.set('sensor.test_state', 'Works')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
state = self.hass.states.get('sensor.test_template_sensor') state = self.hass.states.get('sensor.test_template_sensor')
assert state.state == 'error' assert state.state == 'unknown'
def test_template_attribute_missing(self): def test_template_attribute_missing(self):
"""Test missing attribute template.""" """Test missing attribute template."""
@ -71,7 +71,7 @@ class TestTemplateSensor:
}) })
state = self.hass.states.get('sensor.test_template_sensor') state = self.hass.states.get('sensor.test_template_sensor')
assert state.state == 'error' assert state.state == 'unknown'
def test_invalid_name_does_not_create(self): def test_invalid_name_does_not_create(self):
"""Test invalid name.""" """Test invalid name."""

View File

@ -6,6 +6,7 @@ import unittest
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
import homeassistant.components.switch as switch import homeassistant.components.switch as switch
import homeassistant.components.switch.command_line as command_line
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
@ -155,3 +156,21 @@ class TestCommandSwitch(unittest.TestCase):
state = self.hass.states.get('switch.test') state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state) self.assertEqual(STATE_ON, state.state)
def test_assumed_state_should_be_true_if_command_state_is_false(self):
"""Test with state value."""
self.hass = get_test_home_assistant()
# Set state command to false
statecmd = False
no_state_device = command_line.CommandSwitch(self.hass, "Test", "echo",
"echo", statecmd, None)
self.assertTrue(no_state_device.assumed_state)
# Set state command
statecmd = 'cat {}'
state_device = command_line.CommandSwitch(self.hass, "Test", "echo",
"echo", statecmd, None)
self.assertFalse(state_device.assumed_state)

Some files were not shown because too many files have changed in this diff Show More