mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
commit
763a9ce8c6
11
.coveragerc
11
.coveragerc
@ -26,11 +26,13 @@ omit =
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/tellstick.py
|
||||
homeassistant/components/*/tellstick.py
|
||||
|
||||
homeassistant/components/tellduslive.py
|
||||
homeassistant/components/*/tellduslive.py
|
||||
|
||||
homeassistant/components/vera.py
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
@ -57,9 +59,6 @@ omit =
|
||||
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
|
||||
|
||||
@ -108,9 +107,12 @@ omit =
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nma.py
|
||||
homeassistant/components/notify/pushbullet.py
|
||||
homeassistant/components/notify/pushetta.py
|
||||
@ -149,6 +151,7 @@ omit =
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/worldclock.py
|
||||
homeassistant/components/switch/arest.py
|
||||
homeassistant/components/switch/dlink.py
|
||||
@ -156,8 +159,10 @@ omit =
|
||||
homeassistant/components/switch/hikvisioncam.py
|
||||
homeassistant/components/switch/mystrom.py
|
||||
homeassistant/components/switch/orvibo.py
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/wake_on_lan.py
|
||||
homeassistant/components/thermostat/heatmiser.py
|
||||
homeassistant/components/thermostat/homematic.py
|
||||
homeassistant/components/thermostat/proliphix.py
|
||||
|
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -10,16 +10,15 @@
|
||||
|
||||
**Checklist:**
|
||||
|
||||
- [ ] Local tests with `tox` run successfully.
|
||||
- [ ] TravisCI does not fail. **Your PR cannot be merged unless CI is green!**
|
||||
- [ ] [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:
|
||||
If code communicates with devices:
|
||||
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
|
||||
- [ ] 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 have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
|
||||
- [ ] 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.
|
||||
|
||||
[fork]: http://stackoverflow.com/a/7244456
|
||||
|
@ -83,11 +83,13 @@ def setup(hass, config):
|
||||
hass.http.register_path(
|
||||
'GET', URL_API_COMPONENTS, _handle_get_api_components)
|
||||
|
||||
# /error_log
|
||||
hass.http.register_path('GET', URL_API_ERROR_LOG,
|
||||
_handle_get_api_error_log)
|
||||
|
||||
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
|
||||
|
||||
# /template
|
||||
hass.http.register_path('POST', URL_API_TEMPLATE,
|
||||
_handle_post_api_template)
|
||||
|
||||
|
@ -9,8 +9,9 @@ import logging
|
||||
from homeassistant.bootstrap import prepare_setup_platform
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.helpers.service import call_from_config
|
||||
from homeassistant.helpers.service import validate_service_call
|
||||
from homeassistant.helpers import extract_domain_configs
|
||||
from homeassistant.helpers.service import (call_from_config,
|
||||
validate_service_call)
|
||||
|
||||
|
||||
DOMAIN = 'automation'
|
||||
@ -35,30 +36,17 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the automation."""
|
||||
config_key = DOMAIN
|
||||
found = 1
|
||||
for config_key in extract_domain_configs(config, DOMAIN):
|
||||
conf = config[config_key]
|
||||
|
||||
while config_key in config:
|
||||
# Check for one block syntax
|
||||
if isinstance(config[config_key], dict):
|
||||
config_block = _migrate_old_config(config[config_key])
|
||||
name = config_block.get(CONF_ALIAS, config_key)
|
||||
if not isinstance(conf, list):
|
||||
conf = [conf]
|
||||
|
||||
for list_no, config_block in enumerate(conf):
|
||||
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
|
||||
list_no))
|
||||
_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
|
||||
|
||||
|
||||
@ -97,40 +85,6 @@ def _get_action(hass, config, name):
|
||||
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):
|
||||
"""Process if checks."""
|
||||
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
||||
|
@ -55,8 +55,13 @@ def _check_template(hass, value_template):
|
||||
"""Check if result of template is true."""
|
||||
try:
|
||||
value = template.render(hass, value_template, {})
|
||||
except TemplateError:
|
||||
_LOGGER.exception('Error parsing template')
|
||||
except TemplateError as ex:
|
||||
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 value.lower() == 'true'
|
||||
|
@ -9,7 +9,8 @@ import logging
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
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'
|
||||
SCAN_INTERVAL = 30
|
||||
@ -37,6 +38,7 @@ DISCOVERY_PLATFORMS = {
|
||||
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
|
||||
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
|
||||
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
|
||||
vera.DISCOVER_BINARY_SENSORS: 'vera',
|
||||
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
|
||||
wink.DISCOVER_BINARY_SENSORS: 'wink'
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ https://home-assistant.io/components/binary_sensor.rest/
|
||||
"""
|
||||
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.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.helpers import template
|
||||
@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET'
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup REST binary sensors."""
|
||||
"""Setup the REST binary sensor."""
|
||||
resource = config.get('resource', None)
|
||||
method = config.get('method', DEFAULT_METHOD)
|
||||
payload = config.get('payload', None)
|
||||
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.update()
|
||||
|
||||
@ -33,19 +39,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
return False
|
||||
|
||||
add_devices([RestBinarySensor(
|
||||
hass, rest, config.get('name', DEFAULT_NAME),
|
||||
hass,
|
||||
rest,
|
||||
config.get('name', DEFAULT_NAME),
|
||||
sensor_class,
|
||||
config.get(CONF_VALUE_TEMPLATE))])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
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."""
|
||||
self._hass = hass
|
||||
self.rest = rest
|
||||
self._name = name
|
||||
self._sensor_class = sensor_class
|
||||
self._state = False
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
@ -55,6 +65,11 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
"""Return the name of the binary sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._sensor_class
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.template/
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||
DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
SENSOR_CLASSES)
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
|
||||
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.util import slugify
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
CONF_SENSORS = 'sensors'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -76,34 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
def __init__(self, hass, device, friendly_name, sensor_class,
|
||||
value_template):
|
||||
"""Initialize the Template binary sensor."""
|
||||
self._hass = hass
|
||||
self._device = device
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._sensor_class = sensor_class
|
||||
self._template = value_template
|
||||
self._state = None
|
||||
|
||||
self.entity_id = generate_entity_id(
|
||||
ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self.update()
|
||||
|
||||
_LOGGER.info('Started template sensor %s', device)
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
||||
def template_bsensor_event_listener(event):
|
||||
"""Called when the target device changes state."""
|
||||
self.update_ha_state(True)
|
||||
|
||||
def _event_listener(self, event):
|
||||
if not hasattr(self, 'hass'):
|
||||
return
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
"""Return the sensor class of the sensor."""
|
||||
return self._sensor_class
|
||||
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||
template_bsensor_event_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
"""Return true if sensor is on."""
|
||||
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):
|
||||
"""Get the latest data and update the state."""
|
||||
try:
|
||||
value = template.render(self._hass, self._template)
|
||||
self._state = template.render(self.hass,
|
||||
self._template).lower() == 'true'
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
_LOGGER.warning(ex)
|
||||
return
|
||||
_LOGGER.error(ex)
|
||||
value = 'false'
|
||||
self._state = value.lower() == 'true'
|
||||
self._state = False
|
||||
|
69
homeassistant/components/binary_sensor/vera.py
Normal file
69
homeassistant/components/binary_sensor/vera.py
Normal 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
|
@ -45,6 +45,9 @@ class WemoBinarySensor(BinarySensorDevice):
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s',
|
||||
_device)
|
||||
if not hasattr(self, 'hass'):
|
||||
self.update()
|
||||
return
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
|
@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
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
|
||||
SENSOR_TYPES = {
|
||||
@ -77,6 +77,11 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
||||
"""Return the name of the sensor if any."""
|
||||
return self.wink.name()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def update(self):
|
||||
"""Update state of the sensor."""
|
||||
self.wink.update_state()
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers.event import track_utc_time_change
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['pyicloud==0.7.2']
|
||||
REQUIREMENTS = ['pyicloud==0.8.1']
|
||||
|
||||
CONF_INTERVAL = 'interval'
|
||||
DEFAULT_INTERVAL = 8
|
||||
|
@ -89,4 +89,9 @@ class NetgearDeviceScanner(object):
|
||||
with self.lock:
|
||||
_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 []
|
||||
|
@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# interval in minutes to exclude devices from a scan while they are home
|
||||
CONF_HOME_INTERVAL = "home_interval"
|
||||
|
||||
REQUIREMENTS = ['python-nmap==0.4.3']
|
||||
REQUIREMENTS = ['python-nmap==0.6.0']
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
|
@ -99,14 +99,11 @@ def setup_scanner(hass, config, see):
|
||||
_LOGGER.info("Added beacon %s", location)
|
||||
else:
|
||||
# Normal region
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = location
|
||||
|
||||
regions = REGIONS_ENTERED[dev_id]
|
||||
if location not in regions:
|
||||
regions.append(location)
|
||||
_LOGGER.info("Enter region %s", location)
|
||||
_set_gps_from_zone(kwargs, zone)
|
||||
_set_gps_from_zone(kwargs, location, zone)
|
||||
|
||||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
@ -121,9 +118,7 @@ def setup_scanner(hass, config, see):
|
||||
if new_region:
|
||||
# Exit to previous region
|
||||
zone = hass.states.get("zone.{}".format(new_region))
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = new_region
|
||||
_set_gps_from_zone(kwargs, zone)
|
||||
_set_gps_from_zone(kwargs, new_region, zone)
|
||||
_LOGGER.info("Exit to %s", new_region)
|
||||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
@ -184,11 +179,12 @@ def _parse_see_args(topic, data):
|
||||
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."""
|
||||
if zone is not None:
|
||||
kwargs['gps'] = (
|
||||
zone.attributes['latitude'],
|
||||
zone.attributes['longitude'])
|
||||
kwargs['gps_accuracy'] = zone.attributes['radius']
|
||||
kwargs['location_name'] = location
|
||||
return kwargs
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
EVENT_PLATFORM_DISCOVERED)
|
||||
|
||||
DOMAIN = "discovery"
|
||||
REQUIREMENTS = ['netdisco==0.5.4']
|
||||
REQUIREMENTS = ['netdisco==0.5.5']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast'
|
||||
SERVICE_NETGEAR = 'netgear_router'
|
||||
SERVICE_SONOS = 'sonos'
|
||||
SERVICE_PLEX = 'plex_mediaserver'
|
||||
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
|
||||
|
||||
SERVICE_HANDLERS = {
|
||||
SERVICE_WEMO: "wemo",
|
||||
@ -33,6 +34,7 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_NETGEAR: 'device_tracker',
|
||||
SERVICE_SONOS: 'media_player',
|
||||
SERVICE_PLEX: 'media_player',
|
||||
SERVICE_SQUEEZEBOX: 'media_player',
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
||||
VERSION = "e85dc66e1a0730e44f79ed11501cd79a"
|
||||
VERSION = "df49e6b7c930eb39b42ff1909712e95e"
|
||||
|
@ -1,2 +1,2 @@
|
||||
"""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
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
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):
|
||||
@ -57,6 +57,11 @@ class WinkGarageDoorDevice(GarageDoorDevice):
|
||||
"""Return true if door is closed."""
|
||||
return self.wink.state() == 0
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def close_door(self):
|
||||
"""Close the door."""
|
||||
self.wink.set_state(0)
|
||||
|
@ -74,7 +74,8 @@ def setup(hass, config):
|
||||
hass.bus.listen_once(
|
||||
ha.EVENT_HOMEASSISTANT_START,
|
||||
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.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?
|
||||
if handle_request_method:
|
||||
# For some calls we need a valid password
|
||||
msg = "API password missing or incorrect."
|
||||
if require_auth and not self.authenticated:
|
||||
self.write_json_message(
|
||||
"API password missing or incorrect.", HTTP_UNAUTHORIZED)
|
||||
self.write_json_message(msg, HTTP_UNAUTHORIZED)
|
||||
_LOGGER.warning(msg)
|
||||
return
|
||||
|
||||
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):
|
||||
"""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_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data)))
|
||||
|
||||
if location:
|
||||
self.send_header('Location', location)
|
||||
@ -288,20 +293,20 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
|
||||
if data is not None:
|
||||
self.wfile.write(
|
||||
json.dumps(data, indent=4, sort_keys=True,
|
||||
cls=rem.JSONEncoder).encode("UTF-8"))
|
||||
self.wfile.write(json_data)
|
||||
|
||||
def write_text(self, message, status_code=HTTP_OK):
|
||||
"""Helper method to return a text message to the caller."""
|
||||
msg_data = message.encode('UTF-8')
|
||||
self.send_response(status_code)
|
||||
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.end_headers()
|
||||
|
||||
self.wfile.write(message.encode("UTF-8"))
|
||||
self.wfile.write(msg_data)
|
||||
|
||||
def write_file(self, path, cache_headers=True):
|
||||
"""Return a file to the user."""
|
||||
|
145
homeassistant/components/input_slider.py
Normal file
145
homeassistant/components/input_slider.py
Normal 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()
|
@ -9,7 +9,8 @@ import os
|
||||
import csv
|
||||
|
||||
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.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
@ -64,6 +65,8 @@ DISCOVERY_PLATFORMS = {
|
||||
discovery.SERVICE_HUE: 'hue',
|
||||
zwave.DISCOVER_LIGHTS: 'zwave',
|
||||
mysensors.DISCOVER_LIGHTS: 'mysensors',
|
||||
tellstick.DISCOVER_LIGHTS: 'tellstick',
|
||||
vera.DISCOVER_LIGHTS: 'vera',
|
||||
}
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
@ -224,7 +227,7 @@ def setup(hass, config):
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
# Without this check, a ctcolor with value '99' would work
|
||||
@ -295,7 +298,7 @@ class Light(ToggleEntity):
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the CT color value in mirads."""
|
||||
"""Return the CT color value in mireds."""
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -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):
|
||||
"""Setup the Hue lights."""
|
||||
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
|
||||
allow_unreachable = config.get('allow_unreachable', False)
|
||||
|
||||
if discovery_info is not None:
|
||||
host = urlparse(discovery_info[1]).hostname
|
||||
else:
|
||||
@ -69,10 +71,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
if host in _CONFIGURING:
|
||||
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."""
|
||||
import phue
|
||||
|
||||
@ -88,7 +91,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
||||
except phue.PhueRegistrationException:
|
||||
_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
|
||||
|
||||
@ -130,7 +134,7 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
||||
if light_id not in lights:
|
||||
lights[light_id] = HueLight(int(light_id), info,
|
||||
bridge, update_lights,
|
||||
bridge_type=bridge_type)
|
||||
bridge_type, allow_unreachable)
|
||||
new_lights.append(lights[light_id])
|
||||
else:
|
||||
lights[light_id].info = info
|
||||
@ -141,7 +145,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
||||
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."""
|
||||
configurator = get_component('configurator')
|
||||
|
||||
@ -155,7 +160,8 @@ def request_configuration(host, hass, add_devices_callback, filename):
|
||||
# pylint: disable=unused-argument
|
||||
def hue_configuration_callback(data):
|
||||
"""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(
|
||||
hass, "Philips Hue", hue_configuration_callback,
|
||||
@ -171,7 +177,7 @@ class HueLight(Light):
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, light_id, info, bridge, update_lights,
|
||||
bridge_type='hue'):
|
||||
bridge_type, allow_unreachable):
|
||||
"""Initialize the light."""
|
||||
self.light_id = light_id
|
||||
self.info = info
|
||||
@ -179,6 +185,8 @@ class HueLight(Light):
|
||||
self.update_lights = update_lights
|
||||
self.bridge_type = bridge_type
|
||||
|
||||
self.allow_unreachable = allow_unreachable
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the ID of this Hue light."""
|
||||
@ -209,7 +217,11 @@ class HueLight(Light):
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
self.update_lights()
|
||||
return self.info['state']['reachable'] and self.info['state']['on']
|
||||
|
||||
if self.allow_unreachable:
|
||||
return self.info['state']['on']
|
||||
else:
|
||||
return self.info['state']['reachable'] and self.info['state']['on']
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the specified or all lights on."""
|
||||
|
@ -4,127 +4,80 @@ Support for Tellstick lights.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.tellstick/
|
||||
"""
|
||||
from homeassistant.components import tellstick
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||
SIGNAL_REPETITIONS = 1
|
||||
from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS,
|
||||
ATTR_DISCOVER_DEVICES,
|
||||
ATTR_DISCOVER_CONFIG)
|
||||
|
||||
|
||||
# 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."""
|
||||
import tellcore.telldus as telldus
|
||||
from tellcore.library import DirectCallbackDispatcher
|
||||
import tellcore.constants as tellcore_constants
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None or
|
||||
tellstick.TELLCORE_REGISTRY is None):
|
||||
return
|
||||
|
||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
||||
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
|
||||
signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG,
|
||||
DEFAULT_SIGNAL_REPETITIONS)
|
||||
|
||||
switches_and_lights = core.devices()
|
||||
lights = []
|
||||
|
||||
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)
|
||||
add_devices(TellstickLight(
|
||||
tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
|
||||
for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
|
||||
|
||||
|
||||
class TellstickLight(Light):
|
||||
class TellstickLight(tellstick.TellstickDevice, Light):
|
||||
"""Representation of a Tellstick light."""
|
||||
|
||||
def __init__(self, tellstick_device, signal_repetitions):
|
||||
"""Initialize the light."""
|
||||
import tellcore.constants as tellcore_constants
|
||||
|
||||
self.tellstick_device = tellstick_device
|
||||
self.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
|
||||
self._brightness = 255
|
||||
tellstick.TellstickDevice.__init__(self,
|
||||
tellstick_device,
|
||||
signal_repetitions)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self._brightness > 0
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
for _ in range(self.signal_repetitions):
|
||||
def set_tellstick_state(self, last_command_sent, last_data_sent):
|
||||
"""Update the internal representation of the switch."""
|
||||
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._brightness = 0
|
||||
self.update_ha_state()
|
||||
elif command == TELLSTICK_DIM:
|
||||
self.tellstick_device.dim(self._brightness)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Command not implemented: {}".format(command))
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
from tellcore.constants import TELLSTICK_DIM
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
|
||||
if brightness is None:
|
||||
self._brightness = 255
|
||||
else:
|
||||
if brightness is not None:
|
||||
self._brightness = brightness
|
||||
|
||||
for _ in range(self.signal_repetitions):
|
||||
self.tellstick_device.dim(self._brightness)
|
||||
self.update_ha_state()
|
||||
self.call_tellstick(TELLSTICK_DIM, self._brightness)
|
||||
|
||||
def update(self):
|
||||
"""Update state of the light."""
|
||||
import tellcore.constants as tellcore_constants
|
||||
|
||||
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
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
from tellcore.constants import TELLSTICK_TURNOFF
|
||||
self.call_tellstick(TELLSTICK_TURNOFF)
|
||||
|
@ -6,15 +6,15 @@ https://home-assistant.io/components/light.vera/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||
from homeassistant.const import (
|
||||
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__)
|
||||
|
||||
@ -22,74 +22,17 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Setup Vera lights."""
|
||||
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)
|
||||
|
||||
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)
|
||||
add_devices_callback(
|
||||
VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light'])
|
||||
|
||||
|
||||
class VeraLight(Light):
|
||||
class VeraLight(VeraDevice, Light):
|
||||
"""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."""
|
||||
self.vera_device = vera_device
|
||||
self.extra_data = extra_data
|
||||
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
|
||||
self._state = False
|
||||
VeraDevice.__init__(self, vera_device, controller)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
@ -137,20 +80,13 @@ class VeraLight(Light):
|
||||
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||
|
||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
return attr
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state == STATE_ON
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Called by the vera device callback to update state."""
|
||||
if self.vera_device.is_switched_on():
|
||||
self._state = STATE_ON
|
||||
else:
|
||||
self._state = STATE_OFF
|
||||
self._state = self.vera_device.is_switched_on()
|
||||
|
@ -8,8 +8,10 @@ import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.components.light import (
|
||||
Light, ATTR_BRIGHTNESS)
|
||||
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
|
||||
ATTR_XY_COLOR)
|
||||
|
||||
DEPENDENCIES = ['wemo']
|
||||
|
||||
@ -39,17 +41,14 @@ def setup_bridge(bridge, add_devices_callback):
|
||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||
def update_lights():
|
||||
"""Update the WeMo led objects with latest info from the bridge."""
|
||||
bridge.bridge_get_lights()
|
||||
bridge.bridge_update()
|
||||
|
||||
new_lights = []
|
||||
|
||||
for light_id, info in bridge.Lights.items():
|
||||
for light_id, device in bridge.Lights.items():
|
||||
if light_id not in lights:
|
||||
lights[light_id] = WemoLight(bridge, light_id, info,
|
||||
update_lights)
|
||||
lights[light_id] = WemoLight(device, update_lights)
|
||||
new_lights.append(lights[light_id])
|
||||
else:
|
||||
lights[light_id].info = info
|
||||
|
||||
if new_lights:
|
||||
add_devices_callback(new_lights)
|
||||
@ -60,44 +59,73 @@ def setup_bridge(bridge, add_devices_callback):
|
||||
class WemoLight(Light):
|
||||
"""Representation of a WeMo light."""
|
||||
|
||||
def __init__(self, bridge, light_id, info, update_lights):
|
||||
def __init__(self, device, update_lights):
|
||||
"""Initialize the light."""
|
||||
self.bridge = bridge
|
||||
self.light_id = light_id
|
||||
self.info = info
|
||||
self.light_id = device.name
|
||||
self.device = device
|
||||
self.update_lights = update_lights
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the ID of this light."""
|
||||
deviceid = self.bridge.light_get_id(self.info)
|
||||
deviceid = self.device.uniqueID
|
||||
return "{}.{}".format(self.__class__, deviceid)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the light."""
|
||||
return self.bridge.light_name(self.info)
|
||||
return self.device.name
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
state = self.bridge.light_get_state(self.info)
|
||||
return int(state['dim'])
|
||||
return self.device.state.get('level', 255)
|
||||
|
||||
@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
|
||||
def is_on(self):
|
||||
"""True if device is on."""
|
||||
state = self.bridge.light_get_state(self.info)
|
||||
return int(state['state'])
|
||||
return self.device.state['onoff'] != 0
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the light on."""
|
||||
dim = kwargs.get(ATTR_BRIGHTNESS, self.brightness)
|
||||
self.bridge.light_set_state(self.info, state=1, dim=dim)
|
||||
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
|
||||
|
||||
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):
|
||||
"""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):
|
||||
"""Synchronize state with bridge."""
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||
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):
|
||||
@ -58,6 +58,11 @@ class WinkLight(Light):
|
||||
"""Return the brightness of the light."""
|
||||
return int(self.wink.brightness() * 255)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
|
@ -101,7 +101,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light):
|
||||
|
||||
# Zwave multilevel switches use a range of [0, 99] to control
|
||||
# brightness.
|
||||
brightness = (self._brightness / 255) * 99
|
||||
brightness = int((self._brightness / 255) * 99)
|
||||
|
||||
if self._value.node.set_dimmer(self._value.value_id, brightness):
|
||||
self._state = STATE_ON
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.lock import LockDevice
|
||||
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):
|
||||
@ -56,6 +56,11 @@ class WinkLockDevice(LockDevice):
|
||||
"""Return true if device is locked."""
|
||||
return self.wink.state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def lock(self, **kwargs):
|
||||
"""Lock the device."""
|
||||
self.wink.set_state(True)
|
||||
|
@ -19,6 +19,8 @@ from homeassistant.const import (
|
||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'media_player'
|
||||
SCAN_INTERVAL = 10
|
||||
|
||||
@ -28,6 +30,7 @@ DISCOVERY_PLATFORMS = {
|
||||
discovery.SERVICE_CAST: 'cast',
|
||||
discovery.SERVICE_SONOS: 'sonos',
|
||||
discovery.SERVICE_PLEX: 'plex',
|
||||
discovery.SERVICE_SQUEEZEBOX: 'squeezebox',
|
||||
}
|
||||
|
||||
SERVICE_PLAY_MEDIA = 'play_media'
|
||||
@ -229,11 +232,9 @@ def setup(hass, config):
|
||||
|
||||
def media_player_service_handler(service):
|
||||
"""Map services to methods on MediaPlayerDevice."""
|
||||
target_players = component.extract_from_service(service)
|
||||
|
||||
method = SERVICE_TO_METHOD[service.service]
|
||||
|
||||
for player in target_players:
|
||||
for player in component.extract_from_service(service):
|
||||
getattr(player, method)()
|
||||
|
||||
if player.should_poll:
|
||||
@ -245,14 +246,15 @@ def setup(hass, config):
|
||||
|
||||
def volume_set_service(service):
|
||||
"""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
|
||||
|
||||
volume = service.data[ATTR_MEDIA_VOLUME_LEVEL]
|
||||
|
||||
for player in target_players:
|
||||
for player in component.extract_from_service(service):
|
||||
player.set_volume_level(volume)
|
||||
|
||||
if player.should_poll:
|
||||
@ -263,14 +265,15 @@ def setup(hass, config):
|
||||
|
||||
def volume_mute_service(service):
|
||||
"""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
|
||||
|
||||
mute = service.data[ATTR_MEDIA_VOLUME_MUTED]
|
||||
|
||||
for player in target_players:
|
||||
for player in component.extract_from_service(service):
|
||||
player.mute_volume(mute)
|
||||
|
||||
if player.should_poll:
|
||||
@ -281,14 +284,15 @@ def setup(hass, config):
|
||||
|
||||
def media_seek_service(service):
|
||||
"""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
|
||||
|
||||
position = service.data[ATTR_MEDIA_SEEK_POSITION]
|
||||
|
||||
for player in target_players:
|
||||
for player in component.extract_from_service(service):
|
||||
player.media_seek(position)
|
||||
|
||||
if player.should_poll:
|
||||
@ -302,10 +306,12 @@ def setup(hass, config):
|
||||
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
|
||||
media_id = service.data.get(ATTR_MEDIA_CONTENT_ID)
|
||||
|
||||
if media_type is None:
|
||||
return
|
||||
|
||||
if media_id is None:
|
||||
if media_type is None or media_id is None:
|
||||
missing_attr = (ATTR_MEDIA_CONTENT_TYPE if media_type is None
|
||||
else ATTR_MEDIA_CONTENT_ID)
|
||||
_LOGGER.error(
|
||||
'Received call to %s without attribute %s',
|
||||
service.service, missing_attr)
|
||||
return
|
||||
|
||||
for player in component.extract_from_service(service):
|
||||
|
@ -239,7 +239,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
||||
if self._cur_track > 0:
|
||||
support |= SUPPORT_PREVIOUS_TRACK
|
||||
|
||||
if self._cur_track < len(self.tracks)-1:
|
||||
if self._cur_track < len(self.tracks) - 1:
|
||||
support |= SUPPORT_NEXT_TRACK
|
||||
|
||||
return support
|
||||
@ -252,7 +252,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
||||
|
||||
def media_next_track(self):
|
||||
"""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.update_ha_state()
|
||||
|
||||
|
@ -50,7 +50,7 @@ class KodiDevice(MediaPlayerDevice):
|
||||
self._server = jsonrpc_requests.Server(
|
||||
'{}/jsonrpc'.format(self._url),
|
||||
auth=auth)
|
||||
self._players = None
|
||||
self._players = list()
|
||||
self._properties = None
|
||||
self._item = None
|
||||
self._app_properties = None
|
||||
@ -67,8 +67,9 @@ class KodiDevice(MediaPlayerDevice):
|
||||
try:
|
||||
return self._server.Player.GetActivePlayers()
|
||||
except jsonrpc_requests.jsonrpc.TransportError:
|
||||
_LOGGER.warning('Unable to fetch kodi data')
|
||||
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||
if self._players is not None:
|
||||
_LOGGER.warning('Unable to fetch kodi data')
|
||||
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
|
||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||
|
||||
_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_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||
|
@ -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'
|
@ -9,8 +9,9 @@ import logging
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
|
||||
|
||||
@ -27,7 +28,8 @@ _REQUESTS_LOGGER = logging.getLogger('requests')
|
||||
_REQUESTS_LOGGER.setLevel(logging.ERROR)
|
||||
|
||||
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
|
||||
@ -222,7 +224,7 @@ class SonosDevice(MediaPlayerDevice):
|
||||
|
||||
@only_if_coordinator
|
||||
def media_play(self):
|
||||
"""Send paly command."""
|
||||
"""Send play command."""
|
||||
self._player.play()
|
||||
|
||||
@only_if_coordinator
|
||||
@ -249,3 +251,8 @@ class SonosDevice(MediaPlayerDevice):
|
||||
def turn_on(self):
|
||||
"""Turn the media player on."""
|
||||
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)
|
||||
|
@ -22,19 +22,32 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
|
||||
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
||||
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
KNOWN_DEVICES = []
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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(
|
||||
"Missing required configuration items in %s: %s",
|
||||
DOMAIN,
|
||||
CONF_HOST)
|
||||
return False
|
||||
|
||||
# Only add a media server once
|
||||
if host in KNOWN_DEVICES:
|
||||
return False
|
||||
KNOWN_DEVICES.append(host)
|
||||
|
||||
lms = LogitechMediaServer(
|
||||
config.get(CONF_HOST),
|
||||
config.get('port', '9090'),
|
||||
host, port,
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD))
|
||||
|
||||
|
91
homeassistant/components/media_player/yamaha.py
Normal file
91
homeassistant/components/media_player/yamaha.py
Normal 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
|
@ -10,11 +10,11 @@ import socket
|
||||
import time
|
||||
|
||||
|
||||
from homeassistant.bootstrap import prepare_setup_platform
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.util as util
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
@ -29,6 +29,7 @@ EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
|
||||
|
||||
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||
|
||||
CONF_EMBEDDED = 'embedded'
|
||||
CONF_BROKER = 'broker'
|
||||
CONF_PORT = 'port'
|
||||
CONF_CLIENT_ID = 'client_id'
|
||||
@ -92,21 +93,50 @@ def subscribe(hass, topic, callback, qos=DEFAULT_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):
|
||||
"""Start the MQTT protocol service."""
|
||||
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER):
|
||||
return False
|
||||
# pylint: disable=too-many-locals
|
||||
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)
|
||||
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
||||
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
|
||||
protocol = util.convert(conf.get(CONF_PROTOCOL), str, DEFAULT_PROTOCOL)
|
||||
|
||||
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)
|
||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
|
||||
protocol = util.convert(conf.get(CONF_PROTOCOL), str, DEFAULT_PROTOCOL)
|
||||
|
||||
if protocol not in (PROTOCOL_31, PROTOCOL_311):
|
||||
_LOGGER.error('Invalid protocol specified: %s. Allowed values: %s, %s',
|
||||
|
114
homeassistant/components/mqtt/server.py
Normal file
114
homeassistant/components/mqtt/server.py
Normal 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
|
@ -71,6 +71,9 @@ def setup(hass, config):
|
||||
message = call.data.get(ATTR_MESSAGE)
|
||||
|
||||
if message is None:
|
||||
_LOGGER.error(
|
||||
'Received call to %s without attribute %s',
|
||||
call.service, ATTR_MESSAGE)
|
||||
return
|
||||
|
||||
title = template.render(
|
||||
|
@ -45,7 +45,7 @@ class FileNotificationService(BaseNotificationService):
|
||||
title = '{} notifications (Log started: {})\n{}\n'.format(
|
||||
kwargs.get(ATTR_TITLE),
|
||||
dt_util.strip_microseconds(dt_util.utcnow()),
|
||||
'-'*80)
|
||||
'-' * 80)
|
||||
file.write(title)
|
||||
|
||||
if self.add_timestamp == 1:
|
||||
|
58
homeassistant/components/notify/gntp.py
Normal file
58
homeassistant/components/notify/gntp.py
Normal 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)
|
87
homeassistant/components/notify/message_bird.py
Normal file
87
homeassistant/components/notify/message_bird.py
Normal 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
|
@ -14,7 +14,7 @@ from homeassistant.helpers import validate_config
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['python-telegram-bot==3.2.0']
|
||||
REQUIREMENTS = ['python-telegram-bot==3.4']
|
||||
|
||||
|
||||
def get_service(hass, config):
|
||||
|
@ -7,9 +7,9 @@ https://home-assistant.io/components/rfxtrx/
|
||||
import logging
|
||||
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/' +
|
||||
'archive/0.5.zip#pyRFXtrx==0.5']
|
||||
REQUIREMENTS = ['pyRFXtrx==0.6.5']
|
||||
|
||||
DOMAIN = "rfxtrx"
|
||||
|
||||
@ -72,6 +72,10 @@ def setup(hass, config):
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
|
@ -19,7 +19,8 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.entity import ToggleEntity, split_entity_id
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
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
|
||||
|
||||
DOMAIN = "script"
|
||||
@ -30,9 +31,7 @@ STATE_NOT_RUNNING = 'Not Running'
|
||||
|
||||
CONF_ALIAS = "alias"
|
||||
CONF_SERVICE = "service"
|
||||
CONF_SERVICE_OLD = "execute_service"
|
||||
CONF_SERVICE_DATA = "data"
|
||||
CONF_SERVICE_DATA_OLD = "service_data"
|
||||
CONF_SEQUENCE = "sequence"
|
||||
CONF_EVENT = "event"
|
||||
CONF_EVENT_DATA = "event_data"
|
||||
@ -174,7 +173,7 @@ class Script(ToggleEntity):
|
||||
for cur, action in islice(enumerate(self.sequence), self._cur,
|
||||
None):
|
||||
|
||||
if CONF_SERVICE in action or CONF_SERVICE_OLD in action:
|
||||
if validate_service_call(action) is None:
|
||||
self._call_service(action)
|
||||
|
||||
elif CONF_EVENT in action:
|
||||
@ -211,14 +210,7 @@ class Script(ToggleEntity):
|
||||
|
||||
def _call_service(self, action):
|
||||
"""Call the service specified in the action."""
|
||||
# Backwards compatibility
|
||||
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])
|
||||
self._last_action = action.get(CONF_ALIAS, 'call service')
|
||||
_LOGGER.info("Executing script %s step %s", self._name,
|
||||
self._last_action)
|
||||
call_from_config(self.hass, action, True)
|
||||
|
@ -8,7 +8,8 @@ import logging
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.components import (
|
||||
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, bloomsky)
|
||||
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors,
|
||||
bloomsky, vera)
|
||||
|
||||
DOMAIN = 'sensor'
|
||||
SCAN_INTERVAL = 30
|
||||
@ -25,6 +26,7 @@ DISCOVERY_PLATFORMS = {
|
||||
ecobee.DISCOVER_SENSORS: 'ecobee',
|
||||
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
||||
mysensors.DISCOVER_SENSORS: 'mysensors',
|
||||
vera.DISCOVER_SENSORS: 'vera',
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
https://home-assistant.io/components/sensor.bitcoin/
|
||||
@ -10,10 +10,9 @@ from datetime import timedelta
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['blockchain==1.2.1']
|
||||
REQUIREMENTS = ['blockchain==1.3.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
OPTION_TYPES = {
|
||||
'wallet': ['Wallet balance', 'BTC'],
|
||||
'exchangerate': ['Exchange rate (1 BTC)', None],
|
||||
'trade_volume_btc': ['Trade volume', 'BTC'],
|
||||
'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):
|
||||
"""Setup the Bitcoin sensor."""
|
||||
from blockchain.wallet import Wallet
|
||||
from blockchain import exchangerates, exceptions
|
||||
"""Setup the Bitcoin sensors."""
|
||||
from blockchain import exchangerates
|
||||
|
||||
wallet_id = config.get('wallet', None)
|
||||
password = config.get('password', None)
|
||||
currency = config.get('currency', 'USD')
|
||||
|
||||
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'
|
||||
|
||||
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()
|
||||
dev = []
|
||||
if wallet is not None and password is not None:
|
||||
dev.append(BitcoinSensor(data, 'wallet', currency, wallet))
|
||||
|
||||
for variable in config['display_options']:
|
||||
if variable not in OPTION_TYPES:
|
||||
_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):
|
||||
"""Representation of a Bitcoin sensor."""
|
||||
|
||||
def __init__(self, data, option_type, currency, wallet=''):
|
||||
def __init__(self, data, option_type, currency):
|
||||
"""Initialize the sensor."""
|
||||
self.data = data
|
||||
self._name = OPTION_TYPES[option_type][0]
|
||||
self._unit_of_measurement = OPTION_TYPES[option_type][1]
|
||||
self._currency = currency
|
||||
self._wallet = wallet
|
||||
self.type = option_type
|
||||
self._state = None
|
||||
self.update()
|
||||
@ -122,10 +104,7 @@ class BitcoinSensor(Entity):
|
||||
ticker = self.data.ticker
|
||||
|
||||
# pylint: disable=no-member
|
||||
if self.type == 'wallet' and self._wallet is not None:
|
||||
self._state = '{0:.8f}'.format(self._wallet.get_balance() *
|
||||
0.00000001)
|
||||
elif self.type == 'exchangerate':
|
||||
if self.type == 'exchangerate':
|
||||
self._state = ticker[self._currency].p15min
|
||||
self._unit_of_measurement = self._currency
|
||||
elif self.type == 'trade_volume_btc':
|
||||
|
@ -11,7 +11,7 @@ from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['python-forecastio==1.3.3']
|
||||
REQUIREMENTS = ['python-forecastio==1.3.4']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Sensor types are defined like so:
|
||||
|
@ -61,12 +61,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
return False
|
||||
except requests.exceptions.MissingSchema:
|
||||
_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
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint: '%s'. "
|
||||
"Please check the details in the configuration file.",
|
||||
url)
|
||||
_LOGGER.error("No route to resource/endpoint: %s", url)
|
||||
return False
|
||||
|
||||
rest = GlancesData(url)
|
||||
@ -167,6 +165,5 @@ class GlancesData(object):
|
||||
response = requests.get(self._resource, timeout=10)
|
||||
self.data = response.json()
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to host/endpoint '%s'. Is device offline?",
|
||||
self._resource)
|
||||
_LOGGER.error("No route to host/endpoint: %s", self._resource)
|
||||
self.data = None
|
||||
|
@ -32,10 +32,7 @@ JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity',
|
||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
|
||||
'kph': 'kph', 'temperature': '°C'}
|
||||
|
||||
SENSOR_TEMP_TYPES = ['temperature',
|
||||
'target',
|
||||
'away_temperature[0]',
|
||||
'away_temperature[1]']
|
||||
SENSOR_TEMP_TYPES = ['temperature', 'target']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['psutil==4.0.0']
|
||||
REQUIREMENTS = ['psutil==4.1.0']
|
||||
SENSOR_TYPES = {
|
||||
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||
@ -38,7 +38,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the sensors."""
|
||||
"""Setup the System sensors."""
|
||||
dev = []
|
||||
for resource in config['resources']:
|
||||
if 'arg' not in resource:
|
||||
|
@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.template/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
from homeassistant.components.sensor import ENTITY_ID_FORMAT
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
|
||||
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.util import slugify
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_SENSORS = 'sensors'
|
||||
STATE_ERROR = 'error'
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@ -70,21 +67,21 @@ class SensorTemplate(Entity):
|
||||
def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
|
||||
state_template):
|
||||
"""Initialize the sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
|
||||
self.hass = hass
|
||||
self._name = friendly_name
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._template = state_template
|
||||
self.update()
|
||||
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
||||
self._state = None
|
||||
|
||||
def _event_listener(self, event):
|
||||
"""Called when the target device changes state."""
|
||||
if not hasattr(self, 'hass'):
|
||||
return
|
||||
self.update_ha_state(True)
|
||||
self.update()
|
||||
|
||||
def template_sensor_event_listener(event):
|
||||
"""Called when the target device changes state."""
|
||||
self.update_ha_state(True)
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, template_sensor_event_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -111,10 +108,10 @@ class SensorTemplate(Entity):
|
||||
try:
|
||||
self._state = template.render(self.hass, self._template)
|
||||
except TemplateError as ex:
|
||||
self._state = STATE_ERROR
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
# Common during HA startup - so just a warning
|
||||
_LOGGER.warning(ex)
|
||||
return
|
||||
self._state = None
|
||||
_LOGGER.error(ex)
|
||||
|
208
homeassistant/components/sensor/uber.py
Normal file
208
homeassistant/components/sensor/uber.py
Normal 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')
|
@ -6,109 +6,40 @@ https://home-assistant.io/components/sensor.vera/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
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.components.vera import (
|
||||
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||
|
||||
REQUIREMENTS = ['pyvera==0.2.8']
|
||||
DEPENDENCIES = ['vera']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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):
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""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."""
|
||||
|
||||
def __init__(self, vera_device, controller, extra_data=None):
|
||||
def __init__(self, vera_device, controller):
|
||||
"""Initialize the sensor."""
|
||||
self.vera_device = vera_device
|
||||
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.current_value = None
|
||||
self._temperature_units = None
|
||||
|
||||
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)
|
||||
VeraDevice.__init__(self, vera_device, controller)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self.current_value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the mame of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""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
|
||||
return attr
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
"""Update the state."""
|
||||
if self.vera_device.category == "Temperature Sensor":
|
||||
|
@ -10,7 +10,7 @@ from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
|
||||
STATE_OPEN, TEMP_CELCIUS)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.6.2']
|
||||
REQUIREMENTS = ['python-wink==0.6.4']
|
||||
|
||||
SENSOR_TYPES = ['temperature', 'humidity']
|
||||
|
||||
@ -74,6 +74,11 @@ class WinkSensorDevice(Entity):
|
||||
"""Return the name of the sensor if any."""
|
||||
return self.wink.name()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def update(self):
|
||||
"""Update state of the sensor."""
|
||||
self.wink.update_state()
|
||||
|
@ -16,7 +16,8 @@ from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
ATTR_ENTITY_ID)
|
||||
from homeassistant.components import (
|
||||
group, wemo, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
||||
group, wemo, wink, isy994, verisure,
|
||||
zwave, tellduslive, tellstick, mysensors, vera)
|
||||
|
||||
DOMAIN = 'switch'
|
||||
SCAN_INTERVAL = 30
|
||||
@ -40,6 +41,8 @@ DISCOVERY_PLATFORMS = {
|
||||
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
||||
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
||||
tellstick.DISCOVER_SWITCHES: 'tellstick',
|
||||
vera.DISCOVER_SWITCHES: 'vera',
|
||||
}
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
|
@ -28,24 +28,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
pins = config.get('pins')
|
||||
for pinnum, pin in pins.items():
|
||||
if pin.get('name'):
|
||||
switches.append(ArduinoSwitch(pin.get('name'),
|
||||
pinnum,
|
||||
pin.get('type')))
|
||||
switches.append(ArduinoSwitch(pinnum, pin))
|
||||
add_devices(switches)
|
||||
|
||||
|
||||
class ArduinoSwitch(SwitchDevice):
|
||||
"""Representation of an Arduino switch."""
|
||||
|
||||
def __init__(self, name, pin, pin_type):
|
||||
def __init__(self, pin, options):
|
||||
"""Initialize the Pin."""
|
||||
self._pin = pin
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self.pin_type = pin_type
|
||||
self._name = options.get('name') or DEVICE_DEFAULT_NAME
|
||||
self.pin_type = options.get('type')
|
||||
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)
|
||||
(self.turn_on_handler if self._state else self.turn_off_handler)(pin)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -60,9 +67,9 @@ class ArduinoSwitch(SwitchDevice):
|
||||
def turn_on(self):
|
||||
"""Turn the pin to high/on."""
|
||||
self._state = True
|
||||
arduino.BOARD.set_digital_out_high(self._pin)
|
||||
self.turn_on_handler(self._pin)
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn the pin to low/off."""
|
||||
self._state = False
|
||||
arduino.BOARD.set_digital_out_low(self._pin)
|
||||
self.turn_off_handler(self._pin)
|
||||
|
@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
add_devices_callback(devices)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class CommandSwitch(SwitchDevice):
|
||||
"""Representation a switch that can be toggled using shell commands."""
|
||||
|
||||
@ -92,6 +93,11 @@ class CommandSwitch(SwitchDevice):
|
||||
"""Return true if device is on."""
|
||||
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):
|
||||
"""Query for state."""
|
||||
if not self._command_state:
|
||||
|
131
homeassistant/components/switch/pulseaudio_loopback.py
Normal file
131
homeassistant/components/switch/pulseaudio_loopback.py
Normal 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
|
@ -4,98 +4,56 @@ Support for Tellstick switches.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/switch.tellstick/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.components import tellstick
|
||||
from homeassistant.components.tellstick import (ATTR_DISCOVER_DEVICES,
|
||||
ATTR_DISCOVER_CONFIG)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
||||
SIGNAL_REPETITIONS = 1
|
||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# 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."""
|
||||
import tellcore.telldus as telldus
|
||||
import tellcore.constants as tellcore_constants
|
||||
from tellcore.library import DirectCallbackDispatcher
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None or
|
||||
tellstick.TELLCORE_REGISTRY is None):
|
||||
return
|
||||
|
||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
||||
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
|
||||
switches_and_lights = core.devices()
|
||||
# Allow platform level override, fallback to module config
|
||||
signal_repetitions = discovery_info.get(
|
||||
ATTR_DISCOVER_CONFIG, tellstick.DEFAULT_SIGNAL_REPETITIONS)
|
||||
|
||||
switches = []
|
||||
for switch in switches_and_lights:
|
||||
if not switch.methods(tellcore_constants.TELLSTICK_DIM):
|
||||
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)
|
||||
add_devices(TellstickSwitchDevice(
|
||||
tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
|
||||
for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
|
||||
|
||||
|
||||
class TellstickSwitchDevice(ToggleEntity):
|
||||
class TellstickSwitchDevice(tellstick.TellstickDevice, ToggleEntity):
|
||||
"""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
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
import tellcore.constants as tellcore_constants
|
||||
return self._state
|
||||
|
||||
last_command = self.tellstick_device.last_sent_command(
|
||||
self.last_sent_command_mask)
|
||||
def set_tellstick_state(self, last_command_sent, last_data_sent):
|
||||
"""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):
|
||||
"""Turn the switch on."""
|
||||
for _ in range(self.signal_repetitions):
|
||||
self.tellstick_device.turn_on()
|
||||
self.update_ha_state()
|
||||
from tellcore.constants import TELLSTICK_TURNON
|
||||
self.call_tellstick(TELLSTICK_TURNON)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
for _ in range(self.signal_repetitions):
|
||||
self.tellstick_device.turn_off()
|
||||
self.update_ha_state()
|
||||
from tellcore.constants import TELLSTICK_TURNOFF
|
||||
self.call_tellstick(TELLSTICK_TURNOFF)
|
||||
|
@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.template/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import DOMAIN, SwitchDevice
|
||||
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON)
|
||||
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.util import slugify
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SWITCHES = 'switches'
|
||||
|
||||
STATE_ERROR = 'error'
|
||||
|
||||
ON_ACTION = 'turn_on'
|
||||
OFF_ACTION = 'turn_off'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
on_action, off_action):
|
||||
"""Initialize the Template switch."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self.hass = hass
|
||||
self._name = friendly_name
|
||||
self._template = state_template
|
||||
self._on_action = on_action
|
||||
self._off_action = off_action
|
||||
self.update()
|
||||
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
||||
self._state = False
|
||||
|
||||
def _event_listener(self, event):
|
||||
"""Called when the target device changes state."""
|
||||
if not hasattr(self, 'hass'):
|
||||
return
|
||||
self.update_ha_state(True)
|
||||
self.update()
|
||||
|
||||
def template_switch_event_listener(event):
|
||||
"""Called when the target device changes state."""
|
||||
self.update_ha_state(True)
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||
template_switch_event_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the switch."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""If switch is available."""
|
||||
return self._state is not None
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Fire the on action."""
|
||||
call_from_config(self.hass, self._on_action, True)
|
||||
@ -118,30 +127,19 @@ class SwitchTemplate(SwitchDevice):
|
||||
"""Fire the off action."""
|
||||
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):
|
||||
"""Update the state from the template."""
|
||||
try:
|
||||
self._value = template.render(self.hass, self._template)
|
||||
if not self.available:
|
||||
state = template.render(self.hass, self._template).lower()
|
||||
|
||||
if state in _VALID_STATES:
|
||||
self._state = state in ('true', STATE_ON)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"`%s` is not a switch state, setting %s to unavailable",
|
||||
self._value, self.entity_id)
|
||||
'Received invalid switch is_on state: %s. Expected: %s',
|
||||
state, ', '.join(_VALID_STATES))
|
||||
self._state = None
|
||||
|
||||
except TemplateError as ex:
|
||||
self._value = STATE_ERROR
|
||||
_LOGGER.error(ex)
|
||||
self._state = None
|
||||
|
@ -6,94 +6,33 @@ https://home-assistant.io/components/switch.vera/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import (
|
||||
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__)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get_devices(hass, config):
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Find and return Vera switches."""
|
||||
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)
|
||||
|
||||
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
|
||||
add_devices_callback(
|
||||
VeraSwitch(device, VERA_CONTROLLER) for
|
||||
device in VERA_DEVICES['switch'])
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Find and return Vera lights."""
|
||||
add_devices(get_devices(hass, config))
|
||||
|
||||
|
||||
class VeraSwitch(SwitchDevice):
|
||||
class VeraSwitch(VeraDevice, SwitchDevice):
|
||||
"""Representation of a Vera Switch."""
|
||||
|
||||
def __init__(self, vera_device, controller, extra_data=None):
|
||||
def __init__(self, vera_device, controller):
|
||||
"""Initialize the Vera device."""
|
||||
self.vera_device = vera_device
|
||||
self.extra_data = extra_data
|
||||
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
|
||||
self._state = False
|
||||
VeraDevice.__init__(self, vera_device, controller)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
@ -134,19 +73,11 @@ class VeraSwitch(SwitchDevice):
|
||||
self._state = STATE_OFF
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state == STATE_ON
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Called by the vera device callback to update state."""
|
||||
if self.vera_device.is_switched_on():
|
||||
self._state = STATE_ON
|
||||
else:
|
||||
self._state = STATE_OFF
|
||||
self._state = self.vera_device.is_switched_on()
|
||||
|
79
homeassistant/components/switch/wake_on_lan.py
Normal file
79
homeassistant/components/switch/wake_on_lan.py
Normal 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)
|
@ -63,6 +63,9 @@ class WemoSwitch(SwitchDevice):
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s',
|
||||
_device)
|
||||
if not hasattr(self, 'hass'):
|
||||
self.update()
|
||||
return
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.wink import WinkToggleDevice
|
||||
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):
|
||||
|
217
homeassistant/components/tellstick.py
Normal file
217
homeassistant/components/tellstick.py
Normal 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)
|
@ -123,6 +123,9 @@ def setup(hass, config):
|
||||
service.data.get(ATTR_TEMPERATURE), float)
|
||||
|
||||
if temperature is None:
|
||||
_LOGGER.error(
|
||||
"Received call to %s without attribute %s",
|
||||
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
|
||||
return
|
||||
|
||||
for thermostat in target_thermostats:
|
||||
|
@ -11,8 +11,8 @@ from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Demo thermostats."""
|
||||
add_devices([
|
||||
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19),
|
||||
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77),
|
||||
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19, False),
|
||||
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77, True),
|
||||
])
|
||||
|
||||
|
||||
@ -21,13 +21,14 @@ class DemoThermostat(ThermostatDevice):
|
||||
"""Representation of a demo thermostat."""
|
||||
|
||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
||||
away, current_temperature):
|
||||
away, current_temperature, is_fan_on):
|
||||
"""Initialize the thermostat."""
|
||||
self._name = name
|
||||
self._target_temperature = target_temperature
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._away = away
|
||||
self._current_temperature = current_temperature
|
||||
self._is_fan_on = is_fan_on
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@ -59,6 +60,11 @@ class DemoThermostat(ThermostatDevice):
|
||||
"""Return if away mode is on."""
|
||||
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):
|
||||
"""Set new target temperature."""
|
||||
self._target_temperature = temperature
|
||||
@ -70,3 +76,11 @@ class DemoThermostat(ThermostatDevice):
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
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
|
||||
|
142
homeassistant/components/vera.py
Normal file
142
homeassistant/components/vera.py
Normal 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
|
@ -23,7 +23,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
||||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||
DISCOVER_LOCKS = 'verisure.lock'
|
||||
|
||||
REQUIREMENTS = ['vsure==0.6.1']
|
||||
REQUIREMENTS = ['vsure==0.7.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components import discovery
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['pywemo==0.3.12']
|
||||
REQUIREMENTS = ['pywemo==0.3.19']
|
||||
|
||||
DOMAIN = 'wemo'
|
||||
DISCOVER_LIGHTS = 'wemo.light'
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
DOMAIN = "wink"
|
||||
REQUIREMENTS = ['python-wink==0.6.2']
|
||||
REQUIREMENTS = ['python-wink==0.6.4']
|
||||
|
||||
DISCOVER_LIGHTS = "wink.lights"
|
||||
DISCOVER_SWITCHES = "wink.switches"
|
||||
@ -84,6 +84,11 @@ class WinkToggleDevice(ToggleEntity):
|
||||
"""Return true if device is on."""
|
||||
return self.wink.state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self.wink.set_state(True)
|
||||
|
@ -28,6 +28,8 @@ DEFAULT_ZWAVE_CONFIG_PATH = os.path.join(sys.prefix, 'share',
|
||||
|
||||
SERVICE_ADD_NODE = "add_node"
|
||||
SERVICE_REMOVE_NODE = "remove_node"
|
||||
SERVICE_HEAL_NETWORK = "heal_network"
|
||||
SERVICE_SOFT_RESET = "soft_reset"
|
||||
|
||||
DISCOVER_SENSORS = "zwave.sensors"
|
||||
DISCOVER_SWITCHES = "zwave.switch"
|
||||
@ -149,6 +151,7 @@ def get_config_value(node, value_index):
|
||||
return get_config_value(node, value_index)
|
||||
|
||||
|
||||
# pylint: disable=R0914
|
||||
def setup(hass, config):
|
||||
"""Setup Z-Wave.
|
||||
|
||||
@ -249,6 +252,14 @@ def setup(hass, config):
|
||||
"""Switch into exclusion mode."""
|
||||
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):
|
||||
"""Stop Z-Wave."""
|
||||
NETWORK.stop()
|
||||
@ -268,6 +279,8 @@ def setup(hass, config):
|
||||
# hardware inclusion button
|
||||
hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_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)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# coding: utf-8
|
||||
"""Constants used by Home Assistant components."""
|
||||
|
||||
__version__ = "0.15.0"
|
||||
__version__ = "0.16.0"
|
||||
REQUIRED_PYTHON_VER = (3, 4)
|
||||
|
||||
# Can be used to specify a catch all when registering state or event listeners.
|
||||
|
@ -775,7 +775,7 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
||||
|
||||
def start_timer(event):
|
||||
"""Start the timer."""
|
||||
thread = threading.Thread(target=timer)
|
||||
thread = threading.Thread(target=timer, name='Timer')
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
@ -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):
|
||||
"""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()
|
||||
if current_ids is None:
|
||||
if hass is None:
|
||||
@ -41,12 +41,12 @@ def valid_entity_id(entity_id):
|
||||
|
||||
|
||||
class Entity(object):
|
||||
"""ABC for Home Assistant entities."""
|
||||
"""An abstract class for Home Assistant entities."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
# SAFE TO OVERWRITE
|
||||
# The properties and methods here are safe to overwrite when inherting this
|
||||
# class. These may be used to customize the behavior of the entity.
|
||||
# The properties and methods here are safe to overwrite when inheriting
|
||||
# this class. These may be used to customize the behavior of the entity.
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return True if entity has to be polled for state.
|
||||
@ -57,7 +57,7 @@ class Entity(object):
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
"""Return an unique ID."""
|
||||
return "{}.{}".format(self.__class__, id(self))
|
||||
|
||||
@property
|
||||
@ -113,7 +113,7 @@ class Entity(object):
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def update(self):
|
||||
@ -223,7 +223,7 @@ class Entity(object):
|
||||
|
||||
|
||||
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
|
||||
@property
|
||||
|
@ -331,7 +331,9 @@ class ThreadPool(object):
|
||||
if not self.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.start()
|
||||
|
||||
|
@ -31,7 +31,7 @@ astral==0.9
|
||||
blinkstick==1.1.7
|
||||
|
||||
# homeassistant.components.sensor.bitcoin
|
||||
blockchain==1.2.1
|
||||
blockchain==1.3.1
|
||||
|
||||
# homeassistant.components.notify.xmpp
|
||||
dnspython3==1.12.0
|
||||
@ -54,6 +54,12 @@ freesms==0.1.0
|
||||
# homeassistant.components.conversation
|
||||
fuzzywuzzy==0.8.0
|
||||
|
||||
# homeassistant.components.notify.gntp
|
||||
gntp==1.0.3
|
||||
|
||||
# homeassistant.components.mqtt.server
|
||||
hbmqtt==0.6.3
|
||||
|
||||
# homeassistant.components.thermostat.heatmiser
|
||||
heatmiserV3==0.9.1
|
||||
|
||||
@ -63,9 +69,6 @@ hikvision==0.4
|
||||
# homeassistant.components.sensor.dht
|
||||
# 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
|
||||
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
|
||||
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
|
||||
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
|
||||
limitlessled==1.0.0
|
||||
|
||||
# homeassistant.components.notify.message_bird
|
||||
messagebird==1.1.1
|
||||
|
||||
# homeassistant.components.sensor.mfi
|
||||
# homeassistant.components.switch.mfi
|
||||
mficlient==0.3.0
|
||||
|
||||
# homeassistant.components.discovery
|
||||
netdisco==0.5.4
|
||||
netdisco==0.5.5
|
||||
|
||||
# homeassistant.components.sensor.neurio_energy
|
||||
neurio==0.2.10
|
||||
@ -143,7 +152,7 @@ plexapi==1.1.0
|
||||
proliphix==0.1.0
|
||||
|
||||
# homeassistant.components.sensor.systemmonitor
|
||||
psutil==4.0.0
|
||||
psutil==4.1.0
|
||||
|
||||
# homeassistant.components.notify.pushbullet
|
||||
pushbullet.py==0.9.0
|
||||
@ -154,6 +163,9 @@ pushetta==1.0.15
|
||||
# homeassistant.components.sensor.cpuspeed
|
||||
py-cpuinfo==0.2.3
|
||||
|
||||
# homeassistant.components.rfxtrx
|
||||
pyRFXtrx==0.6.5
|
||||
|
||||
# homeassistant.components.media_player.cast
|
||||
pychromecast==0.7.2
|
||||
|
||||
@ -164,7 +176,7 @@ pydispatcher==2.0.5
|
||||
pyfttt==0.3
|
||||
|
||||
# homeassistant.components.device_tracker.icloud
|
||||
pyicloud==0.7.2
|
||||
pyicloud==0.8.1
|
||||
|
||||
# homeassistant.components.device_tracker.netgear
|
||||
pynetgear==0.3.2
|
||||
@ -180,16 +192,16 @@ pyowm==2.3.0
|
||||
pysnmp==4.2.5
|
||||
|
||||
# homeassistant.components.sensor.forecast
|
||||
python-forecastio==1.3.3
|
||||
python-forecastio==1.3.4
|
||||
|
||||
# homeassistant.components.media_player.mpd
|
||||
python-mpd2==0.5.4
|
||||
python-mpd2==0.5.5
|
||||
|
||||
# homeassistant.components.nest
|
||||
python-nest==2.6.0
|
||||
|
||||
# homeassistant.components.device_tracker.nmap_tracker
|
||||
python-nmap==0.4.3
|
||||
python-nmap==0.6.0
|
||||
|
||||
# homeassistant.components.notify.pushover
|
||||
python-pushover==0.2
|
||||
@ -198,7 +210,7 @@ python-pushover==0.2
|
||||
python-statsd==1.7.2
|
||||
|
||||
# homeassistant.components.notify.telegram
|
||||
python-telegram-bot==3.2.0
|
||||
python-telegram-bot==3.4
|
||||
|
||||
# homeassistant.components.sensor.twitch
|
||||
python-twitch==1.2.0
|
||||
@ -210,22 +222,23 @@ python-twitch==1.2.0
|
||||
# homeassistant.components.lock.wink
|
||||
# homeassistant.components.sensor.wink
|
||||
# homeassistant.components.switch.wink
|
||||
python-wink==0.6.2
|
||||
python-wink==0.6.4
|
||||
|
||||
# homeassistant.components.keyboard
|
||||
pyuserinput==0.1.9
|
||||
|
||||
# homeassistant.components.light.vera
|
||||
# homeassistant.components.sensor.vera
|
||||
# homeassistant.components.switch.vera
|
||||
# homeassistant.components.vera
|
||||
pyvera==0.2.8
|
||||
|
||||
# homeassistant.components.wemo
|
||||
pywemo==0.3.12
|
||||
pywemo==0.3.19
|
||||
|
||||
# homeassistant.components.thermostat.radiotherm
|
||||
radiotherm==1.2
|
||||
|
||||
# homeassistant.components.media_player.yamaha
|
||||
rxv==0.1.9
|
||||
|
||||
# homeassistant.components.media_player.samsungtv
|
||||
samsungctl==0.5.1
|
||||
|
||||
@ -256,9 +269,8 @@ speedtest-cli==0.3.4
|
||||
# homeassistant.components.sensor.steam_online
|
||||
steamodd==4.21
|
||||
|
||||
# homeassistant.components.light.tellstick
|
||||
# homeassistant.components.tellstick
|
||||
# homeassistant.components.sensor.tellstick
|
||||
# homeassistant.components.switch.tellstick
|
||||
tellcore-py==1.1.2
|
||||
|
||||
# homeassistant.components.tellduslive
|
||||
@ -278,7 +290,10 @@ urllib3
|
||||
uvcclient==0.8
|
||||
|
||||
# homeassistant.components.verisure
|
||||
vsure==0.6.1
|
||||
vsure==0.7.1
|
||||
|
||||
# homeassistant.components.switch.wake_on_lan
|
||||
wakeonlan==0.2.2
|
||||
|
||||
# homeassistant.components.zigbee
|
||||
xbee-helper==0.0.6
|
||||
|
@ -24,50 +24,6 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
""""Stop everything that was started."""
|
||||
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):
|
||||
"""Test the firing of events."""
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
|
@ -24,71 +24,6 @@ class TestAutomation(unittest.TestCase):
|
||||
"""Stop everything that was started."""
|
||||
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):
|
||||
"""Test service data not dict."""
|
||||
automation.setup(self.hass, {
|
||||
|
@ -24,50 +24,6 @@ class TestAutomationMQTT(unittest.TestCase):
|
||||
"""Stop everything that was started."""
|
||||
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):
|
||||
"""Test if message is fired on topic match."""
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
|
@ -28,143 +28,6 @@ class TestAutomationState(unittest.TestCase):
|
||||
"""Stop everything that was started."""
|
||||
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):
|
||||
"""Test for firing on entity change."""
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
|
@ -5,8 +5,6 @@ from unittest.mock import patch
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
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
|
||||
|
||||
@ -28,207 +26,6 @@ class TestAutomationTime(unittest.TestCase):
|
||||
"""Stop everything that was started."""
|
||||
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):
|
||||
"""Test for firing if hour is matching."""
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
|
@ -2,9 +2,12 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from homeassistant.const import EVENT_STATE_CHANGED
|
||||
from homeassistant.components.binary_sensor import template
|
||||
from homeassistant.exceptions import TemplateError
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestBinarySensorTemplate(unittest.TestCase):
|
||||
"""Test for Binary sensor template platform."""
|
||||
@ -88,21 +91,19 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
||||
|
||||
def test_event(self):
|
||||
""""Test the event."""
|
||||
hass = mock.MagicMock()
|
||||
hass = get_test_home_assistant()
|
||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||
'motion', '{{ 1 > 1 }}')
|
||||
with mock.patch.object(vs, 'update_ha_state') as mock_update:
|
||||
vs._event_listener(None)
|
||||
mock_update.assert_called_once_with(True)
|
||||
vs.update_ha_state()
|
||||
hass.pool.block_till_done()
|
||||
|
||||
def test_update(self):
|
||||
""""Test the update."""
|
||||
hass = mock.MagicMock()
|
||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||
'motion', '{{ 2 > 1 }}')
|
||||
self.assertEqual(None, vs._state)
|
||||
vs.update()
|
||||
self.assertTrue(vs._state)
|
||||
with mock.patch.object(vs, 'update') as mock_update:
|
||||
hass.bus.fire(EVENT_STATE_CHANGED)
|
||||
hass.pool.block_till_done()
|
||||
try:
|
||||
assert mock_update.call_count == 1
|
||||
finally:
|
||||
hass.stop()
|
||||
|
||||
@mock.patch('homeassistant.helpers.template.render')
|
||||
def test_update_template_error(self, mock_render):
|
||||
|
@ -133,15 +133,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
|
||||
'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
|
||||
self.hass.states.set(DEVICE_TRACKER_STATE, None)
|
||||
owntracks.REGIONS_ENTERED = defaultdict(list)
|
||||
@ -325,43 +316,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
|
||||
self.send_message(EVENT_TOPIC, message)
|
||||
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):
|
||||
"""Test the event for unknown zone."""
|
||||
# Just treat as location update
|
||||
|
@ -5,12 +5,9 @@ from homeassistant.components import rfxtrx as rfxtrx_core
|
||||
from homeassistant.components.light import rfxtrx
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
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):
|
||||
"""Test the Rfxtrx light platform."""
|
||||
|
||||
@ -22,6 +19,8 @@ class TestLightRfxtrx(unittest.TestCase):
|
||||
"""Stop everything that was started."""
|
||||
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS = []
|
||||
rfxtrx_core.RFX_DEVICES = {}
|
||||
if rfxtrx_core.RFXOBJECT:
|
||||
rfxtrx_core.RFXOBJECT.close_connection()
|
||||
self.hass.stop()
|
||||
|
||||
def test_default_config(self):
|
||||
|
@ -25,6 +25,11 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
state = self.hass.states.get(entity_id)
|
||||
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)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(entity_id)
|
||||
@ -41,6 +46,12 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
assert 0.5 == state.attributes.get('volume_level')
|
||||
|
||||
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)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(entity_id)
|
||||
@ -87,7 +98,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
assert self.hass.states.is_state(entity_id, 'playing')
|
||||
|
||||
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'}})
|
||||
state = self.hass.states.get(entity_id)
|
||||
assert 1 == state.attributes.get('media_track')
|
||||
@ -115,6 +126,27 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
|
||||
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.'
|
||||
'media_seek')
|
||||
def test_play_media(self, mock_seek):
|
||||
@ -126,6 +158,13 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
state.attributes.get('supported_media_commands'))
|
||||
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)
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get(ent_id)
|
||||
@ -133,6 +172,9 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
state.attributes.get('supported_media_commands'))
|
||||
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
|
||||
mp.media_seek(self.hass, 100, ent_id)
|
||||
self.hass.pool.block_till_done()
|
||||
|
@ -25,8 +25,37 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
||||
self._media_title = None
|
||||
self._supported_media_commands = 0
|
||||
|
||||
self.turn_off_service_calls = mock_service(
|
||||
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF)
|
||||
self.service_calls = {
|
||||
'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
|
||||
def name(self):
|
||||
@ -97,23 +126,26 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state')
|
||||
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
|
||||
|
||||
self.config_children_only = \
|
||||
{'name': 'test', 'platform': 'universal',
|
||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||
media_player.ENTITY_ID_FORMAT.format('mock2')]}
|
||||
self.config_children_and_attr = \
|
||||
{'name': 'test', 'platform': 'universal',
|
||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
||||
'attributes': {
|
||||
'is_volume_muted': self.mock_mute_switch_id,
|
||||
'state': self.mock_state_switch_id}}
|
||||
self.config_children_only = {
|
||||
'name': 'test', 'platform': 'universal',
|
||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||
media_player.ENTITY_ID_FORMAT.format('mock2')]
|
||||
}
|
||||
self.config_children_and_attr = {
|
||||
'name': 'test', 'platform': 'universal',
|
||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
||||
'attributes': {
|
||||
'is_volume_muted': self.mock_mute_switch_id,
|
||||
'state': self.mock_state_switch_id
|
||||
}
|
||||
}
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_check_config_children_only(self):
|
||||
def test_config_children_only(self):
|
||||
"""Check config with only children."""
|
||||
config_start = copy(self.config_children_only)
|
||||
del config_start['platform']
|
||||
@ -125,7 +157,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertTrue(response)
|
||||
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."""
|
||||
config_start = copy(self.config_children_and_attr)
|
||||
del config_start['platform']
|
||||
@ -136,13 +168,13 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertTrue(response)
|
||||
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."""
|
||||
response = universal.validate_config({'platform': 'universal'})
|
||||
|
||||
self.assertFalse(response)
|
||||
|
||||
def test_check_config_bad_children(self):
|
||||
def test_config_bad_children(self):
|
||||
"""Check config with bad children entry."""
|
||||
config_no_children = {'name': 'test', 'platform': 'universal'}
|
||||
config_bad_children = {'name': 'test', 'children': {},
|
||||
@ -156,7 +188,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertTrue(response)
|
||||
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."""
|
||||
config = {'name': 'test', 'commands': [], 'platform': 'universal'}
|
||||
|
||||
@ -164,7 +196,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertTrue(response)
|
||||
self.assertEqual({}, config['commands'])
|
||||
|
||||
def test_check_config_bad_attributes(self):
|
||||
def test_config_bad_attributes(self):
|
||||
"""Check config with bad attributes."""
|
||||
config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
|
||||
|
||||
@ -172,7 +204,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertTrue(response)
|
||||
self.assertEqual({}, config['attributes'])
|
||||
|
||||
def test_check_config_bad_key(self):
|
||||
def test_config_bad_key(self):
|
||||
"""Check config with bad key."""
|
||||
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
|
||||
|
||||
@ -183,6 +215,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
def test_platform_setup(self):
|
||||
"""Test platform setup."""
|
||||
config = {'name': 'test', 'platform': 'universal'}
|
||||
bad_config = {'platform': 'universal'}
|
||||
entities = []
|
||||
|
||||
def add_devices(new_entities):
|
||||
@ -190,8 +223,10 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
for dev in new_entities:
|
||||
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('test', entities[0].name)
|
||||
|
||||
@ -263,6 +298,15 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
|
||||
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):
|
||||
"""Test media player state with only children."""
|
||||
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.update()
|
||||
|
||||
self.mock_mp_1._supported_media_commands = \
|
||||
universal.SUPPORT_VOLUME_SET
|
||||
self.mock_mp_1._supported_media_commands = universal.SUPPORT_VOLUME_SET
|
||||
self.mock_mp_1._state = STATE_PLAYING
|
||||
self.mock_mp_1.update_ha_state()
|
||||
ump.update()
|
||||
@ -400,7 +443,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertEqual(check_flags, ump.supported_media_commands)
|
||||
|
||||
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
|
||||
universal.validate_config(config)
|
||||
|
||||
@ -413,13 +456,53 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
ump.update()
|
||||
|
||||
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):
|
||||
"""Test service call to command."""
|
||||
config = self.config_children_only
|
||||
config['commands'] = \
|
||||
{'turn_off': {'service': 'test.turn_off', 'data': {}}}
|
||||
config['commands'] = {'turn_off': {
|
||||
'service': 'test.turn_off', 'data': {}}}
|
||||
universal.validate_config(config)
|
||||
|
||||
service = mock_service(self.hass, 'test', 'turn_off')
|
||||
|
@ -44,10 +44,6 @@ class TestMQTT(unittest.TestCase):
|
||||
self.hass.pool.block_till_done()
|
||||
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):
|
||||
"""Test for setup failure if connection to broker is missing."""
|
||||
with mock.patch('homeassistant.components.mqtt.MQTT',
|
55
tests/components/mqtt/test_server.py
Normal file
55
tests/components/mqtt/test_server.py
Normal 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': {}}
|
||||
})
|
@ -3,8 +3,10 @@ import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from homeassistant import core
|
||||
import homeassistant.components.notify as notify
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
@ -13,12 +15,27 @@ class TestCommandLine(unittest.TestCase):
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""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
|
||||
"""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': 'bad_platform',
|
||||
}
|
||||
}))
|
||||
self.assertFalse(notify.setup(self.hass, {
|
||||
'notify': {
|
||||
'name': 'test',
|
||||
'platform': 'command_line',
|
||||
}
|
||||
}))
|
||||
|
||||
def test_command_line_output(self):
|
||||
"""Test the command line output."""
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
@ -41,7 +58,7 @@ class TestCommandLine(unittest.TestCase):
|
||||
|
||||
@patch('homeassistant.components.notify.command_line._LOGGER.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, {
|
||||
'notify': {
|
||||
'name': 'test',
|
||||
|
@ -30,6 +30,12 @@ class TestNotifyDemo(unittest.TestCase):
|
||||
""""Stop down everything that was started."""
|
||||
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):
|
||||
"""Send a templated message."""
|
||||
self.hass.states.set('sensor.temperature', 10)
|
||||
|
56
tests/components/notify/test_file.py
Normal file
56
tests/components/notify/test_file.py
Normal 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))
|
@ -54,7 +54,7 @@ class TestTemplateSensor:
|
||||
self.hass.states.set('sensor.test_state', 'Works')
|
||||
self.hass.pool.block_till_done()
|
||||
state = self.hass.states.get('sensor.test_template_sensor')
|
||||
assert state.state == 'error'
|
||||
assert state.state == 'unknown'
|
||||
|
||||
def test_template_attribute_missing(self):
|
||||
"""Test missing attribute template."""
|
||||
@ -71,7 +71,7 @@ class TestTemplateSensor:
|
||||
})
|
||||
|
||||
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):
|
||||
"""Test invalid name."""
|
||||
|
@ -6,6 +6,7 @@ import unittest
|
||||
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
import homeassistant.components.switch as switch
|
||||
import homeassistant.components.switch.command_line as command_line
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
@ -155,3 +156,21 @@ class TestCommandSwitch(unittest.TestCase):
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
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
Loading…
x
Reference in New Issue
Block a user