mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 21:55:10 +02:00
11
.coveragerc
11
.coveragerc
@@ -26,11 +26,13 @@ omit =
|
|||||||
homeassistant/components/modbus.py
|
homeassistant/components/modbus.py
|
||||||
homeassistant/components/*/modbus.py
|
homeassistant/components/*/modbus.py
|
||||||
|
|
||||||
|
homeassistant/components/tellstick.py
|
||||||
homeassistant/components/*/tellstick.py
|
homeassistant/components/*/tellstick.py
|
||||||
|
|
||||||
homeassistant/components/tellduslive.py
|
homeassistant/components/tellduslive.py
|
||||||
homeassistant/components/*/tellduslive.py
|
homeassistant/components/*/tellduslive.py
|
||||||
|
|
||||||
|
homeassistant/components/vera.py
|
||||||
homeassistant/components/*/vera.py
|
homeassistant/components/*/vera.py
|
||||||
|
|
||||||
homeassistant/components/ecobee.py
|
homeassistant/components/ecobee.py
|
||||||
@@ -57,9 +59,6 @@ omit =
|
|||||||
homeassistant/components/nest.py
|
homeassistant/components/nest.py
|
||||||
homeassistant/components/*/nest.py
|
homeassistant/components/*/nest.py
|
||||||
|
|
||||||
homeassistant/components/rfxtrx.py
|
|
||||||
homeassistant/components/*/rfxtrx.py
|
|
||||||
|
|
||||||
homeassistant/components/rpi_gpio.py
|
homeassistant/components/rpi_gpio.py
|
||||||
homeassistant/components/*/rpi_gpio.py
|
homeassistant/components/*/rpi_gpio.py
|
||||||
|
|
||||||
@@ -108,9 +107,12 @@ omit =
|
|||||||
homeassistant/components/media_player/snapcast.py
|
homeassistant/components/media_player/snapcast.py
|
||||||
homeassistant/components/media_player/sonos.py
|
homeassistant/components/media_player/sonos.py
|
||||||
homeassistant/components/media_player/squeezebox.py
|
homeassistant/components/media_player/squeezebox.py
|
||||||
|
homeassistant/components/media_player/yamaha.py
|
||||||
homeassistant/components/notify/free_mobile.py
|
homeassistant/components/notify/free_mobile.py
|
||||||
homeassistant/components/notify/googlevoice.py
|
homeassistant/components/notify/googlevoice.py
|
||||||
|
homeassistant/components/notify/gntp.py
|
||||||
homeassistant/components/notify/instapush.py
|
homeassistant/components/notify/instapush.py
|
||||||
|
homeassistant/components/notify/message_bird.py
|
||||||
homeassistant/components/notify/nma.py
|
homeassistant/components/notify/nma.py
|
||||||
homeassistant/components/notify/pushbullet.py
|
homeassistant/components/notify/pushbullet.py
|
||||||
homeassistant/components/notify/pushetta.py
|
homeassistant/components/notify/pushetta.py
|
||||||
@@ -149,6 +151,7 @@ omit =
|
|||||||
homeassistant/components/sensor/torque.py
|
homeassistant/components/sensor/torque.py
|
||||||
homeassistant/components/sensor/transmission.py
|
homeassistant/components/sensor/transmission.py
|
||||||
homeassistant/components/sensor/twitch.py
|
homeassistant/components/sensor/twitch.py
|
||||||
|
homeassistant/components/sensor/uber.py
|
||||||
homeassistant/components/sensor/worldclock.py
|
homeassistant/components/sensor/worldclock.py
|
||||||
homeassistant/components/switch/arest.py
|
homeassistant/components/switch/arest.py
|
||||||
homeassistant/components/switch/dlink.py
|
homeassistant/components/switch/dlink.py
|
||||||
@@ -156,8 +159,10 @@ omit =
|
|||||||
homeassistant/components/switch/hikvisioncam.py
|
homeassistant/components/switch/hikvisioncam.py
|
||||||
homeassistant/components/switch/mystrom.py
|
homeassistant/components/switch/mystrom.py
|
||||||
homeassistant/components/switch/orvibo.py
|
homeassistant/components/switch/orvibo.py
|
||||||
|
homeassistant/components/switch/pulseaudio_loopback.py
|
||||||
homeassistant/components/switch/rest.py
|
homeassistant/components/switch/rest.py
|
||||||
homeassistant/components/switch/transmission.py
|
homeassistant/components/switch/transmission.py
|
||||||
|
homeassistant/components/switch/wake_on_lan.py
|
||||||
homeassistant/components/thermostat/heatmiser.py
|
homeassistant/components/thermostat/heatmiser.py
|
||||||
homeassistant/components/thermostat/homematic.py
|
homeassistant/components/thermostat/homematic.py
|
||||||
homeassistant/components/thermostat/proliphix.py
|
homeassistant/components/thermostat/proliphix.py
|
||||||
|
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -10,16 +10,15 @@
|
|||||||
|
|
||||||
**Checklist:**
|
**Checklist:**
|
||||||
|
|
||||||
- [ ] Local tests with `tox` run successfully.
|
If code communicates with devices:
|
||||||
- [ ] TravisCI does not fail. **Your PR cannot be merged unless CI is green!**
|
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
|
||||||
- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR.
|
|
||||||
- [ ] Commits have been [squashed][squash].
|
|
||||||
- If code communicates with devices:
|
|
||||||
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
|
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
|
||||||
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
|
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
|
||||||
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
|
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
|
||||||
- [ ] New files were added to `.coveragerc`.
|
- [ ] New files were added to `.coveragerc`.
|
||||||
- If the code does not interact with devices:
|
|
||||||
|
If the code does not interact with devices:
|
||||||
|
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
|
||||||
- [ ] Tests have been added to verify that the new code works.
|
- [ ] Tests have been added to verify that the new code works.
|
||||||
|
|
||||||
[fork]: http://stackoverflow.com/a/7244456
|
[fork]: http://stackoverflow.com/a/7244456
|
||||||
|
@@ -83,11 +83,13 @@ def setup(hass, config):
|
|||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
'GET', URL_API_COMPONENTS, _handle_get_api_components)
|
'GET', URL_API_COMPONENTS, _handle_get_api_components)
|
||||||
|
|
||||||
|
# /error_log
|
||||||
hass.http.register_path('GET', URL_API_ERROR_LOG,
|
hass.http.register_path('GET', URL_API_ERROR_LOG,
|
||||||
_handle_get_api_error_log)
|
_handle_get_api_error_log)
|
||||||
|
|
||||||
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
|
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
|
||||||
|
|
||||||
|
# /template
|
||||||
hass.http.register_path('POST', URL_API_TEMPLATE,
|
hass.http.register_path('POST', URL_API_TEMPLATE,
|
||||||
_handle_post_api_template)
|
_handle_post_api_template)
|
||||||
|
|
||||||
|
@@ -9,8 +9,9 @@ import logging
|
|||||||
from homeassistant.bootstrap import prepare_setup_platform
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
from homeassistant.components import logbook
|
from homeassistant.components import logbook
|
||||||
from homeassistant.helpers.service import call_from_config
|
from homeassistant.helpers import extract_domain_configs
|
||||||
from homeassistant.helpers.service import validate_service_call
|
from homeassistant.helpers.service import (call_from_config,
|
||||||
|
validate_service_call)
|
||||||
|
|
||||||
|
|
||||||
DOMAIN = 'automation'
|
DOMAIN = 'automation'
|
||||||
@@ -35,30 +36,17 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Setup the automation."""
|
"""Setup the automation."""
|
||||||
config_key = DOMAIN
|
for config_key in extract_domain_configs(config, DOMAIN):
|
||||||
found = 1
|
conf = config[config_key]
|
||||||
|
|
||||||
while config_key in config:
|
if not isinstance(conf, list):
|
||||||
# Check for one block syntax
|
conf = [conf]
|
||||||
if isinstance(config[config_key], dict):
|
|
||||||
config_block = _migrate_old_config(config[config_key])
|
for list_no, config_block in enumerate(conf):
|
||||||
name = config_block.get(CONF_ALIAS, config_key)
|
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
|
||||||
|
list_no))
|
||||||
_setup_automation(hass, config_block, name, config)
|
_setup_automation(hass, config_block, name, config)
|
||||||
|
|
||||||
# Check for multiple block syntax
|
|
||||||
elif isinstance(config[config_key], list):
|
|
||||||
for list_no, config_block in enumerate(config[config_key]):
|
|
||||||
name = config_block.get(CONF_ALIAS,
|
|
||||||
"{}, {}".format(config_key, list_no))
|
|
||||||
_setup_automation(hass, config_block, name, config)
|
|
||||||
|
|
||||||
# Any scalar value is incorrect
|
|
||||||
else:
|
|
||||||
_LOGGER.error('Error in config in section %s.', config_key)
|
|
||||||
|
|
||||||
found += 1
|
|
||||||
config_key = "{} {}".format(DOMAIN, found)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -97,40 +85,6 @@ def _get_action(hass, config, name):
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
def _migrate_old_config(config):
|
|
||||||
"""Migrate old configuration to new."""
|
|
||||||
if CONF_PLATFORM not in config:
|
|
||||||
return config
|
|
||||||
|
|
||||||
_LOGGER.warning(
|
|
||||||
'You are using an old configuration format. Please upgrade: '
|
|
||||||
'https://home-assistant.io/components/automation/')
|
|
||||||
|
|
||||||
new_conf = {
|
|
||||||
CONF_TRIGGER: dict(config),
|
|
||||||
CONF_CONDITION: config.get('if', []),
|
|
||||||
CONF_ACTION: dict(config),
|
|
||||||
}
|
|
||||||
|
|
||||||
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
|
|
||||||
('trigger', 'mqtt_payload', 'payload'),
|
|
||||||
('trigger', 'state_entity_id', 'entity_id'),
|
|
||||||
('trigger', 'state_before', 'before'),
|
|
||||||
('trigger', 'state_after', 'after'),
|
|
||||||
('trigger', 'state_to', 'to'),
|
|
||||||
('trigger', 'state_from', 'from'),
|
|
||||||
('trigger', 'state_hours', 'hours'),
|
|
||||||
('trigger', 'state_minutes', 'minutes'),
|
|
||||||
('trigger', 'state_seconds', 'seconds'),
|
|
||||||
('action', 'execute_service', 'service'),
|
|
||||||
('action', 'service_entity_id', 'entity_id'),
|
|
||||||
('action', 'service_data', 'data')):
|
|
||||||
if key in new_conf[cat]:
|
|
||||||
new_conf[cat][new_key] = new_conf[cat].pop(key)
|
|
||||||
|
|
||||||
return new_conf
|
|
||||||
|
|
||||||
|
|
||||||
def _process_if(hass, config, p_config, action):
|
def _process_if(hass, config, p_config, action):
|
||||||
"""Process if checks."""
|
"""Process if checks."""
|
||||||
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
||||||
|
@@ -55,8 +55,13 @@ def _check_template(hass, value_template):
|
|||||||
"""Check if result of template is true."""
|
"""Check if result of template is true."""
|
||||||
try:
|
try:
|
||||||
value = template.render(hass, value_template, {})
|
value = template.render(hass, value_template, {})
|
||||||
except TemplateError:
|
except TemplateError as ex:
|
||||||
_LOGGER.exception('Error parsing template')
|
if ex.args and ex.args[0].startswith(
|
||||||
|
"UndefinedError: 'None' has no attribute"):
|
||||||
|
# Common during HA startup - so just a warning
|
||||||
|
_LOGGER.warning(ex)
|
||||||
|
else:
|
||||||
|
_LOGGER.error(ex)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return value.lower() == 'true'
|
return value.lower() == 'true'
|
||||||
|
@@ -9,7 +9,8 @@ import logging
|
|||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import (STATE_ON, STATE_OFF)
|
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||||
from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink)
|
from homeassistant.components import (
|
||||||
|
bloomsky, mysensors, zwave, vera, wemo, wink)
|
||||||
|
|
||||||
DOMAIN = 'binary_sensor'
|
DOMAIN = 'binary_sensor'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@@ -37,6 +38,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
|
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
|
||||||
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
|
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
|
||||||
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
|
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
|
||||||
|
vera.DISCOVER_BINARY_SENSORS: 'vera',
|
||||||
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
|
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
|
||||||
wink.DISCOVER_BINARY_SENSORS: 'wink'
|
wink.DISCOVER_BINARY_SENSORS: 'wink'
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@ https://home-assistant.io/components/binary_sensor.rest/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||||
|
SENSOR_CLASSES)
|
||||||
from homeassistant.components.sensor.rest import RestData
|
from homeassistant.components.sensor.rest import RestData
|
||||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
@@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET'
|
|||||||
|
|
||||||
# pylint: disable=unused-variable
|
# pylint: disable=unused-variable
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup REST binary sensors."""
|
"""Setup the REST binary sensor."""
|
||||||
resource = config.get('resource', None)
|
resource = config.get('resource', None)
|
||||||
method = config.get('method', DEFAULT_METHOD)
|
method = config.get('method', DEFAULT_METHOD)
|
||||||
payload = config.get('payload', None)
|
payload = config.get('payload', None)
|
||||||
verify_ssl = config.get('verify_ssl', True)
|
verify_ssl = config.get('verify_ssl', True)
|
||||||
|
|
||||||
|
sensor_class = config.get('sensor_class')
|
||||||
|
if sensor_class not in SENSOR_CLASSES:
|
||||||
|
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
|
||||||
|
sensor_class = None
|
||||||
|
|
||||||
rest = RestData(method, resource, payload, verify_ssl)
|
rest = RestData(method, resource, payload, verify_ssl)
|
||||||
rest.update()
|
rest.update()
|
||||||
|
|
||||||
@@ -33,19 +39,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
add_devices([RestBinarySensor(
|
add_devices([RestBinarySensor(
|
||||||
hass, rest, config.get('name', DEFAULT_NAME),
|
hass,
|
||||||
|
rest,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
sensor_class,
|
||||||
config.get(CONF_VALUE_TEMPLATE))])
|
config.get(CONF_VALUE_TEMPLATE))])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
class RestBinarySensor(BinarySensorDevice):
|
class RestBinarySensor(BinarySensorDevice):
|
||||||
"""A REST binary sensor."""
|
"""Representation of a REST binary sensor."""
|
||||||
|
|
||||||
def __init__(self, hass, rest, name, value_template):
|
def __init__(self, hass, rest, name, sensor_class, value_template):
|
||||||
"""Initialize a REST binary sensor."""
|
"""Initialize a REST binary sensor."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self.rest = rest
|
self.rest = rest
|
||||||
self._name = name
|
self._name = name
|
||||||
|
self._sensor_class = sensor_class
|
||||||
self._state = False
|
self._state = False
|
||||||
self._value_template = value_template
|
self._value_template = value_template
|
||||||
self.update()
|
self.update()
|
||||||
@@ -55,6 +65,11 @@ class RestBinarySensor(BinarySensorDevice):
|
|||||||
"""Return the name of the binary sensor."""
|
"""Return the name of the binary sensor."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_class(self):
|
||||||
|
"""Return the class of this sensor."""
|
||||||
|
return self._sensor_class
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
|
@@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.template/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||||
DOMAIN,
|
ENTITY_ID_FORMAT,
|
||||||
SENSOR_CLASSES)
|
SENSOR_CLASSES)
|
||||||
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
|
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
|
||||||
from homeassistant.core import EVENT_STATE_CHANGED
|
from homeassistant.core import EVENT_STATE_CHANGED
|
||||||
@@ -16,7 +16,6 @@ from homeassistant.helpers.entity import generate_entity_id
|
|||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|
||||||
CONF_SENSORS = 'sensors'
|
CONF_SENSORS = 'sensors'
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -76,34 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
def __init__(self, hass, device, friendly_name, sensor_class,
|
def __init__(self, hass, device, friendly_name, sensor_class,
|
||||||
value_template):
|
value_template):
|
||||||
"""Initialize the Template binary sensor."""
|
"""Initialize the Template binary sensor."""
|
||||||
self._hass = hass
|
self.hass = hass
|
||||||
self._device = device
|
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||||
|
hass=hass)
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._sensor_class = sensor_class
|
self._sensor_class = sensor_class
|
||||||
self._template = value_template
|
self._template = value_template
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
self.entity_id = generate_entity_id(
|
self.update()
|
||||||
ENTITY_ID_FORMAT, device,
|
|
||||||
hass=hass)
|
|
||||||
|
|
||||||
_LOGGER.info('Started template sensor %s', device)
|
def template_bsensor_event_listener(event):
|
||||||
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
"""Called when the target device changes state."""
|
||||||
|
self.update_ha_state(True)
|
||||||
|
|
||||||
def _event_listener(self, event):
|
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||||
if not hasattr(self, 'hass'):
|
template_bsensor_event_listener)
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
"""Return true if sensor is on."""
|
"""Return true if sensor is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_class(self):
|
||||||
|
"""Return the sensor class of the sensor."""
|
||||||
|
return self._sensor_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No polling needed."""
|
||||||
|
return False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data and update the state."""
|
"""Get the latest data and update the state."""
|
||||||
try:
|
try:
|
||||||
value = template.render(self._hass, self._template)
|
self._state = template.render(self.hass,
|
||||||
|
self._template).lower() == 'true'
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
if ex.args and ex.args[0].startswith(
|
if ex.args and ex.args[0].startswith(
|
||||||
"UndefinedError: 'None' has no attribute"):
|
"UndefinedError: 'None' has no attribute"):
|
||||||
@@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
_LOGGER.warning(ex)
|
_LOGGER.warning(ex)
|
||||||
return
|
return
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
value = 'false'
|
self._state = False
|
||||||
self._state = value.lower() == 'true'
|
|
||||||
|
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(
|
_LOGGER.info(
|
||||||
'Subscription update for %s',
|
'Subscription update for %s',
|
||||||
_device)
|
_device)
|
||||||
|
if not hasattr(self, 'hass'):
|
||||||
|
self.update()
|
||||||
|
return
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
|||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
# These are the available sensors mapped to binary_sensor class
|
# These are the available sensors mapped to binary_sensor class
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
@@ -77,6 +77,11 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
|||||||
"""Return the name of the sensor if any."""
|
"""Return the name of the sensor if any."""
|
||||||
return self.wink.name()
|
return self.wink.name()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update state of the sensor."""
|
"""Update state of the sensor."""
|
||||||
self.wink.update_state()
|
self.wink.update_state()
|
||||||
|
@@ -12,7 +12,7 @@ from homeassistant.helpers.event import track_utc_time_change
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyicloud==0.7.2']
|
REQUIREMENTS = ['pyicloud==0.8.1']
|
||||||
|
|
||||||
CONF_INTERVAL = 'interval'
|
CONF_INTERVAL = 'interval'
|
||||||
DEFAULT_INTERVAL = 8
|
DEFAULT_INTERVAL = 8
|
||||||
|
@@ -89,4 +89,9 @@ class NetgearDeviceScanner(object):
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
_LOGGER.info("Scanning")
|
_LOGGER.info("Scanning")
|
||||||
|
|
||||||
self.last_results = self._api.get_attached_devices() or []
|
results = self._api.get_attached_devices()
|
||||||
|
|
||||||
|
if results is None:
|
||||||
|
_LOGGER.warning('Error scanning devices')
|
||||||
|
|
||||||
|
self.last_results = results or []
|
||||||
|
@@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# interval in minutes to exclude devices from a scan while they are home
|
# interval in minutes to exclude devices from a scan while they are home
|
||||||
CONF_HOME_INTERVAL = "home_interval"
|
CONF_HOME_INTERVAL = "home_interval"
|
||||||
|
|
||||||
REQUIREMENTS = ['python-nmap==0.4.3']
|
REQUIREMENTS = ['python-nmap==0.6.0']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
|
@@ -99,14 +99,11 @@ def setup_scanner(hass, config, see):
|
|||||||
_LOGGER.info("Added beacon %s", location)
|
_LOGGER.info("Added beacon %s", location)
|
||||||
else:
|
else:
|
||||||
# Normal region
|
# Normal region
|
||||||
if not zone.attributes.get('passive'):
|
|
||||||
kwargs['location_name'] = location
|
|
||||||
|
|
||||||
regions = REGIONS_ENTERED[dev_id]
|
regions = REGIONS_ENTERED[dev_id]
|
||||||
if location not in regions:
|
if location not in regions:
|
||||||
regions.append(location)
|
regions.append(location)
|
||||||
_LOGGER.info("Enter region %s", location)
|
_LOGGER.info("Enter region %s", location)
|
||||||
_set_gps_from_zone(kwargs, zone)
|
_set_gps_from_zone(kwargs, location, zone)
|
||||||
|
|
||||||
see(**kwargs)
|
see(**kwargs)
|
||||||
see_beacons(dev_id, kwargs)
|
see_beacons(dev_id, kwargs)
|
||||||
@@ -121,9 +118,7 @@ def setup_scanner(hass, config, see):
|
|||||||
if new_region:
|
if new_region:
|
||||||
# Exit to previous region
|
# Exit to previous region
|
||||||
zone = hass.states.get("zone.{}".format(new_region))
|
zone = hass.states.get("zone.{}".format(new_region))
|
||||||
if not zone.attributes.get('passive'):
|
_set_gps_from_zone(kwargs, new_region, zone)
|
||||||
kwargs['location_name'] = new_region
|
|
||||||
_set_gps_from_zone(kwargs, zone)
|
|
||||||
_LOGGER.info("Exit to %s", new_region)
|
_LOGGER.info("Exit to %s", new_region)
|
||||||
see(**kwargs)
|
see(**kwargs)
|
||||||
see_beacons(dev_id, kwargs)
|
see_beacons(dev_id, kwargs)
|
||||||
@@ -184,11 +179,12 @@ def _parse_see_args(topic, data):
|
|||||||
return dev_id, kwargs
|
return dev_id, kwargs
|
||||||
|
|
||||||
|
|
||||||
def _set_gps_from_zone(kwargs, zone):
|
def _set_gps_from_zone(kwargs, location, zone):
|
||||||
"""Set the see parameters from the zone parameters."""
|
"""Set the see parameters from the zone parameters."""
|
||||||
if zone is not None:
|
if zone is not None:
|
||||||
kwargs['gps'] = (
|
kwargs['gps'] = (
|
||||||
zone.attributes['latitude'],
|
zone.attributes['latitude'],
|
||||||
zone.attributes['longitude'])
|
zone.attributes['longitude'])
|
||||||
kwargs['gps_accuracy'] = zone.attributes['radius']
|
kwargs['gps_accuracy'] = zone.attributes['radius']
|
||||||
|
kwargs['location_name'] = location
|
||||||
return kwargs
|
return kwargs
|
||||||
|
@@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
EVENT_PLATFORM_DISCOVERED)
|
EVENT_PLATFORM_DISCOVERED)
|
||||||
|
|
||||||
DOMAIN = "discovery"
|
DOMAIN = "discovery"
|
||||||
REQUIREMENTS = ['netdisco==0.5.4']
|
REQUIREMENTS = ['netdisco==0.5.5']
|
||||||
|
|
||||||
SCAN_INTERVAL = 300 # seconds
|
SCAN_INTERVAL = 300 # seconds
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast'
|
|||||||
SERVICE_NETGEAR = 'netgear_router'
|
SERVICE_NETGEAR = 'netgear_router'
|
||||||
SERVICE_SONOS = 'sonos'
|
SERVICE_SONOS = 'sonos'
|
||||||
SERVICE_PLEX = 'plex_mediaserver'
|
SERVICE_PLEX = 'plex_mediaserver'
|
||||||
|
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
|
||||||
|
|
||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
SERVICE_WEMO: "wemo",
|
SERVICE_WEMO: "wemo",
|
||||||
@@ -33,6 +34,7 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_NETGEAR: 'device_tracker',
|
SERVICE_NETGEAR: 'device_tracker',
|
||||||
SERVICE_SONOS: 'media_player',
|
SERVICE_SONOS: 'media_player',
|
||||||
SERVICE_PLEX: 'media_player',
|
SERVICE_PLEX: 'media_player',
|
||||||
|
SERVICE_SQUEEZEBOX: 'media_player',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
||||||
VERSION = "e85dc66e1a0730e44f79ed11501cd79a"
|
VERSION = "df49e6b7c930eb39b42ff1909712e95e"
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
||||||
VERSION = "30bcc0eacc13a2317000824741dc9ac0"
|
VERSION = "49974cb3bb443751f7548e4e3b353304"
|
||||||
|
File diff suppressed because one or more lines are too long
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.components.garage_door import GarageDoorDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@@ -57,6 +57,11 @@ class WinkGarageDoorDevice(GarageDoorDevice):
|
|||||||
"""Return true if door is closed."""
|
"""Return true if door is closed."""
|
||||||
return self.wink.state() == 0
|
return self.wink.state() == 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
def close_door(self):
|
def close_door(self):
|
||||||
"""Close the door."""
|
"""Close the door."""
|
||||||
self.wink.set_state(0)
|
self.wink.set_state(0)
|
||||||
|
@@ -74,7 +74,8 @@ def setup(hass, config):
|
|||||||
hass.bus.listen_once(
|
hass.bus.listen_once(
|
||||||
ha.EVENT_HOMEASSISTANT_START,
|
ha.EVENT_HOMEASSISTANT_START,
|
||||||
lambda event:
|
lambda event:
|
||||||
threading.Thread(target=server.start, daemon=True).start())
|
threading.Thread(target=server.start, daemon=True,
|
||||||
|
name='HTTP-server').start())
|
||||||
|
|
||||||
hass.http = server
|
hass.http = server
|
||||||
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
|
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
|
||||||
@@ -236,9 +237,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
# Did we find a handler for the incoming request?
|
# Did we find a handler for the incoming request?
|
||||||
if handle_request_method:
|
if handle_request_method:
|
||||||
# For some calls we need a valid password
|
# For some calls we need a valid password
|
||||||
|
msg = "API password missing or incorrect."
|
||||||
if require_auth and not self.authenticated:
|
if require_auth and not self.authenticated:
|
||||||
self.write_json_message(
|
self.write_json_message(msg, HTTP_UNAUTHORIZED)
|
||||||
"API password missing or incorrect.", HTTP_UNAUTHORIZED)
|
_LOGGER.warning(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
handle_request_method(self, path_match, data)
|
handle_request_method(self, path_match, data)
|
||||||
@@ -277,8 +279,11 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
def write_json(self, data=None, status_code=HTTP_OK, location=None):
|
def write_json(self, data=None, status_code=HTTP_OK, location=None):
|
||||||
"""Helper method to return JSON to the caller."""
|
"""Helper method to return JSON to the caller."""
|
||||||
|
json_data = json.dumps(data, indent=4, sort_keys=True,
|
||||||
|
cls=rem.JSONEncoder).encode('UTF-8')
|
||||||
self.send_response(status_code)
|
self.send_response(status_code)
|
||||||
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
|
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||||
|
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data)))
|
||||||
|
|
||||||
if location:
|
if location:
|
||||||
self.send_header('Location', location)
|
self.send_header('Location', location)
|
||||||
@@ -288,20 +293,20 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
self.wfile.write(
|
self.wfile.write(json_data)
|
||||||
json.dumps(data, indent=4, sort_keys=True,
|
|
||||||
cls=rem.JSONEncoder).encode("UTF-8"))
|
|
||||||
|
|
||||||
def write_text(self, message, status_code=HTTP_OK):
|
def write_text(self, message, status_code=HTTP_OK):
|
||||||
"""Helper method to return a text message to the caller."""
|
"""Helper method to return a text message to the caller."""
|
||||||
|
msg_data = message.encode('UTF-8')
|
||||||
self.send_response(status_code)
|
self.send_response(status_code)
|
||||||
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
|
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
|
||||||
|
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(msg_data)))
|
||||||
|
|
||||||
self.set_session_cookie_header()
|
self.set_session_cookie_header()
|
||||||
|
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
self.wfile.write(message.encode("UTF-8"))
|
self.wfile.write(msg_data)
|
||||||
|
|
||||||
def write_file(self, path, cache_headers=True):
|
def write_file(self, path, cache_headers=True):
|
||||||
"""Return a file to the user."""
|
"""Return a file to the user."""
|
||||||
|
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
|
import csv
|
||||||
|
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
group, discovery, wemo, wink, isy994, zwave, insteon_hub, mysensors)
|
group, discovery, wemo, wink, isy994,
|
||||||
|
zwave, insteon_hub, mysensors, tellstick, vera)
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||||
@@ -64,6 +65,8 @@ DISCOVERY_PLATFORMS = {
|
|||||||
discovery.SERVICE_HUE: 'hue',
|
discovery.SERVICE_HUE: 'hue',
|
||||||
zwave.DISCOVER_LIGHTS: 'zwave',
|
zwave.DISCOVER_LIGHTS: 'zwave',
|
||||||
mysensors.DISCOVER_LIGHTS: 'mysensors',
|
mysensors.DISCOVER_LIGHTS: 'mysensors',
|
||||||
|
tellstick.DISCOVER_LIGHTS: 'tellstick',
|
||||||
|
vera.DISCOVER_LIGHTS: 'vera',
|
||||||
}
|
}
|
||||||
|
|
||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
@@ -224,7 +227,7 @@ def setup(hass, config):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if ATTR_COLOR_TEMP in dat:
|
if ATTR_COLOR_TEMP in dat:
|
||||||
# color_temp should be an int of mirads value
|
# color_temp should be an int of mireds value
|
||||||
colortemp = dat.get(ATTR_COLOR_TEMP)
|
colortemp = dat.get(ATTR_COLOR_TEMP)
|
||||||
|
|
||||||
# Without this check, a ctcolor with value '99' would work
|
# Without this check, a ctcolor with value '99' would work
|
||||||
@@ -295,7 +298,7 @@ class Light(ToggleEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def color_temp(self):
|
def color_temp(self):
|
||||||
"""Return the CT color value in mirads."""
|
"""Return the CT color value in mireds."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -53,6 +53,8 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
|
|||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
"""Setup the Hue lights."""
|
"""Setup the Hue lights."""
|
||||||
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
|
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
|
||||||
|
allow_unreachable = config.get('allow_unreachable', False)
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
host = urlparse(discovery_info[1]).hostname
|
host = urlparse(discovery_info[1]).hostname
|
||||||
else:
|
else:
|
||||||
@@ -69,10 +71,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
if host in _CONFIGURING:
|
if host in _CONFIGURING:
|
||||||
return
|
return
|
||||||
|
|
||||||
setup_bridge(host, hass, add_devices_callback, filename)
|
setup_bridge(host, hass, add_devices_callback, filename, allow_unreachable)
|
||||||
|
|
||||||
|
|
||||||
def setup_bridge(host, hass, add_devices_callback, filename):
|
def setup_bridge(host, hass, add_devices_callback, filename,
|
||||||
|
allow_unreachable):
|
||||||
"""Setup a phue bridge based on host parameter."""
|
"""Setup a phue bridge based on host parameter."""
|
||||||
import phue
|
import phue
|
||||||
|
|
||||||
@@ -88,7 +91,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
|||||||
except phue.PhueRegistrationException:
|
except phue.PhueRegistrationException:
|
||||||
_LOGGER.warning("Connected to Hue at %s but not registered.", host)
|
_LOGGER.warning("Connected to Hue at %s but not registered.", host)
|
||||||
|
|
||||||
request_configuration(host, hass, add_devices_callback, filename)
|
request_configuration(host, hass, add_devices_callback, filename,
|
||||||
|
allow_unreachable)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -130,7 +134,7 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
|||||||
if light_id not in lights:
|
if light_id not in lights:
|
||||||
lights[light_id] = HueLight(int(light_id), info,
|
lights[light_id] = HueLight(int(light_id), info,
|
||||||
bridge, update_lights,
|
bridge, update_lights,
|
||||||
bridge_type=bridge_type)
|
bridge_type, allow_unreachable)
|
||||||
new_lights.append(lights[light_id])
|
new_lights.append(lights[light_id])
|
||||||
else:
|
else:
|
||||||
lights[light_id].info = info
|
lights[light_id].info = info
|
||||||
@@ -141,7 +145,8 @@ def setup_bridge(host, hass, add_devices_callback, filename):
|
|||||||
update_lights()
|
update_lights()
|
||||||
|
|
||||||
|
|
||||||
def request_configuration(host, hass, add_devices_callback, filename):
|
def request_configuration(host, hass, add_devices_callback, filename,
|
||||||
|
allow_unreachable):
|
||||||
"""Request configuration steps from the user."""
|
"""Request configuration steps from the user."""
|
||||||
configurator = get_component('configurator')
|
configurator = get_component('configurator')
|
||||||
|
|
||||||
@@ -155,7 +160,8 @@ def request_configuration(host, hass, add_devices_callback, filename):
|
|||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def hue_configuration_callback(data):
|
def hue_configuration_callback(data):
|
||||||
"""The actions to do when our configuration callback is called."""
|
"""The actions to do when our configuration callback is called."""
|
||||||
setup_bridge(host, hass, add_devices_callback, filename)
|
setup_bridge(host, hass, add_devices_callback, filename,
|
||||||
|
allow_unreachable)
|
||||||
|
|
||||||
_CONFIGURING[host] = configurator.request_config(
|
_CONFIGURING[host] = configurator.request_config(
|
||||||
hass, "Philips Hue", hue_configuration_callback,
|
hass, "Philips Hue", hue_configuration_callback,
|
||||||
@@ -171,7 +177,7 @@ class HueLight(Light):
|
|||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, light_id, info, bridge, update_lights,
|
def __init__(self, light_id, info, bridge, update_lights,
|
||||||
bridge_type='hue'):
|
bridge_type, allow_unreachable):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.light_id = light_id
|
self.light_id = light_id
|
||||||
self.info = info
|
self.info = info
|
||||||
@@ -179,6 +185,8 @@ class HueLight(Light):
|
|||||||
self.update_lights = update_lights
|
self.update_lights = update_lights
|
||||||
self.bridge_type = bridge_type
|
self.bridge_type = bridge_type
|
||||||
|
|
||||||
|
self.allow_unreachable = allow_unreachable
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the ID of this Hue light."""
|
"""Return the ID of this Hue light."""
|
||||||
@@ -209,7 +217,11 @@ class HueLight(Light):
|
|||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
self.update_lights()
|
self.update_lights()
|
||||||
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):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the specified or all lights on."""
|
"""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
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.tellstick/
|
https://home-assistant.io/components/light.tellstick/
|
||||||
"""
|
"""
|
||||||
|
from homeassistant.components import tellstick
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.components.tellstick import (DEFAULT_SIGNAL_REPETITIONS,
|
||||||
|
ATTR_DISCOVER_DEVICES,
|
||||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
ATTR_DISCOVER_CONFIG)
|
||||||
SIGNAL_REPETITIONS = 1
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup Tellstick lights."""
|
"""Setup Tellstick lights."""
|
||||||
import tellcore.telldus as telldus
|
if (discovery_info is None or
|
||||||
from tellcore.library import DirectCallbackDispatcher
|
discovery_info[ATTR_DISCOVER_DEVICES] is None or
|
||||||
import tellcore.constants as tellcore_constants
|
tellstick.TELLCORE_REGISTRY is None):
|
||||||
|
return
|
||||||
|
|
||||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
signal_repetitions = discovery_info.get(ATTR_DISCOVER_CONFIG,
|
||||||
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
|
DEFAULT_SIGNAL_REPETITIONS)
|
||||||
|
|
||||||
switches_and_lights = core.devices()
|
add_devices(TellstickLight(
|
||||||
lights = []
|
tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
|
||||||
|
for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
|
||||||
for switch in switches_and_lights:
|
|
||||||
if switch.methods(tellcore_constants.TELLSTICK_DIM):
|
|
||||||
lights.append(TellstickLight(switch, signal_repetitions))
|
|
||||||
|
|
||||||
def _device_event_callback(id_, method, data, cid):
|
|
||||||
"""Called from the TelldusCore library to update one device."""
|
|
||||||
for light_device in lights:
|
|
||||||
if light_device.tellstick_device.id == id_:
|
|
||||||
# Execute the update in another thread
|
|
||||||
light_device.update_ha_state(True)
|
|
||||||
break
|
|
||||||
|
|
||||||
callback_id = core.register_device_event(_device_event_callback)
|
|
||||||
|
|
||||||
def unload_telldus_lib(event):
|
|
||||||
"""Un-register the callback bindings."""
|
|
||||||
if callback_id is not None:
|
|
||||||
core.unregister_callback(callback_id)
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
|
|
||||||
|
|
||||||
add_devices_callback(lights)
|
|
||||||
|
|
||||||
|
|
||||||
class TellstickLight(Light):
|
class TellstickLight(tellstick.TellstickDevice, Light):
|
||||||
"""Representation of a Tellstick light."""
|
"""Representation of a Tellstick light."""
|
||||||
|
|
||||||
def __init__(self, tellstick_device, signal_repetitions):
|
def __init__(self, tellstick_device, signal_repetitions):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
import tellcore.constants as tellcore_constants
|
self._brightness = 255
|
||||||
|
tellstick.TellstickDevice.__init__(self,
|
||||||
self.tellstick_device = tellstick_device
|
tellstick_device,
|
||||||
self.signal_repetitions = signal_repetitions
|
signal_repetitions)
|
||||||
self._brightness = 0
|
|
||||||
|
|
||||||
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
|
||||||
tellcore_constants.TELLSTICK_TURNOFF |
|
|
||||||
tellcore_constants.TELLSTICK_DIM |
|
|
||||||
tellcore_constants.TELLSTICK_UP |
|
|
||||||
tellcore_constants.TELLSTICK_DOWN)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the switch if any."""
|
|
||||||
return self.tellstick_device.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if switch is on."""
|
"""Return true if switch is on."""
|
||||||
return self._brightness > 0
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def set_tellstick_state(self, last_command_sent, last_data_sent):
|
||||||
"""Turn the switch off."""
|
"""Update the internal representation of the switch."""
|
||||||
for _ in range(self.signal_repetitions):
|
from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_DIM
|
||||||
|
if last_command_sent == TELLSTICK_DIM:
|
||||||
|
if last_data_sent is not None:
|
||||||
|
self._brightness = int(last_data_sent)
|
||||||
|
self._state = self._brightness > 0
|
||||||
|
else:
|
||||||
|
self._state = last_command_sent == TELLSTICK_TURNON
|
||||||
|
|
||||||
|
def _send_tellstick_command(self, command, data):
|
||||||
|
"""Handle the turn_on / turn_off commands."""
|
||||||
|
from tellcore.constants import (TELLSTICK_TURNOFF, TELLSTICK_DIM)
|
||||||
|
if command == TELLSTICK_TURNOFF:
|
||||||
self.tellstick_device.turn_off()
|
self.tellstick_device.turn_off()
|
||||||
self._brightness = 0
|
elif command == TELLSTICK_DIM:
|
||||||
self.update_ha_state()
|
self.tellstick_device.dim(self._brightness)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Command not implemented: {}".format(command))
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
|
from tellcore.constants import TELLSTICK_DIM
|
||||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
|
if brightness is not None:
|
||||||
if brightness is None:
|
|
||||||
self._brightness = 255
|
|
||||||
else:
|
|
||||||
self._brightness = brightness
|
self._brightness = brightness
|
||||||
|
|
||||||
for _ in range(self.signal_repetitions):
|
self.call_tellstick(TELLSTICK_DIM, self._brightness)
|
||||||
self.tellstick_device.dim(self._brightness)
|
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
def update(self):
|
def turn_off(self, **kwargs):
|
||||||
"""Update state of the light."""
|
"""Turn the switch off."""
|
||||||
import tellcore.constants as tellcore_constants
|
from tellcore.constants import TELLSTICK_TURNOFF
|
||||||
|
self.call_tellstick(TELLSTICK_TURNOFF)
|
||||||
last_command = self.tellstick_device.last_sent_command(
|
|
||||||
self.last_sent_command_mask)
|
|
||||||
|
|
||||||
if last_command == tellcore_constants.TELLSTICK_TURNON:
|
|
||||||
self._brightness = 255
|
|
||||||
elif last_command == tellcore_constants.TELLSTICK_TURNOFF:
|
|
||||||
self._brightness = 0
|
|
||||||
elif (last_command == tellcore_constants.TELLSTICK_DIM or
|
|
||||||
last_command == tellcore_constants.TELLSTICK_UP or
|
|
||||||
last_command == tellcore_constants.TELLSTICK_DOWN):
|
|
||||||
last_sent_value = self.tellstick_device.last_sent_value()
|
|
||||||
if last_sent_value is not None:
|
|
||||||
self._brightness = last_sent_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assumed_state(self):
|
|
||||||
"""Tellstick devices are always assumed state."""
|
|
||||||
return True
|
|
||||||
|
@@ -6,15 +6,15 @@ https://home-assistant.io/components/light.vera/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
||||||
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
|
STATE_OFF, STATE_ON)
|
||||||
|
from homeassistant.components.vera import (
|
||||||
|
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.8']
|
DEPENDENCIES = ['vera']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -22,74 +22,17 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
"""Setup Vera lights."""
|
"""Setup Vera lights."""
|
||||||
import pyvera as veraApi
|
add_devices_callback(
|
||||||
|
VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light'])
|
||||||
base_url = config.get('vera_controller_url')
|
|
||||||
if not base_url:
|
|
||||||
_LOGGER.error(
|
|
||||||
"The required parameter 'vera_controller_url'"
|
|
||||||
" was not found in config"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
device_data = config.get('device_data', {})
|
|
||||||
|
|
||||||
vera_controller, created = veraApi.init_controller(base_url)
|
|
||||||
|
|
||||||
if created:
|
|
||||||
def stop_subscription(event):
|
|
||||||
"""Shutdown Vera subscriptions and subscription thread on exit."""
|
|
||||||
_LOGGER.info("Shutting down subscriptions.")
|
|
||||||
vera_controller.stop()
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
|
||||||
|
|
||||||
devices = []
|
|
||||||
try:
|
|
||||||
devices = vera_controller.get_devices([
|
|
||||||
'Switch',
|
|
||||||
'On/Off Switch',
|
|
||||||
'Dimmable Switch'])
|
|
||||||
except RequestException:
|
|
||||||
# There was a network related error connecting to the vera controller.
|
|
||||||
_LOGGER.exception("Error communicating with Vera API")
|
|
||||||
return False
|
|
||||||
|
|
||||||
lights = []
|
|
||||||
for device in devices:
|
|
||||||
extra_data = device_data.get(device.device_id, {})
|
|
||||||
exclude = extra_data.get('exclude', False)
|
|
||||||
|
|
||||||
if exclude is not True:
|
|
||||||
lights.append(VeraLight(device, vera_controller, extra_data))
|
|
||||||
|
|
||||||
add_devices_callback(lights)
|
|
||||||
|
|
||||||
|
|
||||||
class VeraLight(Light):
|
class VeraLight(VeraDevice, Light):
|
||||||
"""Representation of a Vera Light, including dimmable."""
|
"""Representation of a Vera Light, including dimmable."""
|
||||||
|
|
||||||
def __init__(self, vera_device, controller, extra_data=None):
|
def __init__(self, vera_device, controller):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.vera_device = vera_device
|
self._state = False
|
||||||
self.extra_data = extra_data
|
VeraDevice.__init__(self, vera_device, controller)
|
||||||
self.controller = controller
|
|
||||||
if self.extra_data and self.extra_data.get('name'):
|
|
||||||
self._name = self.extra_data.get('name')
|
|
||||||
else:
|
|
||||||
self._name = self.vera_device.name
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
|
||||||
self.controller.register(vera_device, self._update_callback)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def _update_callback(self, _device):
|
|
||||||
self.update_ha_state(True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the light."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
@@ -137,20 +80,13 @@ class VeraLight(Light):
|
|||||||
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||||
|
|
||||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||||
|
return attr
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self._state == STATE_ON
|
return self._state
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Called by the vera device callback to update state."""
|
"""Called by the vera device callback to update state."""
|
||||||
if self.vera_device.is_switched_on():
|
self._state = self.vera_device.is_switched_on()
|
||||||
self._state = STATE_ON
|
|
||||||
else:
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
@@ -8,8 +8,10 @@ import logging
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.color as color_util
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
Light, ATTR_BRIGHTNESS)
|
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
|
||||||
|
ATTR_XY_COLOR)
|
||||||
|
|
||||||
DEPENDENCIES = ['wemo']
|
DEPENDENCIES = ['wemo']
|
||||||
|
|
||||||
@@ -39,17 +41,14 @@ def setup_bridge(bridge, add_devices_callback):
|
|||||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||||
def update_lights():
|
def update_lights():
|
||||||
"""Update the WeMo led objects with latest info from the bridge."""
|
"""Update the WeMo led objects with latest info from the bridge."""
|
||||||
bridge.bridge_get_lights()
|
bridge.bridge_update()
|
||||||
|
|
||||||
new_lights = []
|
new_lights = []
|
||||||
|
|
||||||
for light_id, info in bridge.Lights.items():
|
for light_id, device in bridge.Lights.items():
|
||||||
if light_id not in lights:
|
if light_id not in lights:
|
||||||
lights[light_id] = WemoLight(bridge, light_id, info,
|
lights[light_id] = WemoLight(device, update_lights)
|
||||||
update_lights)
|
|
||||||
new_lights.append(lights[light_id])
|
new_lights.append(lights[light_id])
|
||||||
else:
|
|
||||||
lights[light_id].info = info
|
|
||||||
|
|
||||||
if new_lights:
|
if new_lights:
|
||||||
add_devices_callback(new_lights)
|
add_devices_callback(new_lights)
|
||||||
@@ -60,44 +59,73 @@ def setup_bridge(bridge, add_devices_callback):
|
|||||||
class WemoLight(Light):
|
class WemoLight(Light):
|
||||||
"""Representation of a WeMo light."""
|
"""Representation of a WeMo light."""
|
||||||
|
|
||||||
def __init__(self, bridge, light_id, info, update_lights):
|
def __init__(self, device, update_lights):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.bridge = bridge
|
self.light_id = device.name
|
||||||
self.light_id = light_id
|
self.device = device
|
||||||
self.info = info
|
|
||||||
self.update_lights = update_lights
|
self.update_lights = update_lights
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the ID of this light."""
|
"""Return the ID of this light."""
|
||||||
deviceid = self.bridge.light_get_id(self.info)
|
deviceid = self.device.uniqueID
|
||||||
return "{}.{}".format(self.__class__, deviceid)
|
return "{}.{}".format(self.__class__, deviceid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the light."""
|
"""Return the name of the light."""
|
||||||
return self.bridge.light_name(self.info)
|
return self.device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
state = self.bridge.light_get_state(self.info)
|
return self.device.state.get('level', 255)
|
||||||
return int(state['dim'])
|
|
||||||
|
@property
|
||||||
|
def xy_color(self):
|
||||||
|
"""Return the XY color values of this light."""
|
||||||
|
return self.device.state.get('color_xy')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp(self):
|
||||||
|
"""Return the color temperature of this light in mireds."""
|
||||||
|
return self.device.state.get('temperature_mireds')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""True if device is on."""
|
"""True if device is on."""
|
||||||
state = self.bridge.light_get_state(self.info)
|
return self.device.state['onoff'] != 0
|
||||||
return int(state['state'])
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the light on."""
|
"""Turn the light on."""
|
||||||
dim = kwargs.get(ATTR_BRIGHTNESS, self.brightness)
|
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
|
||||||
self.bridge.light_set_state(self.info, state=1, dim=dim)
|
|
||||||
|
if ATTR_XY_COLOR in kwargs:
|
||||||
|
xycolor = kwargs[ATTR_XY_COLOR]
|
||||||
|
elif ATTR_RGB_COLOR in kwargs:
|
||||||
|
xycolor = color_util.color_RGB_to_xy(
|
||||||
|
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
|
||||||
|
else:
|
||||||
|
xycolor = None
|
||||||
|
|
||||||
|
if xycolor is not None:
|
||||||
|
self.device.set_color(xycolor, transition=transitiontime)
|
||||||
|
|
||||||
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
|
colortemp = kwargs[ATTR_COLOR_TEMP]
|
||||||
|
self.device.set_temperature(mireds=colortemp,
|
||||||
|
transition=transitiontime)
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS in kwargs:
|
||||||
|
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
|
||||||
|
self.device.turn_on(level=brightness, transition=transitiontime)
|
||||||
|
else:
|
||||||
|
self.device.turn_on(transition=transitiontime)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the light off."""
|
"""Turn the light off."""
|
||||||
self.bridge.light_set_state(self.info, state=0, dim=0)
|
transitiontime = int(kwargs.get(ATTR_TRANSITION, 0))
|
||||||
|
self.device.turn_off(transition=transitiontime)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Synchronize state with bridge."""
|
"""Synchronize state with bridge."""
|
||||||
|
@@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
@@ -58,6 +58,11 @@ class WinkLight(Light):
|
|||||||
"""Return the brightness of the light."""
|
"""Return the brightness of the light."""
|
||||||
return int(self.wink.brightness() * 255)
|
return int(self.wink.brightness() * 255)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
|
@@ -101,7 +101,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, Light):
|
|||||||
|
|
||||||
# Zwave multilevel switches use a range of [0, 99] to control
|
# Zwave multilevel switches use a range of [0, 99] to control
|
||||||
# brightness.
|
# brightness.
|
||||||
brightness = (self._brightness / 255) * 99
|
brightness = int((self._brightness / 255) * 99)
|
||||||
|
|
||||||
if self._value.node.set_dimmer(self._value.value_id, brightness):
|
if self._value.node.set_dimmer(self._value.value_id, brightness):
|
||||||
self._state = STATE_ON
|
self._state = STATE_ON
|
||||||
|
@@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@@ -56,6 +56,11 @@ class WinkLockDevice(LockDevice):
|
|||||||
"""Return true if device is locked."""
|
"""Return true if device is locked."""
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
def lock(self, **kwargs):
|
def lock(self, **kwargs):
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
self.wink.set_state(True)
|
self.wink.set_state(True)
|
||||||
|
@@ -19,6 +19,8 @@ from homeassistant.const import (
|
|||||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = 'media_player'
|
DOMAIN = 'media_player'
|
||||||
SCAN_INTERVAL = 10
|
SCAN_INTERVAL = 10
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
discovery.SERVICE_CAST: 'cast',
|
discovery.SERVICE_CAST: 'cast',
|
||||||
discovery.SERVICE_SONOS: 'sonos',
|
discovery.SERVICE_SONOS: 'sonos',
|
||||||
discovery.SERVICE_PLEX: 'plex',
|
discovery.SERVICE_PLEX: 'plex',
|
||||||
|
discovery.SERVICE_SQUEEZEBOX: 'squeezebox',
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVICE_PLAY_MEDIA = 'play_media'
|
SERVICE_PLAY_MEDIA = 'play_media'
|
||||||
@@ -229,11 +232,9 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def media_player_service_handler(service):
|
def media_player_service_handler(service):
|
||||||
"""Map services to methods on MediaPlayerDevice."""
|
"""Map services to methods on MediaPlayerDevice."""
|
||||||
target_players = component.extract_from_service(service)
|
|
||||||
|
|
||||||
method = SERVICE_TO_METHOD[service.service]
|
method = SERVICE_TO_METHOD[service.service]
|
||||||
|
|
||||||
for player in target_players:
|
for player in component.extract_from_service(service):
|
||||||
getattr(player, method)()
|
getattr(player, method)()
|
||||||
|
|
||||||
if player.should_poll:
|
if player.should_poll:
|
||||||
@@ -245,14 +246,15 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def volume_set_service(service):
|
def volume_set_service(service):
|
||||||
"""Set specified volume on the media player."""
|
"""Set specified volume on the media player."""
|
||||||
target_players = component.extract_from_service(service)
|
volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL)
|
||||||
|
|
||||||
if ATTR_MEDIA_VOLUME_LEVEL not in service.data:
|
if volume is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Received call to %s without attribute %s',
|
||||||
|
service.service, ATTR_MEDIA_VOLUME_LEVEL)
|
||||||
return
|
return
|
||||||
|
|
||||||
volume = service.data[ATTR_MEDIA_VOLUME_LEVEL]
|
for player in component.extract_from_service(service):
|
||||||
|
|
||||||
for player in target_players:
|
|
||||||
player.set_volume_level(volume)
|
player.set_volume_level(volume)
|
||||||
|
|
||||||
if player.should_poll:
|
if player.should_poll:
|
||||||
@@ -263,14 +265,15 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def volume_mute_service(service):
|
def volume_mute_service(service):
|
||||||
"""Mute (true) or unmute (false) the media player."""
|
"""Mute (true) or unmute (false) the media player."""
|
||||||
target_players = component.extract_from_service(service)
|
mute = service.data.get(ATTR_MEDIA_VOLUME_MUTED)
|
||||||
|
|
||||||
if ATTR_MEDIA_VOLUME_MUTED not in service.data:
|
if mute is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Received call to %s without attribute %s',
|
||||||
|
service.service, ATTR_MEDIA_VOLUME_MUTED)
|
||||||
return
|
return
|
||||||
|
|
||||||
mute = service.data[ATTR_MEDIA_VOLUME_MUTED]
|
for player in component.extract_from_service(service):
|
||||||
|
|
||||||
for player in target_players:
|
|
||||||
player.mute_volume(mute)
|
player.mute_volume(mute)
|
||||||
|
|
||||||
if player.should_poll:
|
if player.should_poll:
|
||||||
@@ -281,14 +284,15 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def media_seek_service(service):
|
def media_seek_service(service):
|
||||||
"""Seek to a position."""
|
"""Seek to a position."""
|
||||||
target_players = component.extract_from_service(service)
|
position = service.data.get(ATTR_MEDIA_SEEK_POSITION)
|
||||||
|
|
||||||
if ATTR_MEDIA_SEEK_POSITION not in service.data:
|
if position is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Received call to %s without attribute %s',
|
||||||
|
service.service, ATTR_MEDIA_SEEK_POSITION)
|
||||||
return
|
return
|
||||||
|
|
||||||
position = service.data[ATTR_MEDIA_SEEK_POSITION]
|
for player in component.extract_from_service(service):
|
||||||
|
|
||||||
for player in target_players:
|
|
||||||
player.media_seek(position)
|
player.media_seek(position)
|
||||||
|
|
||||||
if player.should_poll:
|
if player.should_poll:
|
||||||
@@ -302,10 +306,12 @@ def setup(hass, config):
|
|||||||
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
|
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
|
||||||
media_id = service.data.get(ATTR_MEDIA_CONTENT_ID)
|
media_id = service.data.get(ATTR_MEDIA_CONTENT_ID)
|
||||||
|
|
||||||
if media_type is None:
|
if media_type is None or media_id is None:
|
||||||
return
|
missing_attr = (ATTR_MEDIA_CONTENT_TYPE if media_type is None
|
||||||
|
else ATTR_MEDIA_CONTENT_ID)
|
||||||
if media_id is None:
|
_LOGGER.error(
|
||||||
|
'Received call to %s without attribute %s',
|
||||||
|
service.service, missing_attr)
|
||||||
return
|
return
|
||||||
|
|
||||||
for player in component.extract_from_service(service):
|
for player in component.extract_from_service(service):
|
||||||
|
@@ -239,7 +239,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
|||||||
if self._cur_track > 0:
|
if self._cur_track > 0:
|
||||||
support |= SUPPORT_PREVIOUS_TRACK
|
support |= SUPPORT_PREVIOUS_TRACK
|
||||||
|
|
||||||
if self._cur_track < len(self.tracks)-1:
|
if self._cur_track < len(self.tracks) - 1:
|
||||||
support |= SUPPORT_NEXT_TRACK
|
support |= SUPPORT_NEXT_TRACK
|
||||||
|
|
||||||
return support
|
return support
|
||||||
@@ -252,7 +252,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
|||||||
|
|
||||||
def media_next_track(self):
|
def media_next_track(self):
|
||||||
"""Send next track command."""
|
"""Send next track command."""
|
||||||
if self._cur_track < len(self.tracks)-1:
|
if self._cur_track < len(self.tracks) - 1:
|
||||||
self._cur_track += 1
|
self._cur_track += 1
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
|
@@ -50,7 +50,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
self._server = jsonrpc_requests.Server(
|
self._server = jsonrpc_requests.Server(
|
||||||
'{}/jsonrpc'.format(self._url),
|
'{}/jsonrpc'.format(self._url),
|
||||||
auth=auth)
|
auth=auth)
|
||||||
self._players = None
|
self._players = list()
|
||||||
self._properties = None
|
self._properties = None
|
||||||
self._item = None
|
self._item = None
|
||||||
self._app_properties = None
|
self._app_properties = None
|
||||||
@@ -67,8 +67,9 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
try:
|
try:
|
||||||
return self._server.Player.GetActivePlayers()
|
return self._server.Player.GetActivePlayers()
|
||||||
except jsonrpc_requests.jsonrpc.TransportError:
|
except jsonrpc_requests.jsonrpc.TransportError:
|
||||||
_LOGGER.warning('Unable to fetch kodi data')
|
if self._players is not None:
|
||||||
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
_LOGGER.warning('Unable to fetch kodi data')
|
||||||
|
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
|
|||||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['python-mpd2==0.5.4']
|
REQUIREMENTS = ['python-mpd2==0.5.5']
|
||||||
|
|
||||||
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||||
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||||
|
@@ -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 (
|
from homeassistant.components.media_player import (
|
||||||
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_MUTE,
|
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||||
SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||||
|
MediaPlayerDevice)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
|
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN)
|
||||||
|
|
||||||
@@ -27,7 +28,8 @@ _REQUESTS_LOGGER = logging.getLogger('requests')
|
|||||||
_REQUESTS_LOGGER.setLevel(logging.ERROR)
|
_REQUESTS_LOGGER.setLevel(logging.ERROR)
|
||||||
|
|
||||||
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA |\
|
||||||
|
SUPPORT_SEEK
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@@ -222,7 +224,7 @@ class SonosDevice(MediaPlayerDevice):
|
|||||||
|
|
||||||
@only_if_coordinator
|
@only_if_coordinator
|
||||||
def media_play(self):
|
def media_play(self):
|
||||||
"""Send paly command."""
|
"""Send play command."""
|
||||||
self._player.play()
|
self._player.play()
|
||||||
|
|
||||||
@only_if_coordinator
|
@only_if_coordinator
|
||||||
@@ -249,3 +251,8 @@ class SonosDevice(MediaPlayerDevice):
|
|||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
"""Turn the media player on."""
|
"""Turn the media player on."""
|
||||||
self._player.play()
|
self._player.play()
|
||||||
|
|
||||||
|
@only_if_coordinator
|
||||||
|
def play_media(self, media_type, media_id):
|
||||||
|
"""Send the play_media command to the media player."""
|
||||||
|
self._player.play_uri(media_id)
|
||||||
|
@@ -22,19 +22,32 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
|
|||||||
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
||||||
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||||
|
|
||||||
|
KNOWN_DEVICES = []
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the squeezebox platform."""
|
"""Setup the squeezebox platform."""
|
||||||
if not config.get(CONF_HOST):
|
if discovery_info is not None:
|
||||||
|
host = discovery_info[0]
|
||||||
|
port = 9090
|
||||||
|
else:
|
||||||
|
host = config.get(CONF_HOST)
|
||||||
|
port = int(config.get('port', 9090))
|
||||||
|
|
||||||
|
if not host:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Missing required configuration items in %s: %s",
|
"Missing required configuration items in %s: %s",
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
CONF_HOST)
|
CONF_HOST)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Only add a media server once
|
||||||
|
if host in KNOWN_DEVICES:
|
||||||
|
return False
|
||||||
|
KNOWN_DEVICES.append(host)
|
||||||
|
|
||||||
lms = LogitechMediaServer(
|
lms = LogitechMediaServer(
|
||||||
config.get(CONF_HOST),
|
host, port,
|
||||||
config.get('port', '9090'),
|
|
||||||
config.get(CONF_USERNAME),
|
config.get(CONF_USERNAME),
|
||||||
config.get(CONF_PASSWORD))
|
config.get(CONF_PASSWORD))
|
||||||
|
|
||||||
|
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
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.helpers import validate_config
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
|
|||||||
|
|
||||||
REQUIREMENTS = ['paho-mqtt==1.1']
|
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||||
|
|
||||||
|
CONF_EMBEDDED = 'embedded'
|
||||||
CONF_BROKER = 'broker'
|
CONF_BROKER = 'broker'
|
||||||
CONF_PORT = 'port'
|
CONF_PORT = 'port'
|
||||||
CONF_CLIENT_ID = 'client_id'
|
CONF_CLIENT_ID = 'client_id'
|
||||||
@@ -92,21 +93,50 @@ def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
|
|||||||
MQTT_CLIENT.subscribe(topic, qos)
|
MQTT_CLIENT.subscribe(topic, qos)
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_server(hass, config):
|
||||||
|
"""Try to start embedded MQTT broker."""
|
||||||
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
|
# Only setup if embedded config passed in or no broker specified
|
||||||
|
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server = prepare_setup_platform(hass, config, DOMAIN, 'server')
|
||||||
|
|
||||||
|
if server is None:
|
||||||
|
_LOGGER.error('Unable to load embedded server.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
success, broker_config = server.start(hass, conf.get(CONF_EMBEDDED))
|
||||||
|
|
||||||
|
return success and broker_config
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Start the MQTT protocol service."""
|
"""Start the MQTT protocol service."""
|
||||||
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER):
|
# pylint: disable=too-many-locals
|
||||||
return False
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
|
||||||
|
|
||||||
broker = conf[CONF_BROKER]
|
|
||||||
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
|
||||||
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
|
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
|
||||||
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
||||||
username = util.convert(conf.get(CONF_USERNAME), str)
|
|
||||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
broker_config = _setup_server(hass, config)
|
||||||
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
|
|
||||||
protocol = util.convert(conf.get(CONF_PROTOCOL), str, DEFAULT_PROTOCOL)
|
# 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):
|
if protocol not in (PROTOCOL_31, PROTOCOL_311):
|
||||||
_LOGGER.error('Invalid protocol specified: %s. Allowed values: %s, %s',
|
_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)
|
message = call.data.get(ATTR_MESSAGE)
|
||||||
|
|
||||||
if message is None:
|
if message is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
'Received call to %s without attribute %s',
|
||||||
|
call.service, ATTR_MESSAGE)
|
||||||
return
|
return
|
||||||
|
|
||||||
title = template.render(
|
title = template.render(
|
||||||
|
@@ -45,7 +45,7 @@ class FileNotificationService(BaseNotificationService):
|
|||||||
title = '{} notifications (Log started: {})\n{}\n'.format(
|
title = '{} notifications (Log started: {})\n{}\n'.format(
|
||||||
kwargs.get(ATTR_TITLE),
|
kwargs.get(ATTR_TITLE),
|
||||||
dt_util.strip_microseconds(dt_util.utcnow()),
|
dt_util.strip_microseconds(dt_util.utcnow()),
|
||||||
'-'*80)
|
'-' * 80)
|
||||||
file.write(title)
|
file.write(title)
|
||||||
|
|
||||||
if self.add_timestamp == 1:
|
if self.add_timestamp == 1:
|
||||||
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = ['python-telegram-bot==3.2.0']
|
REQUIREMENTS = ['python-telegram-bot==3.4']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
@@ -7,9 +7,9 @@ https://home-assistant.io/components/rfxtrx/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/' +
|
REQUIREMENTS = ['pyRFXtrx==0.6.5']
|
||||||
'archive/0.5.zip#pyRFXtrx==0.5']
|
|
||||||
|
|
||||||
DOMAIN = "rfxtrx"
|
DOMAIN = "rfxtrx"
|
||||||
|
|
||||||
@@ -72,6 +72,10 @@ def setup(hass, config):
|
|||||||
else:
|
else:
|
||||||
RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug)
|
RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug)
|
||||||
|
|
||||||
|
def _shutdown_rfxtrx(event):
|
||||||
|
RFXOBJECT.close_connection()
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown_rfxtrx)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@@ -19,7 +19,8 @@ from homeassistant.const import (
|
|||||||
from homeassistant.helpers.entity import ToggleEntity, split_entity_id
|
from homeassistant.helpers.entity import ToggleEntity, split_entity_id
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.event import track_point_in_utc_time
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
from homeassistant.helpers.service import call_from_config
|
from homeassistant.helpers.service import (call_from_config,
|
||||||
|
validate_service_call)
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
DOMAIN = "script"
|
DOMAIN = "script"
|
||||||
@@ -30,9 +31,7 @@ STATE_NOT_RUNNING = 'Not Running'
|
|||||||
|
|
||||||
CONF_ALIAS = "alias"
|
CONF_ALIAS = "alias"
|
||||||
CONF_SERVICE = "service"
|
CONF_SERVICE = "service"
|
||||||
CONF_SERVICE_OLD = "execute_service"
|
|
||||||
CONF_SERVICE_DATA = "data"
|
CONF_SERVICE_DATA = "data"
|
||||||
CONF_SERVICE_DATA_OLD = "service_data"
|
|
||||||
CONF_SEQUENCE = "sequence"
|
CONF_SEQUENCE = "sequence"
|
||||||
CONF_EVENT = "event"
|
CONF_EVENT = "event"
|
||||||
CONF_EVENT_DATA = "event_data"
|
CONF_EVENT_DATA = "event_data"
|
||||||
@@ -174,7 +173,7 @@ class Script(ToggleEntity):
|
|||||||
for cur, action in islice(enumerate(self.sequence), self._cur,
|
for cur, action in islice(enumerate(self.sequence), self._cur,
|
||||||
None):
|
None):
|
||||||
|
|
||||||
if CONF_SERVICE in action or CONF_SERVICE_OLD in action:
|
if validate_service_call(action) is None:
|
||||||
self._call_service(action)
|
self._call_service(action)
|
||||||
|
|
||||||
elif CONF_EVENT in action:
|
elif CONF_EVENT in action:
|
||||||
@@ -211,14 +210,7 @@ class Script(ToggleEntity):
|
|||||||
|
|
||||||
def _call_service(self, action):
|
def _call_service(self, action):
|
||||||
"""Call the service specified in the action."""
|
"""Call the service specified in the action."""
|
||||||
# Backwards compatibility
|
self._last_action = action.get(CONF_ALIAS, 'call service')
|
||||||
if CONF_SERVICE not in action and CONF_SERVICE_OLD in action:
|
|
||||||
action[CONF_SERVICE] = action[CONF_SERVICE_OLD]
|
|
||||||
|
|
||||||
if CONF_SERVICE_DATA not in action and CONF_SERVICE_DATA_OLD in action:
|
|
||||||
action[CONF_SERVICE_DATA] = action[CONF_SERVICE_DATA_OLD]
|
|
||||||
|
|
||||||
self._last_action = action.get(CONF_ALIAS, action[CONF_SERVICE])
|
|
||||||
_LOGGER.info("Executing script %s step %s", self._name,
|
_LOGGER.info("Executing script %s step %s", self._name,
|
||||||
self._last_action)
|
self._last_action)
|
||||||
call_from_config(self.hass, action, True)
|
call_from_config(self.hass, action, True)
|
||||||
|
@@ -8,7 +8,8 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, bloomsky)
|
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors,
|
||||||
|
bloomsky, vera)
|
||||||
|
|
||||||
DOMAIN = 'sensor'
|
DOMAIN = 'sensor'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@@ -25,6 +26,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
ecobee.DISCOVER_SENSORS: 'ecobee',
|
ecobee.DISCOVER_SENSORS: 'ecobee',
|
||||||
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
||||||
mysensors.DISCOVER_SENSORS: 'mysensors',
|
mysensors.DISCOVER_SENSORS: 'mysensors',
|
||||||
|
vera.DISCOVER_SENSORS: 'vera',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Bitcoin information service that uses blockchain.info and its online wallet.
|
Bitcoin information service that uses blockchain.info.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/sensor.bitcoin/
|
https://home-assistant.io/components/sensor.bitcoin/
|
||||||
@@ -10,10 +10,9 @@ from datetime import timedelta
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
REQUIREMENTS = ['blockchain==1.2.1']
|
REQUIREMENTS = ['blockchain==1.3.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
OPTION_TYPES = {
|
OPTION_TYPES = {
|
||||||
'wallet': ['Wallet balance', 'BTC'],
|
|
||||||
'exchangerate': ['Exchange rate (1 BTC)', None],
|
'exchangerate': ['Exchange rate (1 BTC)', None],
|
||||||
'trade_volume_btc': ['Trade volume', 'BTC'],
|
'trade_volume_btc': ['Trade volume', 'BTC'],
|
||||||
'miners_revenue_usd': ['Miners revenue', 'USD'],
|
'miners_revenue_usd': ['Miners revenue', 'USD'],
|
||||||
@@ -43,33 +42,17 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the Bitcoin sensor."""
|
"""Setup the Bitcoin sensors."""
|
||||||
from blockchain.wallet import Wallet
|
from blockchain import exchangerates
|
||||||
from blockchain import exchangerates, exceptions
|
|
||||||
|
|
||||||
wallet_id = config.get('wallet', None)
|
|
||||||
password = config.get('password', None)
|
|
||||||
currency = config.get('currency', 'USD')
|
currency = config.get('currency', 'USD')
|
||||||
|
|
||||||
if currency not in exchangerates.get_ticker():
|
if currency not in exchangerates.get_ticker():
|
||||||
_LOGGER.error('Currency "%s" is not available. Using "USD".', currency)
|
_LOGGER.error('Currency "%s" is not available. Using "USD"', currency)
|
||||||
currency = 'USD'
|
currency = 'USD'
|
||||||
|
|
||||||
if wallet_id is not None and password is not None:
|
|
||||||
wallet = Wallet(wallet_id, password)
|
|
||||||
try:
|
|
||||||
wallet.get_balance()
|
|
||||||
except exceptions.APIException as error:
|
|
||||||
_LOGGER.error(error)
|
|
||||||
wallet = None
|
|
||||||
else:
|
|
||||||
wallet = None
|
|
||||||
|
|
||||||
data = BitcoinData()
|
data = BitcoinData()
|
||||||
dev = []
|
dev = []
|
||||||
if wallet is not None and password is not None:
|
|
||||||
dev.append(BitcoinSensor(data, 'wallet', currency, wallet))
|
|
||||||
|
|
||||||
for variable in config['display_options']:
|
for variable in config['display_options']:
|
||||||
if variable not in OPTION_TYPES:
|
if variable not in OPTION_TYPES:
|
||||||
_LOGGER.error('Option type: "%s" does not exist', variable)
|
_LOGGER.error('Option type: "%s" does not exist', variable)
|
||||||
@@ -83,13 +66,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
class BitcoinSensor(Entity):
|
class BitcoinSensor(Entity):
|
||||||
"""Representation of a Bitcoin sensor."""
|
"""Representation of a Bitcoin sensor."""
|
||||||
|
|
||||||
def __init__(self, data, option_type, currency, wallet=''):
|
def __init__(self, data, option_type, currency):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.data = data
|
self.data = data
|
||||||
self._name = OPTION_TYPES[option_type][0]
|
self._name = OPTION_TYPES[option_type][0]
|
||||||
self._unit_of_measurement = OPTION_TYPES[option_type][1]
|
self._unit_of_measurement = OPTION_TYPES[option_type][1]
|
||||||
self._currency = currency
|
self._currency = currency
|
||||||
self._wallet = wallet
|
|
||||||
self.type = option_type
|
self.type = option_type
|
||||||
self._state = None
|
self._state = None
|
||||||
self.update()
|
self.update()
|
||||||
@@ -122,10 +104,7 @@ class BitcoinSensor(Entity):
|
|||||||
ticker = self.data.ticker
|
ticker = self.data.ticker
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
if self.type == 'wallet' and self._wallet is not None:
|
if self.type == 'exchangerate':
|
||||||
self._state = '{0:.8f}'.format(self._wallet.get_balance() *
|
|
||||||
0.00000001)
|
|
||||||
elif self.type == 'exchangerate':
|
|
||||||
self._state = ticker[self._currency].p15min
|
self._state = ticker[self._currency].p15min
|
||||||
self._unit_of_measurement = self._currency
|
self._unit_of_measurement = self._currency
|
||||||
elif self.type == 'trade_volume_btc':
|
elif self.type == 'trade_volume_btc':
|
||||||
|
@@ -11,7 +11,7 @@ from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
REQUIREMENTS = ['python-forecastio==1.3.3']
|
REQUIREMENTS = ['python-forecastio==1.3.4']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Sensor types are defined like so:
|
# Sensor types are defined like so:
|
||||||
|
@@ -61,12 +61,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return False
|
return False
|
||||||
except requests.exceptions.MissingSchema:
|
except requests.exceptions.MissingSchema:
|
||||||
_LOGGER.error("Missing resource or schema in configuration. "
|
_LOGGER.error("Missing resource or schema in configuration. "
|
||||||
"Please check the details in the configuration file.")
|
"Please check the details in the configuration file")
|
||||||
return False
|
return False
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.error("No route to resource/endpoint: '%s'. "
|
_LOGGER.error("No route to resource/endpoint: %s", url)
|
||||||
"Please check the details in the configuration file.",
|
|
||||||
url)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
rest = GlancesData(url)
|
rest = GlancesData(url)
|
||||||
@@ -167,6 +165,5 @@ class GlancesData(object):
|
|||||||
response = requests.get(self._resource, timeout=10)
|
response = requests.get(self._resource, timeout=10)
|
||||||
self.data = response.json()
|
self.data = response.json()
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.error("No route to host/endpoint '%s'. Is device offline?",
|
_LOGGER.error("No route to host/endpoint: %s", self._resource)
|
||||||
self._resource)
|
|
||||||
self.data = None
|
self.data = None
|
||||||
|
@@ -32,10 +32,7 @@ JSON_VARIABLE_NAMES = {'weather_humidity': 'humidity',
|
|||||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
|
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V',
|
||||||
'kph': 'kph', 'temperature': '°C'}
|
'kph': 'kph', 'temperature': '°C'}
|
||||||
|
|
||||||
SENSOR_TEMP_TYPES = ['temperature',
|
SENSOR_TEMP_TYPES = ['temperature', 'target']
|
||||||
'target',
|
|
||||||
'away_temperature[0]',
|
|
||||||
'away_temperature[1]']
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['psutil==4.0.0']
|
REQUIREMENTS = ['psutil==4.1.0']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||||
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||||
@@ -38,7 +38,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the sensors."""
|
"""Setup the System sensors."""
|
||||||
dev = []
|
dev = []
|
||||||
for resource in config['resources']:
|
for resource in config['resources']:
|
||||||
if 'arg' not in resource:
|
if 'arg' not in resource:
|
||||||
|
@@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.template/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.sensor import DOMAIN
|
from homeassistant.components.sensor import ENTITY_ID_FORMAT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
|
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
|
||||||
from homeassistant.core import EVENT_STATE_CHANGED
|
from homeassistant.core import EVENT_STATE_CHANGED
|
||||||
@@ -15,11 +15,8 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
|
|||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
CONF_SENSORS = 'sensors'
|
CONF_SENSORS = 'sensors'
|
||||||
STATE_ERROR = 'error'
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@@ -70,21 +67,21 @@ class SensorTemplate(Entity):
|
|||||||
def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
|
def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
|
||||||
state_template):
|
state_template):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
|
|
||||||
self.hass = hass
|
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._template = state_template
|
self._template = state_template
|
||||||
self.update()
|
self._state = None
|
||||||
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
|
||||||
|
|
||||||
def _event_listener(self, event):
|
self.update()
|
||||||
"""Called when the target device changes state."""
|
|
||||||
if not hasattr(self, 'hass'):
|
def template_sensor_event_listener(event):
|
||||||
return
|
"""Called when the target device changes state."""
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED, template_sensor_event_listener)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -111,10 +108,10 @@ class SensorTemplate(Entity):
|
|||||||
try:
|
try:
|
||||||
self._state = template.render(self.hass, self._template)
|
self._state = template.render(self.hass, self._template)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
self._state = STATE_ERROR
|
|
||||||
if ex.args and ex.args[0].startswith(
|
if ex.args and ex.args[0].startswith(
|
||||||
"UndefinedError: 'None' has no attribute"):
|
"UndefinedError: 'None' has no attribute"):
|
||||||
# Common during HA startup - so just a warning
|
# Common during HA startup - so just a warning
|
||||||
_LOGGER.warning(ex)
|
_LOGGER.warning(ex)
|
||||||
return
|
return
|
||||||
|
self._state = None
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
|
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
|
import logging
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
||||||
EVENT_HOMEASSISTANT_STOP, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.components.vera import (
|
||||||
|
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.8']
|
DEPENDENCIES = ['vera']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
def get_devices(hass, config):
|
|
||||||
"""Setup the Vera Sensors."""
|
|
||||||
import pyvera as veraApi
|
|
||||||
|
|
||||||
base_url = config.get('vera_controller_url')
|
|
||||||
if not base_url:
|
|
||||||
_LOGGER.error(
|
|
||||||
"The required parameter 'vera_controller_url'"
|
|
||||||
" was not found in config"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
device_data = config.get('device_data', {})
|
|
||||||
|
|
||||||
vera_controller, created = veraApi.init_controller(base_url)
|
|
||||||
|
|
||||||
if created:
|
|
||||||
def stop_subscription(event):
|
|
||||||
"""Shutdown Vera subscriptions and subscription thread on exit."""
|
|
||||||
_LOGGER.info("Shutting down subscriptions.")
|
|
||||||
vera_controller.stop()
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
|
||||||
|
|
||||||
categories = ['Temperature Sensor',
|
|
||||||
'Light Sensor',
|
|
||||||
'Humidity Sensor',
|
|
||||||
'Sensor']
|
|
||||||
devices = []
|
|
||||||
try:
|
|
||||||
devices = vera_controller.get_devices(categories)
|
|
||||||
except RequestException:
|
|
||||||
# There was a network related error connecting to the vera controller.
|
|
||||||
_LOGGER.exception("Error communicating with Vera API")
|
|
||||||
return False
|
|
||||||
|
|
||||||
vera_sensors = []
|
|
||||||
for device in devices:
|
|
||||||
extra_data = device_data.get(device.device_id, {})
|
|
||||||
exclude = extra_data.get('exclude', False)
|
|
||||||
|
|
||||||
if exclude is not True:
|
|
||||||
vera_sensors.append(
|
|
||||||
VeraSensor(device, vera_controller, extra_data))
|
|
||||||
|
|
||||||
return vera_sensors
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
||||||
"""Perform the setup for Vera controller devices."""
|
"""Perform the setup for Vera controller devices."""
|
||||||
add_devices(get_devices(hass, config))
|
add_devices_callback(
|
||||||
|
VeraSensor(device, VERA_CONTROLLER)
|
||||||
|
for device in VERA_DEVICES['sensor'])
|
||||||
|
|
||||||
|
|
||||||
class VeraSensor(Entity):
|
class VeraSensor(VeraDevice, Entity):
|
||||||
"""Representation of a Vera Sensor."""
|
"""Representation of a Vera Sensor."""
|
||||||
|
|
||||||
def __init__(self, vera_device, controller, extra_data=None):
|
def __init__(self, vera_device, controller):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.vera_device = vera_device
|
self.current_value = None
|
||||||
self.controller = controller
|
|
||||||
self.extra_data = extra_data
|
|
||||||
if self.extra_data and self.extra_data.get('name'):
|
|
||||||
self._name = self.extra_data.get('name')
|
|
||||||
else:
|
|
||||||
self._name = self.vera_device.name
|
|
||||||
self.current_value = ''
|
|
||||||
self._temperature_units = None
|
self._temperature_units = None
|
||||||
|
VeraDevice.__init__(self, vera_device, controller)
|
||||||
self.controller.register(vera_device, self._update_callback)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def _update_callback(self, _device):
|
|
||||||
"""Called by the vera device callback to update state."""
|
|
||||||
self.update_ha_state(True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""String representation of sensor."""
|
|
||||||
return "%s %s %s" % (self.name, self.vera_device.device_id, self.state)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self.current_value
|
return self.current_value
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the mame of the sensor."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit of measurement of this entity, if any."""
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
@@ -144,11 +75,6 @@ class VeraSensor(Entity):
|
|||||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
if self.vera_device.category == "Temperature Sensor":
|
if self.vera_device.category == "Temperature Sensor":
|
||||||
|
@@ -10,7 +10,7 @@ from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
|
|||||||
STATE_OPEN, TEMP_CELCIUS)
|
STATE_OPEN, TEMP_CELCIUS)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
SENSOR_TYPES = ['temperature', 'humidity']
|
SENSOR_TYPES = ['temperature', 'humidity']
|
||||||
|
|
||||||
@@ -74,6 +74,11 @@ class WinkSensorDevice(Entity):
|
|||||||
"""Return the name of the sensor if any."""
|
"""Return the name of the sensor if any."""
|
||||||
return self.wink.name()
|
return self.wink.name()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update state of the sensor."""
|
"""Update state of the sensor."""
|
||||||
self.wink.update_state()
|
self.wink.update_state()
|
||||||
|
@@ -16,7 +16,8 @@ from homeassistant.const import (
|
|||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||||
ATTR_ENTITY_ID)
|
ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
group, wemo, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
group, wemo, wink, isy994, verisure,
|
||||||
|
zwave, tellduslive, tellstick, mysensors, vera)
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@@ -40,6 +41,8 @@ DISCOVERY_PLATFORMS = {
|
|||||||
zwave.DISCOVER_SWITCHES: 'zwave',
|
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||||
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
||||||
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
||||||
|
tellstick.DISCOVER_SWITCHES: 'tellstick',
|
||||||
|
vera.DISCOVER_SWITCHES: 'vera',
|
||||||
}
|
}
|
||||||
|
|
||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
|
@@ -28,24 +28,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
pins = config.get('pins')
|
pins = config.get('pins')
|
||||||
for pinnum, pin in pins.items():
|
for pinnum, pin in pins.items():
|
||||||
if pin.get('name'):
|
if pin.get('name'):
|
||||||
switches.append(ArduinoSwitch(pin.get('name'),
|
switches.append(ArduinoSwitch(pinnum, pin))
|
||||||
pinnum,
|
|
||||||
pin.get('type')))
|
|
||||||
add_devices(switches)
|
add_devices(switches)
|
||||||
|
|
||||||
|
|
||||||
class ArduinoSwitch(SwitchDevice):
|
class ArduinoSwitch(SwitchDevice):
|
||||||
"""Representation of an Arduino switch."""
|
"""Representation of an Arduino switch."""
|
||||||
|
|
||||||
def __init__(self, name, pin, pin_type):
|
def __init__(self, pin, options):
|
||||||
"""Initialize the Pin."""
|
"""Initialize the Pin."""
|
||||||
self._pin = pin
|
self._pin = pin
|
||||||
self._name = name or DEVICE_DEFAULT_NAME
|
self._name = options.get('name') or DEVICE_DEFAULT_NAME
|
||||||
self.pin_type = pin_type
|
self.pin_type = options.get('type')
|
||||||
self.direction = 'out'
|
self.direction = 'out'
|
||||||
self._state = False
|
|
||||||
|
self._state = options.get('initial', False)
|
||||||
|
|
||||||
|
if options.get('negate', False):
|
||||||
|
self.turn_on_handler = arduino.BOARD.set_digital_out_low
|
||||||
|
self.turn_off_handler = arduino.BOARD.set_digital_out_high
|
||||||
|
else:
|
||||||
|
self.turn_on_handler = arduino.BOARD.set_digital_out_high
|
||||||
|
self.turn_off_handler = arduino.BOARD.set_digital_out_low
|
||||||
|
|
||||||
arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type)
|
arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type)
|
||||||
|
(self.turn_on_handler if self._state else self.turn_off_handler)(pin)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -60,9 +67,9 @@ class ArduinoSwitch(SwitchDevice):
|
|||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
"""Turn the pin to high/on."""
|
"""Turn the pin to high/on."""
|
||||||
self._state = True
|
self._state = True
|
||||||
arduino.BOARD.set_digital_out_high(self._pin)
|
self.turn_on_handler(self._pin)
|
||||||
|
|
||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
"""Turn the pin to low/off."""
|
"""Turn the pin to low/off."""
|
||||||
self._state = False
|
self._state = False
|
||||||
arduino.BOARD.set_digital_out_low(self._pin)
|
self.turn_off_handler(self._pin)
|
||||||
|
@@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
add_devices_callback(devices)
|
add_devices_callback(devices)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
class CommandSwitch(SwitchDevice):
|
class CommandSwitch(SwitchDevice):
|
||||||
"""Representation a switch that can be toggled using shell commands."""
|
"""Representation a switch that can be toggled using shell commands."""
|
||||||
|
|
||||||
@@ -92,6 +93,11 @@ class CommandSwitch(SwitchDevice):
|
|||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def assumed_state(self):
|
||||||
|
"""Return true if we do optimistic updates."""
|
||||||
|
return self._command_state is False
|
||||||
|
|
||||||
def _query_state(self):
|
def _query_state(self):
|
||||||
"""Query for state."""
|
"""Query for state."""
|
||||||
if not self._command_state:
|
if not self._command_state:
|
||||||
|
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
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/switch.tellstick/
|
https://home-assistant.io/components/switch.tellstick/
|
||||||
"""
|
"""
|
||||||
import logging
|
from homeassistant.components import tellstick
|
||||||
|
from homeassistant.components.tellstick import (ATTR_DISCOVER_DEVICES,
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
ATTR_DISCOVER_CONFIG)
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
SIGNAL_REPETITIONS = 1
|
|
||||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup Tellstick switches."""
|
"""Setup Tellstick switches."""
|
||||||
import tellcore.telldus as telldus
|
if (discovery_info is None or
|
||||||
import tellcore.constants as tellcore_constants
|
discovery_info[ATTR_DISCOVER_DEVICES] is None or
|
||||||
from tellcore.library import DirectCallbackDispatcher
|
tellstick.TELLCORE_REGISTRY is None):
|
||||||
|
return
|
||||||
|
|
||||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
# Allow platform level override, fallback to module config
|
||||||
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
|
signal_repetitions = discovery_info.get(
|
||||||
switches_and_lights = core.devices()
|
ATTR_DISCOVER_CONFIG, tellstick.DEFAULT_SIGNAL_REPETITIONS)
|
||||||
|
|
||||||
switches = []
|
add_devices(TellstickSwitchDevice(
|
||||||
for switch in switches_and_lights:
|
tellstick.TELLCORE_REGISTRY.get_device(switch_id), signal_repetitions)
|
||||||
if not switch.methods(tellcore_constants.TELLSTICK_DIM):
|
for switch_id in discovery_info[ATTR_DISCOVER_DEVICES])
|
||||||
switches.append(
|
|
||||||
TellstickSwitchDevice(switch, signal_repetitions))
|
|
||||||
|
|
||||||
def _device_event_callback(id_, method, data, cid):
|
|
||||||
"""Called from the TelldusCore library to update one device."""
|
|
||||||
for switch_device in switches:
|
|
||||||
if switch_device.tellstick_device.id == id_:
|
|
||||||
switch_device.update_ha_state()
|
|
||||||
break
|
|
||||||
|
|
||||||
callback_id = core.register_device_event(_device_event_callback)
|
|
||||||
|
|
||||||
def unload_telldus_lib(event):
|
|
||||||
"""Un-register the callback bindings."""
|
|
||||||
if callback_id is not None:
|
|
||||||
core.unregister_callback(callback_id)
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
|
|
||||||
|
|
||||||
add_devices_callback(switches)
|
|
||||||
|
|
||||||
|
|
||||||
class TellstickSwitchDevice(ToggleEntity):
|
class TellstickSwitchDevice(tellstick.TellstickDevice, ToggleEntity):
|
||||||
"""Representation of a Tellstick switch."""
|
"""Representation of a Tellstick switch."""
|
||||||
|
|
||||||
def __init__(self, tellstick_device, signal_repetitions):
|
|
||||||
"""Initialize the Tellstick switch."""
|
|
||||||
import tellcore.constants as tellcore_constants
|
|
||||||
|
|
||||||
self.tellstick_device = tellstick_device
|
|
||||||
self.signal_repetitions = signal_repetitions
|
|
||||||
|
|
||||||
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
|
||||||
tellcore_constants.TELLSTICK_TURNOFF)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assumed_state(self):
|
|
||||||
"""The Tellstick devices are always assumed state."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the switch if any."""
|
|
||||||
return self.tellstick_device.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if switch is on."""
|
"""Return true if switch is on."""
|
||||||
import tellcore.constants as tellcore_constants
|
return self._state
|
||||||
|
|
||||||
last_command = self.tellstick_device.last_sent_command(
|
def set_tellstick_state(self, last_command_sent, last_data_sent):
|
||||||
self.last_sent_command_mask)
|
"""Update the internal representation of the switch."""
|
||||||
|
from tellcore.constants import TELLSTICK_TURNON
|
||||||
|
self._state = last_command_sent == TELLSTICK_TURNON
|
||||||
|
|
||||||
return last_command == tellcore_constants.TELLSTICK_TURNON
|
def _send_tellstick_command(self, command, data):
|
||||||
|
"""Handle the turn_on / turn_off commands."""
|
||||||
|
from tellcore.constants import TELLSTICK_TURNON, TELLSTICK_TURNOFF
|
||||||
|
if command == TELLSTICK_TURNON:
|
||||||
|
self.tellstick_device.turn_on()
|
||||||
|
elif command == TELLSTICK_TURNOFF:
|
||||||
|
self.tellstick_device.turn_off()
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
for _ in range(self.signal_repetitions):
|
from tellcore.constants import TELLSTICK_TURNON
|
||||||
self.tellstick_device.turn_on()
|
self.call_tellstick(TELLSTICK_TURNON)
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the switch off."""
|
"""Turn the switch off."""
|
||||||
for _ in range(self.signal_repetitions):
|
from tellcore.constants import TELLSTICK_TURNOFF
|
||||||
self.tellstick_device.turn_off()
|
self.call_tellstick(TELLSTICK_TURNOFF)
|
||||||
self.update_ha_state()
|
|
||||||
|
@@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.template/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.switch import DOMAIN, SwitchDevice
|
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON)
|
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON)
|
||||||
from homeassistant.core import EVENT_STATE_CHANGED
|
from homeassistant.core import EVENT_STATE_CHANGED
|
||||||
@@ -16,17 +16,14 @@ from homeassistant.helpers.service import call_from_config
|
|||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF_SWITCHES = 'switches'
|
CONF_SWITCHES = 'switches'
|
||||||
|
|
||||||
STATE_ERROR = 'error'
|
|
||||||
|
|
||||||
ON_ACTION = 'turn_on'
|
ON_ACTION = 'turn_on'
|
||||||
OFF_ACTION = 'turn_off'
|
OFF_ACTION = 'turn_off'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@@ -84,32 +81,44 @@ class SwitchTemplate(SwitchDevice):
|
|||||||
def __init__(self, hass, device_id, friendly_name, state_template,
|
def __init__(self, hass, device_id, friendly_name, state_template,
|
||||||
on_action, off_action):
|
on_action, off_action):
|
||||||
"""Initialize the Template switch."""
|
"""Initialize the Template switch."""
|
||||||
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
self.hass = hass
|
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._template = state_template
|
self._template = state_template
|
||||||
self._on_action = on_action
|
self._on_action = on_action
|
||||||
self._off_action = off_action
|
self._off_action = off_action
|
||||||
self.update()
|
self._state = False
|
||||||
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
|
||||||
|
|
||||||
def _event_listener(self, event):
|
self.update()
|
||||||
"""Called when the target device changes state."""
|
|
||||||
if not hasattr(self, 'hass'):
|
def template_switch_event_listener(event):
|
||||||
return
|
"""Called when the target device changes state."""
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||||
|
template_switch_event_listener)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the switch."""
|
"""Return the name of the switch."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""If switch is available."""
|
||||||
|
return self._state is not None
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Fire the on action."""
|
"""Fire the on action."""
|
||||||
call_from_config(self.hass, self._on_action, True)
|
call_from_config(self.hass, self._on_action, True)
|
||||||
@@ -118,30 +127,19 @@ class SwitchTemplate(SwitchDevice):
|
|||||||
"""Fire the off action."""
|
"""Fire the off action."""
|
||||||
call_from_config(self.hass, self._off_action, True)
|
call_from_config(self.hass, self._off_action, True)
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if device is on."""
|
|
||||||
return self._value.lower() == 'true' or self._value == STATE_ON
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_off(self):
|
|
||||||
"""Return true if device is off."""
|
|
||||||
return self._value.lower() == 'false' or self._value == STATE_OFF
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""Return true if entity is available."""
|
|
||||||
return self.is_on or self.is_off
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state from the template."""
|
"""Update the state from the template."""
|
||||||
try:
|
try:
|
||||||
self._value = template.render(self.hass, self._template)
|
state = template.render(self.hass, self._template).lower()
|
||||||
if not self.available:
|
|
||||||
|
if state in _VALID_STATES:
|
||||||
|
self._state = state in ('true', STATE_ON)
|
||||||
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"`%s` is not a switch state, setting %s to unavailable",
|
'Received invalid switch is_on state: %s. Expected: %s',
|
||||||
self._value, self.entity_id)
|
state, ', '.join(_VALID_STATES))
|
||||||
|
self._state = None
|
||||||
|
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
self._value = STATE_ERROR
|
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
|
self._state = None
|
||||||
|
@@ -6,94 +6,33 @@ https://home-assistant.io/components/switch.vera/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED,
|
||||||
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
|
STATE_OFF, STATE_ON)
|
||||||
|
from homeassistant.components.vera import (
|
||||||
|
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.8']
|
DEPENDENCIES = ['vera']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
def get_devices(hass, config):
|
|
||||||
"""Find and return Vera switches."""
|
"""Find and return Vera switches."""
|
||||||
import pyvera as veraApi
|
add_devices_callback(
|
||||||
|
VeraSwitch(device, VERA_CONTROLLER) for
|
||||||
base_url = config.get('vera_controller_url')
|
device in VERA_DEVICES['switch'])
|
||||||
if not base_url:
|
|
||||||
_LOGGER.error(
|
|
||||||
"The required parameter 'vera_controller_url'"
|
|
||||||
" was not found in config"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
device_data = config.get('device_data', {})
|
|
||||||
|
|
||||||
vera_controller, created = veraApi.init_controller(base_url)
|
|
||||||
|
|
||||||
if created:
|
|
||||||
def stop_subscription(event):
|
|
||||||
"""Shutdown Vera subscriptions and subscription thread on exit."""
|
|
||||||
_LOGGER.info("Shutting down subscriptions.")
|
|
||||||
vera_controller.stop()
|
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
|
||||||
|
|
||||||
devices = []
|
|
||||||
try:
|
|
||||||
devices = vera_controller.get_devices([
|
|
||||||
'Switch', 'Armable Sensor', 'On/Off Switch'])
|
|
||||||
except RequestException:
|
|
||||||
# There was a network related error connecting to the vera controller.
|
|
||||||
_LOGGER.exception("Error communicating with Vera API")
|
|
||||||
return False
|
|
||||||
|
|
||||||
vera_switches = []
|
|
||||||
for device in devices:
|
|
||||||
extra_data = device_data.get(device.device_id, {})
|
|
||||||
exclude = extra_data.get('exclude', False)
|
|
||||||
|
|
||||||
if exclude is not True:
|
|
||||||
vera_switches.append(
|
|
||||||
VeraSwitch(device, vera_controller, extra_data))
|
|
||||||
|
|
||||||
return vera_switches
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
class VeraSwitch(VeraDevice, SwitchDevice):
|
||||||
"""Find and return Vera lights."""
|
|
||||||
add_devices(get_devices(hass, config))
|
|
||||||
|
|
||||||
|
|
||||||
class VeraSwitch(SwitchDevice):
|
|
||||||
"""Representation of a Vera Switch."""
|
"""Representation of a Vera Switch."""
|
||||||
|
|
||||||
def __init__(self, vera_device, controller, extra_data=None):
|
def __init__(self, vera_device, controller):
|
||||||
"""Initialize the Vera device."""
|
"""Initialize the Vera device."""
|
||||||
self.vera_device = vera_device
|
self._state = False
|
||||||
self.extra_data = extra_data
|
VeraDevice.__init__(self, vera_device, controller)
|
||||||
self.controller = controller
|
|
||||||
if self.extra_data and self.extra_data.get('name'):
|
|
||||||
self._name = self.extra_data.get('name')
|
|
||||||
else:
|
|
||||||
self._name = self.vera_device.name
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
|
||||||
self.controller.register(vera_device, self._update_callback)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def _update_callback(self, _device):
|
|
||||||
self.update_ha_state(True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the mame of the switch."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@@ -134,19 +73,11 @@ class VeraSwitch(SwitchDevice):
|
|||||||
self._state = STATE_OFF
|
self._state = STATE_OFF
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self._state == STATE_ON
|
return self._state
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Called by the vera device callback to update state."""
|
"""Called by the vera device callback to update state."""
|
||||||
if self.vera_device.is_switched_on():
|
self._state = self.vera_device.is_switched_on()
|
||||||
self._state = STATE_ON
|
|
||||||
else:
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
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(
|
_LOGGER.info(
|
||||||
'Subscription update for %s',
|
'Subscription update for %s',
|
||||||
_device)
|
_device)
|
||||||
|
if not hasattr(self, 'hass'):
|
||||||
|
self.update()
|
||||||
|
return
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
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)
|
service.data.get(ATTR_TEMPERATURE), float)
|
||||||
|
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Received call to %s without attribute %s",
|
||||||
|
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
|
||||||
return
|
return
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
|
@@ -11,8 +11,8 @@ from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the Demo thermostats."""
|
"""Setup the Demo thermostats."""
|
||||||
add_devices([
|
add_devices([
|
||||||
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19),
|
DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19, False),
|
||||||
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77),
|
DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77, True),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -21,13 +21,14 @@ class DemoThermostat(ThermostatDevice):
|
|||||||
"""Representation of a demo thermostat."""
|
"""Representation of a demo thermostat."""
|
||||||
|
|
||||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
def __init__(self, name, target_temperature, unit_of_measurement,
|
||||||
away, current_temperature):
|
away, current_temperature, is_fan_on):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._target_temperature = target_temperature
|
self._target_temperature = target_temperature
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._away = away
|
self._away = away
|
||||||
self._current_temperature = current_temperature
|
self._current_temperature = current_temperature
|
||||||
|
self._is_fan_on = is_fan_on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@@ -59,6 +60,11 @@ class DemoThermostat(ThermostatDevice):
|
|||||||
"""Return if away mode is on."""
|
"""Return if away mode is on."""
|
||||||
return self._away
|
return self._away
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fan_on(self):
|
||||||
|
"""Return true if the fan is on."""
|
||||||
|
return self._is_fan_on
|
||||||
|
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
self._target_temperature = temperature
|
self._target_temperature = temperature
|
||||||
@@ -70,3 +76,11 @@ class DemoThermostat(ThermostatDevice):
|
|||||||
def turn_away_mode_off(self):
|
def turn_away_mode_off(self):
|
||||||
"""Turn away mode off."""
|
"""Turn away mode off."""
|
||||||
self._away = False
|
self._away = False
|
||||||
|
|
||||||
|
def turn_fan_on(self):
|
||||||
|
"""Turn fan on."""
|
||||||
|
self._is_fan_on = True
|
||||||
|
|
||||||
|
def turn_fan_off(self):
|
||||||
|
"""Turn fan off."""
|
||||||
|
self._is_fan_on = False
|
||||||
|
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_ALARMS = 'verisure.alarm_control_panel'
|
||||||
DISCOVER_LOCKS = 'verisure.lock'
|
DISCOVER_LOCKS = 'verisure.lock'
|
||||||
|
|
||||||
REQUIREMENTS = ['vsure==0.6.1']
|
REQUIREMENTS = ['vsure==0.7.1']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.components import discovery
|
from homeassistant.components import discovery
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
|
||||||
REQUIREMENTS = ['pywemo==0.3.12']
|
REQUIREMENTS = ['pywemo==0.3.19']
|
||||||
|
|
||||||
DOMAIN = 'wemo'
|
DOMAIN = 'wemo'
|
||||||
DISCOVER_LIGHTS = 'wemo.light'
|
DISCOVER_LIGHTS = 'wemo.light'
|
||||||
|
@@ -15,7 +15,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
REQUIREMENTS = ['python-wink==0.6.2']
|
REQUIREMENTS = ['python-wink==0.6.4']
|
||||||
|
|
||||||
DISCOVER_LIGHTS = "wink.lights"
|
DISCOVER_LIGHTS = "wink.lights"
|
||||||
DISCOVER_SWITCHES = "wink.switches"
|
DISCOVER_SWITCHES = "wink.switches"
|
||||||
@@ -84,6 +84,11 @@ class WinkToggleDevice(ToggleEntity):
|
|||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""True if connection == True."""
|
||||||
|
return self.wink.available
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
self.wink.set_state(True)
|
self.wink.set_state(True)
|
||||||
|
@@ -28,6 +28,8 @@ DEFAULT_ZWAVE_CONFIG_PATH = os.path.join(sys.prefix, 'share',
|
|||||||
|
|
||||||
SERVICE_ADD_NODE = "add_node"
|
SERVICE_ADD_NODE = "add_node"
|
||||||
SERVICE_REMOVE_NODE = "remove_node"
|
SERVICE_REMOVE_NODE = "remove_node"
|
||||||
|
SERVICE_HEAL_NETWORK = "heal_network"
|
||||||
|
SERVICE_SOFT_RESET = "soft_reset"
|
||||||
|
|
||||||
DISCOVER_SENSORS = "zwave.sensors"
|
DISCOVER_SENSORS = "zwave.sensors"
|
||||||
DISCOVER_SWITCHES = "zwave.switch"
|
DISCOVER_SWITCHES = "zwave.switch"
|
||||||
@@ -149,6 +151,7 @@ def get_config_value(node, value_index):
|
|||||||
return get_config_value(node, value_index)
|
return get_config_value(node, value_index)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=R0914
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Setup Z-Wave.
|
"""Setup Z-Wave.
|
||||||
|
|
||||||
@@ -249,6 +252,14 @@ def setup(hass, config):
|
|||||||
"""Switch into exclusion mode."""
|
"""Switch into exclusion mode."""
|
||||||
NETWORK.controller.begin_command_remove_device()
|
NETWORK.controller.begin_command_remove_device()
|
||||||
|
|
||||||
|
def heal_network(event):
|
||||||
|
"""Heal the network."""
|
||||||
|
NETWORK.heal()
|
||||||
|
|
||||||
|
def soft_reset(event):
|
||||||
|
"""Soft reset the controller."""
|
||||||
|
NETWORK.controller.soft_reset()
|
||||||
|
|
||||||
def stop_zwave(event):
|
def stop_zwave(event):
|
||||||
"""Stop Z-Wave."""
|
"""Stop Z-Wave."""
|
||||||
NETWORK.stop()
|
NETWORK.stop()
|
||||||
@@ -268,6 +279,8 @@ def setup(hass, config):
|
|||||||
# hardware inclusion button
|
# hardware inclusion button
|
||||||
hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_node)
|
hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_node)
|
||||||
hass.services.register(DOMAIN, SERVICE_REMOVE_NODE, remove_node)
|
hass.services.register(DOMAIN, SERVICE_REMOVE_NODE, remove_node)
|
||||||
|
hass.services.register(DOMAIN, SERVICE_HEAL_NETWORK, heal_network)
|
||||||
|
hass.services.register(DOMAIN, SERVICE_SOFT_RESET, soft_reset)
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
|
|
||||||
__version__ = "0.15.0"
|
__version__ = "0.16.0"
|
||||||
REQUIRED_PYTHON_VER = (3, 4)
|
REQUIRED_PYTHON_VER = (3, 4)
|
||||||
|
|
||||||
# Can be used to specify a catch all when registering state or event listeners.
|
# Can be used to specify a catch all when registering state or event listeners.
|
||||||
|
@@ -775,7 +775,7 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
|||||||
|
|
||||||
def start_timer(event):
|
def start_timer(event):
|
||||||
"""Start the timer."""
|
"""Start the timer."""
|
||||||
thread = threading.Thread(target=timer)
|
thread = threading.Thread(target=timer, name='Timer')
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
|
|||||||
|
|
||||||
|
|
||||||
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
||||||
"""Generate a unique entity ID based on given entity IDs or used ids."""
|
"""Generate a unique entity ID based on given entity IDs or used IDs."""
|
||||||
name = (name or DEVICE_DEFAULT_NAME).lower()
|
name = (name or DEVICE_DEFAULT_NAME).lower()
|
||||||
if current_ids is None:
|
if current_ids is None:
|
||||||
if hass is None:
|
if hass is None:
|
||||||
@@ -41,12 +41,12 @@ def valid_entity_id(entity_id):
|
|||||||
|
|
||||||
|
|
||||||
class Entity(object):
|
class Entity(object):
|
||||||
"""ABC for Home Assistant entities."""
|
"""An abstract class for Home Assistant entities."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
# SAFE TO OVERWRITE
|
# SAFE TO OVERWRITE
|
||||||
# The properties and methods here are safe to overwrite when inherting this
|
# The properties and methods here are safe to overwrite when inheriting
|
||||||
# class. These may be used to customize the behavior of the entity.
|
# this class. These may be used to customize the behavior of the entity.
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return True if entity has to be polled for state.
|
"""Return True if entity has to be polled for state.
|
||||||
@@ -57,7 +57,7 @@ class Entity(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique id."""
|
"""Return an unique ID."""
|
||||||
return "{}.{}".format(self.__class__, id(self))
|
return "{}.{}".format(self.__class__, id(self))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -113,7 +113,7 @@ class Entity(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def assumed_state(self):
|
def assumed_state(self):
|
||||||
"""Return True if unable to access real state of entity."""
|
"""Return True if unable to access real state of the entity."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
@@ -223,7 +223,7 @@ class Entity(object):
|
|||||||
|
|
||||||
|
|
||||||
class ToggleEntity(Entity):
|
class ToggleEntity(Entity):
|
||||||
"""ABC for entities that can be turned on and off."""
|
"""An abstract class for entities that can be turned on and off."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
@property
|
@property
|
||||||
|
@@ -331,7 +331,9 @@ class ThreadPool(object):
|
|||||||
if not self.running:
|
if not self.running:
|
||||||
raise RuntimeError("ThreadPool not running")
|
raise RuntimeError("ThreadPool not running")
|
||||||
|
|
||||||
worker = threading.Thread(target=self._worker)
|
worker = threading.Thread(
|
||||||
|
target=self._worker,
|
||||||
|
name='ThreadPool Worker {}'.format(self.worker_count))
|
||||||
worker.daemon = True
|
worker.daemon = True
|
||||||
worker.start()
|
worker.start()
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ astral==0.9
|
|||||||
blinkstick==1.1.7
|
blinkstick==1.1.7
|
||||||
|
|
||||||
# homeassistant.components.sensor.bitcoin
|
# homeassistant.components.sensor.bitcoin
|
||||||
blockchain==1.2.1
|
blockchain==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.notify.xmpp
|
# homeassistant.components.notify.xmpp
|
||||||
dnspython3==1.12.0
|
dnspython3==1.12.0
|
||||||
@@ -54,6 +54,12 @@ freesms==0.1.0
|
|||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
fuzzywuzzy==0.8.0
|
fuzzywuzzy==0.8.0
|
||||||
|
|
||||||
|
# homeassistant.components.notify.gntp
|
||||||
|
gntp==1.0.3
|
||||||
|
|
||||||
|
# homeassistant.components.mqtt.server
|
||||||
|
hbmqtt==0.6.3
|
||||||
|
|
||||||
# homeassistant.components.thermostat.heatmiser
|
# homeassistant.components.thermostat.heatmiser
|
||||||
heatmiserV3==0.9.1
|
heatmiserV3==0.9.1
|
||||||
|
|
||||||
@@ -63,9 +69,6 @@ hikvision==0.4
|
|||||||
# homeassistant.components.sensor.dht
|
# homeassistant.components.sensor.dht
|
||||||
# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0
|
# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.rfxtrx
|
|
||||||
https://github.com/Danielhiversen/pyRFXtrx/archive/0.5.zip#pyRFXtrx==0.5
|
|
||||||
|
|
||||||
# homeassistant.components.sensor.netatmo
|
# homeassistant.components.sensor.netatmo
|
||||||
https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0
|
https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0
|
||||||
|
|
||||||
@@ -78,6 +81,9 @@ https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1
|
|||||||
# homeassistant.components.modbus
|
# homeassistant.components.modbus
|
||||||
https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0
|
https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0
|
||||||
|
|
||||||
|
# homeassistant.components.sensor.uber
|
||||||
|
https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_rides==0.1.2-dev
|
||||||
|
|
||||||
# homeassistant.components.sensor.sabnzbd
|
# homeassistant.components.sensor.sabnzbd
|
||||||
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
|
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
|
||||||
|
|
||||||
@@ -114,12 +120,15 @@ liffylights==0.9.4
|
|||||||
# homeassistant.components.light.limitlessled
|
# homeassistant.components.light.limitlessled
|
||||||
limitlessled==1.0.0
|
limitlessled==1.0.0
|
||||||
|
|
||||||
|
# homeassistant.components.notify.message_bird
|
||||||
|
messagebird==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.sensor.mfi
|
# homeassistant.components.sensor.mfi
|
||||||
# homeassistant.components.switch.mfi
|
# homeassistant.components.switch.mfi
|
||||||
mficlient==0.3.0
|
mficlient==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.discovery
|
# homeassistant.components.discovery
|
||||||
netdisco==0.5.4
|
netdisco==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.sensor.neurio_energy
|
# homeassistant.components.sensor.neurio_energy
|
||||||
neurio==0.2.10
|
neurio==0.2.10
|
||||||
@@ -143,7 +152,7 @@ plexapi==1.1.0
|
|||||||
proliphix==0.1.0
|
proliphix==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.sensor.systemmonitor
|
# homeassistant.components.sensor.systemmonitor
|
||||||
psutil==4.0.0
|
psutil==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.notify.pushbullet
|
# homeassistant.components.notify.pushbullet
|
||||||
pushbullet.py==0.9.0
|
pushbullet.py==0.9.0
|
||||||
@@ -154,6 +163,9 @@ pushetta==1.0.15
|
|||||||
# homeassistant.components.sensor.cpuspeed
|
# homeassistant.components.sensor.cpuspeed
|
||||||
py-cpuinfo==0.2.3
|
py-cpuinfo==0.2.3
|
||||||
|
|
||||||
|
# homeassistant.components.rfxtrx
|
||||||
|
pyRFXtrx==0.6.5
|
||||||
|
|
||||||
# homeassistant.components.media_player.cast
|
# homeassistant.components.media_player.cast
|
||||||
pychromecast==0.7.2
|
pychromecast==0.7.2
|
||||||
|
|
||||||
@@ -164,7 +176,7 @@ pydispatcher==2.0.5
|
|||||||
pyfttt==0.3
|
pyfttt==0.3
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.icloud
|
# homeassistant.components.device_tracker.icloud
|
||||||
pyicloud==0.7.2
|
pyicloud==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.netgear
|
# homeassistant.components.device_tracker.netgear
|
||||||
pynetgear==0.3.2
|
pynetgear==0.3.2
|
||||||
@@ -180,16 +192,16 @@ pyowm==2.3.0
|
|||||||
pysnmp==4.2.5
|
pysnmp==4.2.5
|
||||||
|
|
||||||
# homeassistant.components.sensor.forecast
|
# homeassistant.components.sensor.forecast
|
||||||
python-forecastio==1.3.3
|
python-forecastio==1.3.4
|
||||||
|
|
||||||
# homeassistant.components.media_player.mpd
|
# homeassistant.components.media_player.mpd
|
||||||
python-mpd2==0.5.4
|
python-mpd2==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==2.6.0
|
python-nest==2.6.0
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.nmap_tracker
|
# homeassistant.components.device_tracker.nmap_tracker
|
||||||
python-nmap==0.4.3
|
python-nmap==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.notify.pushover
|
# homeassistant.components.notify.pushover
|
||||||
python-pushover==0.2
|
python-pushover==0.2
|
||||||
@@ -198,7 +210,7 @@ python-pushover==0.2
|
|||||||
python-statsd==1.7.2
|
python-statsd==1.7.2
|
||||||
|
|
||||||
# homeassistant.components.notify.telegram
|
# homeassistant.components.notify.telegram
|
||||||
python-telegram-bot==3.2.0
|
python-telegram-bot==3.4
|
||||||
|
|
||||||
# homeassistant.components.sensor.twitch
|
# homeassistant.components.sensor.twitch
|
||||||
python-twitch==1.2.0
|
python-twitch==1.2.0
|
||||||
@@ -210,22 +222,23 @@ python-twitch==1.2.0
|
|||||||
# homeassistant.components.lock.wink
|
# homeassistant.components.lock.wink
|
||||||
# homeassistant.components.sensor.wink
|
# homeassistant.components.sensor.wink
|
||||||
# homeassistant.components.switch.wink
|
# homeassistant.components.switch.wink
|
||||||
python-wink==0.6.2
|
python-wink==0.6.4
|
||||||
|
|
||||||
# homeassistant.components.keyboard
|
# homeassistant.components.keyboard
|
||||||
pyuserinput==0.1.9
|
pyuserinput==0.1.9
|
||||||
|
|
||||||
# homeassistant.components.light.vera
|
# homeassistant.components.vera
|
||||||
# homeassistant.components.sensor.vera
|
|
||||||
# homeassistant.components.switch.vera
|
|
||||||
pyvera==0.2.8
|
pyvera==0.2.8
|
||||||
|
|
||||||
# homeassistant.components.wemo
|
# homeassistant.components.wemo
|
||||||
pywemo==0.3.12
|
pywemo==0.3.19
|
||||||
|
|
||||||
# homeassistant.components.thermostat.radiotherm
|
# homeassistant.components.thermostat.radiotherm
|
||||||
radiotherm==1.2
|
radiotherm==1.2
|
||||||
|
|
||||||
|
# homeassistant.components.media_player.yamaha
|
||||||
|
rxv==0.1.9
|
||||||
|
|
||||||
# homeassistant.components.media_player.samsungtv
|
# homeassistant.components.media_player.samsungtv
|
||||||
samsungctl==0.5.1
|
samsungctl==0.5.1
|
||||||
|
|
||||||
@@ -256,9 +269,8 @@ speedtest-cli==0.3.4
|
|||||||
# homeassistant.components.sensor.steam_online
|
# homeassistant.components.sensor.steam_online
|
||||||
steamodd==4.21
|
steamodd==4.21
|
||||||
|
|
||||||
# homeassistant.components.light.tellstick
|
# homeassistant.components.tellstick
|
||||||
# homeassistant.components.sensor.tellstick
|
# homeassistant.components.sensor.tellstick
|
||||||
# homeassistant.components.switch.tellstick
|
|
||||||
tellcore-py==1.1.2
|
tellcore-py==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.tellduslive
|
# homeassistant.components.tellduslive
|
||||||
@@ -278,7 +290,10 @@ urllib3
|
|||||||
uvcclient==0.8
|
uvcclient==0.8
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
# homeassistant.components.verisure
|
||||||
vsure==0.6.1
|
vsure==0.7.1
|
||||||
|
|
||||||
|
# homeassistant.components.switch.wake_on_lan
|
||||||
|
wakeonlan==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.zigbee
|
# homeassistant.components.zigbee
|
||||||
xbee-helper==0.0.6
|
xbee-helper==0.0.6
|
||||||
|
@@ -24,50 +24,6 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
""""Stop everything that was started."""
|
""""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_old_config_if_fires_on_event(self):
|
|
||||||
"""."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_on_event_with_data(self):
|
|
||||||
"""Test old configuration ."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'event_data': {'some_attr': 'some_value'},
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event', {'some_attr': 'some_value'})
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_not_fires_if_event_data_not_matches(self):
|
|
||||||
"""test old configuration."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'event_data': {'some_attr': 'some_value'},
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
def test_if_fires_on_event(self):
|
def test_if_fires_on_event(self):
|
||||||
"""Test the firing of events."""
|
"""Test the firing of events."""
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
@@ -24,71 +24,6 @@ class TestAutomation(unittest.TestCase):
|
|||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_old_config_service_data_not_a_dict(self):
|
|
||||||
"""Test old configuration service data."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'service_data': 100
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_service_specify_data(self):
|
|
||||||
"""Test old configuration service data."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'service_data': {'some': 'data'}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
self.assertEqual('data', self.calls[0].data['some'])
|
|
||||||
|
|
||||||
def test_old_config_service_specify_entity_id(self):
|
|
||||||
"""Test old configuration service data."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'service_entity_id': 'hello.world'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
self.assertEqual(['hello.world'],
|
|
||||||
self.calls[0].data.get(ATTR_ENTITY_ID))
|
|
||||||
|
|
||||||
def test_old_config_service_specify_entity_id_list(self):
|
|
||||||
"""Test old configuration service data."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'service_entity_id': ['hello.world', 'hello.world2']
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
self.assertEqual(['hello.world', 'hello.world2'],
|
|
||||||
self.calls[0].data.get(ATTR_ENTITY_ID))
|
|
||||||
|
|
||||||
def test_service_data_not_a_dict(self):
|
def test_service_data_not_a_dict(self):
|
||||||
"""Test service data not dict."""
|
"""Test service data not dict."""
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
|
@@ -24,50 +24,6 @@ class TestAutomationMQTT(unittest.TestCase):
|
|||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_old_config_if_fires_on_topic_match(self):
|
|
||||||
"""Test if message is fired on topic match."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'mqtt',
|
|
||||||
'mqtt_topic': 'test-topic',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_mqtt_message(self.hass, 'test-topic', '')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_on_topic_and_payload_match(self):
|
|
||||||
"""Test if message is fired on topic and payload match."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'mqtt',
|
|
||||||
'mqtt_topic': 'test-topic',
|
|
||||||
'mqtt_payload': 'hello',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_mqtt_message(self.hass, 'test-topic', 'hello')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_not_fires_on_topic_but_no_payload_match(self):
|
|
||||||
"""Test if message is not fired on topic but no payload."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'mqtt',
|
|
||||||
'mqtt_topic': 'test-topic',
|
|
||||||
'mqtt_payload': 'hello',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_mqtt_message(self.hass, 'test-topic', 'no-hello')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
def test_if_fires_on_topic_match(self):
|
def test_if_fires_on_topic_match(self):
|
||||||
"""Test if message is fired on topic match."""
|
"""Test if message is fired on topic match."""
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
@@ -28,143 +28,6 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_old_config_if_fires_on_entity_change(self):
|
|
||||||
"""Test for firing if entity change ."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_on_entity_change_with_from_filter(self):
|
|
||||||
"""Test for firing on entity change with filter."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'state_from': 'hello',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_on_entity_change_with_to_filter(self):
|
|
||||||
"""Test for firing on entity change no filter."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'state_to': 'world',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_on_entity_change_with_both_filters(self):
|
|
||||||
"""Test for firing on entity change with both filters."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'state_from': 'hello',
|
|
||||||
'state_to': 'world',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_not_fires_if_to_filter_not_match(self):
|
|
||||||
"""Test for not firing if no match."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'state_from': 'hello',
|
|
||||||
'state_to': 'world',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'moon')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_not_fires_if_from_filter_not_match(self):
|
|
||||||
"""Test for no firing if no match."""
|
|
||||||
self.hass.states.set('test.entity', 'bye')
|
|
||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.entity',
|
|
||||||
'state_from': 'hello',
|
|
||||||
'state_to': 'world',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_not_fires_if_entity_not_match(self):
|
|
||||||
"""Test for not firing if no match."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'state',
|
|
||||||
'state_entity_id': 'test.another_entity',
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_action(self):
|
|
||||||
"""Test for if action."""
|
|
||||||
entity_id = 'domain.test_entity'
|
|
||||||
test_state = 'new_state'
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'if': [{
|
|
||||||
'platform': 'state',
|
|
||||||
'entity_id': entity_id,
|
|
||||||
'state': test_state,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.states.set(entity_id, test_state)
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
self.hass.states.set(entity_id, test_state + 'something')
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_if_fires_on_entity_change(self):
|
def test_if_fires_on_entity_change(self):
|
||||||
"""Test for firing on entity change."""
|
"""Test for firing on entity change."""
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
@@ -5,8 +5,6 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.automation import time, event
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
|
||||||
|
|
||||||
from tests.common import fire_time_changed, get_test_home_assistant
|
from tests.common import fire_time_changed, get_test_home_assistant
|
||||||
|
|
||||||
@@ -28,207 +26,6 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_old_config_if_fires_when_hour_matches(self):
|
|
||||||
"""Test for firing if hours are matching."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'time',
|
|
||||||
time.CONF_HOURS: 0,
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_when_minute_matches(self):
|
|
||||||
"""Test for firing if minutes are matching."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'time',
|
|
||||||
time.CONF_MINUTES: 0,
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_when_second_matches(self):
|
|
||||||
"""Test for firing if seconds are matching."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'platform': 'time',
|
|
||||||
time.CONF_SECONDS: 0,
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_time_changed(self.hass, dt_util.utcnow().replace(second=0))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_fires_when_all_matches(self):
|
|
||||||
"""Test for firing if everything matches."""
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'time',
|
|
||||||
time.CONF_HOURS: 0,
|
|
||||||
time.CONF_MINUTES: 0,
|
|
||||||
time.CONF_SECONDS: 0,
|
|
||||||
'execute_service': 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
fire_time_changed(self.hass, dt_util.utcnow().replace(
|
|
||||||
hour=0, minute=0, second=0))
|
|
||||||
|
|
||||||
self.hass.states.set('test.entity', 'world')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_action_before(self):
|
|
||||||
"""Test for action before."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'event',
|
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'if': {
|
|
||||||
CONF_PLATFORM: 'time',
|
|
||||||
time.CONF_BEFORE: '10:00'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
before_10 = dt_util.now().replace(hour=8)
|
|
||||||
after_10 = dt_util.now().replace(hour=14)
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=before_10):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=after_10):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_action_after(self):
|
|
||||||
"""Test for action after."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'event',
|
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'if': {
|
|
||||||
CONF_PLATFORM: 'time',
|
|
||||||
time.CONF_AFTER: '10:00'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
before_10 = dt_util.now().replace(hour=8)
|
|
||||||
after_10 = dt_util.now().replace(hour=14)
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=before_10):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(0, len(self.calls))
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=after_10):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_action_one_weekday(self):
|
|
||||||
"""Test for action with one weekday."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'event',
|
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'if': {
|
|
||||||
CONF_PLATFORM: 'time',
|
|
||||||
time.CONF_WEEKDAY: 'mon',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
days_past_monday = dt_util.now().weekday()
|
|
||||||
monday = dt_util.now() - timedelta(days=days_past_monday)
|
|
||||||
tuesday = monday + timedelta(days=1)
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=monday):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=tuesday):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
def test_old_config_if_action_list_weekday(self):
|
|
||||||
"""Test for action with a list of weekdays."""
|
|
||||||
automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'event',
|
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
|
||||||
'execute_service': 'test.automation',
|
|
||||||
'if': {
|
|
||||||
CONF_PLATFORM: 'time',
|
|
||||||
time.CONF_WEEKDAY: ['mon', 'tue'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
days_past_monday = dt_util.now().weekday()
|
|
||||||
monday = dt_util.now() - timedelta(days=days_past_monday)
|
|
||||||
tuesday = monday + timedelta(days=1)
|
|
||||||
wednesday = tuesday + timedelta(days=1)
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=monday):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=tuesday):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(2, len(self.calls))
|
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
|
||||||
return_value=wednesday):
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(2, len(self.calls))
|
|
||||||
|
|
||||||
def test_if_fires_when_hour_matches(self):
|
def test_if_fires_when_hour_matches(self):
|
||||||
"""Test for firing if hour is matching."""
|
"""Test for firing if hour is matching."""
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
@@ -2,9 +2,12 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from homeassistant.const import EVENT_STATE_CHANGED
|
||||||
from homeassistant.components.binary_sensor import template
|
from homeassistant.components.binary_sensor import template
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
|
||||||
class TestBinarySensorTemplate(unittest.TestCase):
|
class TestBinarySensorTemplate(unittest.TestCase):
|
||||||
"""Test for Binary sensor template platform."""
|
"""Test for Binary sensor template platform."""
|
||||||
@@ -88,21 +91,19 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
def test_event(self):
|
def test_event(self):
|
||||||
""""Test the event."""
|
""""Test the event."""
|
||||||
hass = mock.MagicMock()
|
hass = get_test_home_assistant()
|
||||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
||||||
'motion', '{{ 1 > 1 }}')
|
'motion', '{{ 1 > 1 }}')
|
||||||
with mock.patch.object(vs, 'update_ha_state') as mock_update:
|
vs.update_ha_state()
|
||||||
vs._event_listener(None)
|
hass.pool.block_till_done()
|
||||||
mock_update.assert_called_once_with(True)
|
|
||||||
|
|
||||||
def test_update(self):
|
with mock.patch.object(vs, 'update') as mock_update:
|
||||||
""""Test the update."""
|
hass.bus.fire(EVENT_STATE_CHANGED)
|
||||||
hass = mock.MagicMock()
|
hass.pool.block_till_done()
|
||||||
vs = template.BinarySensorTemplate(hass, 'parent', 'Parent',
|
try:
|
||||||
'motion', '{{ 2 > 1 }}')
|
assert mock_update.call_count == 1
|
||||||
self.assertEqual(None, vs._state)
|
finally:
|
||||||
vs.update()
|
hass.stop()
|
||||||
self.assertTrue(vs._state)
|
|
||||||
|
|
||||||
@mock.patch('homeassistant.helpers.template.render')
|
@mock.patch('homeassistant.helpers.template.render')
|
||||||
def test_update_template_error(self, mock_render):
|
def test_update_template_error(self, mock_render):
|
||||||
|
@@ -133,15 +133,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
|
|||||||
'radius': 100000
|
'radius': 100000
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hass.states.set(
|
|
||||||
'zone.passive', 'zoning',
|
|
||||||
{
|
|
||||||
'name': 'zone',
|
|
||||||
'latitude': 3.0,
|
|
||||||
'longitude': 1.0,
|
|
||||||
'radius': 10,
|
|
||||||
'passive': True
|
|
||||||
})
|
|
||||||
# Clear state between teste
|
# Clear state between teste
|
||||||
self.hass.states.set(DEVICE_TRACKER_STATE, None)
|
self.hass.states.set(DEVICE_TRACKER_STATE, None)
|
||||||
owntracks.REGIONS_ENTERED = defaultdict(list)
|
owntracks.REGIONS_ENTERED = defaultdict(list)
|
||||||
@@ -325,43 +316,6 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase):
|
|||||||
self.send_message(EVENT_TOPIC, message)
|
self.send_message(EVENT_TOPIC, message)
|
||||||
self.assert_location_state('outer')
|
self.assert_location_state('outer')
|
||||||
|
|
||||||
def test_event_entry_exit_passive_zone(self):
|
|
||||||
"""Test the event for passive zone exits."""
|
|
||||||
# Enter passive zone
|
|
||||||
message = REGION_ENTER_MESSAGE.copy()
|
|
||||||
message['desc'] = "passive"
|
|
||||||
self.send_message(EVENT_TOPIC, message)
|
|
||||||
|
|
||||||
# Should pick up gps put not zone
|
|
||||||
self.assert_location_state('not_home')
|
|
||||||
self.assert_location_latitude(3.0)
|
|
||||||
self.assert_location_accuracy(10.0)
|
|
||||||
|
|
||||||
# Enter inner2 zone
|
|
||||||
message = REGION_ENTER_MESSAGE.copy()
|
|
||||||
message['desc'] = "inner_2"
|
|
||||||
self.send_message(EVENT_TOPIC, message)
|
|
||||||
self.assert_location_state('inner_2')
|
|
||||||
self.assert_location_latitude(2.1)
|
|
||||||
self.assert_location_accuracy(10.0)
|
|
||||||
|
|
||||||
# Exit inner_2 - should be in 'passive'
|
|
||||||
# ie gps co-ords - but not zone
|
|
||||||
message = REGION_LEAVE_MESSAGE.copy()
|
|
||||||
message['desc'] = "inner_2"
|
|
||||||
self.send_message(EVENT_TOPIC, message)
|
|
||||||
self.assert_location_state('not_home')
|
|
||||||
self.assert_location_latitude(3.0)
|
|
||||||
self.assert_location_accuracy(10.0)
|
|
||||||
|
|
||||||
# Exit passive - should be in 'outer'
|
|
||||||
message = REGION_LEAVE_MESSAGE.copy()
|
|
||||||
message['desc'] = "passive"
|
|
||||||
self.send_message(EVENT_TOPIC, message)
|
|
||||||
self.assert_location_state('outer')
|
|
||||||
self.assert_location_latitude(2.0)
|
|
||||||
self.assert_location_accuracy(60.0)
|
|
||||||
|
|
||||||
def test_event_entry_unknown_zone(self):
|
def test_event_entry_unknown_zone(self):
|
||||||
"""Test the event for unknown zone."""
|
"""Test the event for unknown zone."""
|
||||||
# Just treat as location update
|
# Just treat as location update
|
||||||
|
@@ -5,12 +5,9 @@ from homeassistant.components import rfxtrx as rfxtrx_core
|
|||||||
from homeassistant.components.light import rfxtrx
|
from homeassistant.components.light import rfxtrx
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Does not clean up properly, takes 100% CPU')
|
|
||||||
class TestLightRfxtrx(unittest.TestCase):
|
class TestLightRfxtrx(unittest.TestCase):
|
||||||
"""Test the Rfxtrx light platform."""
|
"""Test the Rfxtrx light platform."""
|
||||||
|
|
||||||
@@ -22,6 +19,8 @@ class TestLightRfxtrx(unittest.TestCase):
|
|||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS = []
|
rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS = []
|
||||||
rfxtrx_core.RFX_DEVICES = {}
|
rfxtrx_core.RFX_DEVICES = {}
|
||||||
|
if rfxtrx_core.RFXOBJECT:
|
||||||
|
rfxtrx_core.RFXOBJECT.close_connection()
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_default_config(self):
|
def test_default_config(self):
|
||||||
|
@@ -25,6 +25,11 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
assert 1.0 == state.attributes.get('volume_level')
|
assert 1.0 == state.attributes.get('volume_level')
|
||||||
|
|
||||||
|
mp.set_volume_level(self.hass, None, entity_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
assert 1.0 == state.attributes.get('volume_level')
|
||||||
|
|
||||||
mp.set_volume_level(self.hass, 0.5, entity_id)
|
mp.set_volume_level(self.hass, 0.5, entity_id)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
@@ -41,6 +46,12 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
assert 0.5 == state.attributes.get('volume_level')
|
assert 0.5 == state.attributes.get('volume_level')
|
||||||
|
|
||||||
assert False is state.attributes.get('is_volume_muted')
|
assert False is state.attributes.get('is_volume_muted')
|
||||||
|
|
||||||
|
mp.mute_volume(self.hass, None, entity_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
assert False is state.attributes.get('is_volume_muted')
|
||||||
|
|
||||||
mp.mute_volume(self.hass, True, entity_id)
|
mp.mute_volume(self.hass, True, entity_id)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
@@ -87,7 +98,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
assert self.hass.states.is_state(entity_id, 'playing')
|
assert self.hass.states.is_state(entity_id, 'playing')
|
||||||
|
|
||||||
def test_prev_next_track(self):
|
def test_prev_next_track(self):
|
||||||
"""Test media_next_track and media_prevoius_track ."""
|
"""Test media_next_track and media_previous_track ."""
|
||||||
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
assert 1 == state.attributes.get('media_track')
|
assert 1 == state.attributes.get('media_track')
|
||||||
@@ -115,6 +126,27 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
|
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
|
||||||
state.attributes.get('supported_media_commands'))
|
state.attributes.get('supported_media_commands'))
|
||||||
|
|
||||||
|
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||||
|
ent_id = 'media_player.lounge_room'
|
||||||
|
state = self.hass.states.get(ent_id)
|
||||||
|
assert 1 == state.attributes.get('media_episode')
|
||||||
|
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
|
||||||
|
state.attributes.get('supported_media_commands'))
|
||||||
|
|
||||||
|
mp.media_next_track(self.hass, ent_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get(ent_id)
|
||||||
|
assert 2 == state.attributes.get('media_episode')
|
||||||
|
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
|
||||||
|
state.attributes.get('supported_media_commands'))
|
||||||
|
|
||||||
|
mp.media_previous_track(self.hass, ent_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get(ent_id)
|
||||||
|
assert 1 == state.attributes.get('media_episode')
|
||||||
|
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
|
||||||
|
state.attributes.get('supported_media_commands'))
|
||||||
|
|
||||||
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.'
|
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.'
|
||||||
'media_seek')
|
'media_seek')
|
||||||
def test_play_media(self, mock_seek):
|
def test_play_media(self, mock_seek):
|
||||||
@@ -126,6 +158,13 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
state.attributes.get('supported_media_commands'))
|
state.attributes.get('supported_media_commands'))
|
||||||
assert state.attributes.get('media_content_id') is not None
|
assert state.attributes.get('media_content_id') is not None
|
||||||
|
|
||||||
|
mp.play_media(self.hass, None, 'some_id', ent_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get(ent_id)
|
||||||
|
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
|
||||||
|
state.attributes.get('supported_media_commands'))
|
||||||
|
assert not 'some_id' == state.attributes.get('media_content_id')
|
||||||
|
|
||||||
mp.play_media(self.hass, 'youtube', 'some_id', ent_id)
|
mp.play_media(self.hass, 'youtube', 'some_id', ent_id)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
state = self.hass.states.get(ent_id)
|
state = self.hass.states.get(ent_id)
|
||||||
@@ -133,6 +172,9 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
state.attributes.get('supported_media_commands'))
|
state.attributes.get('supported_media_commands'))
|
||||||
assert 'some_id' == state.attributes.get('media_content_id')
|
assert 'some_id' == state.attributes.get('media_content_id')
|
||||||
|
|
||||||
|
assert not mock_seek.called
|
||||||
|
mp.media_seek(self.hass, None, ent_id)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
assert not mock_seek.called
|
assert not mock_seek.called
|
||||||
mp.media_seek(self.hass, 100, ent_id)
|
mp.media_seek(self.hass, 100, ent_id)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
@@ -25,8 +25,37 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||||||
self._media_title = None
|
self._media_title = None
|
||||||
self._supported_media_commands = 0
|
self._supported_media_commands = 0
|
||||||
|
|
||||||
self.turn_off_service_calls = mock_service(
|
self.service_calls = {
|
||||||
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF)
|
'turn_on': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_TURN_ON),
|
||||||
|
'turn_off': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF),
|
||||||
|
'mute_volume': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_MUTE),
|
||||||
|
'set_volume_level': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET),
|
||||||
|
'media_play': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY),
|
||||||
|
'media_pause': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE),
|
||||||
|
'media_previous_track': mock_service(
|
||||||
|
hass, media_player.DOMAIN,
|
||||||
|
media_player.SERVICE_MEDIA_PREVIOUS_TRACK),
|
||||||
|
'media_next_track': mock_service(
|
||||||
|
hass, media_player.DOMAIN,
|
||||||
|
media_player.SERVICE_MEDIA_NEXT_TRACK),
|
||||||
|
'media_seek': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK),
|
||||||
|
'play_media': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_PLAY_MEDIA),
|
||||||
|
'volume_up': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_UP),
|
||||||
|
'volume_down': mock_service(
|
||||||
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_DOWN),
|
||||||
|
'media_play_pause': mock_service(
|
||||||
|
hass, media_player.DOMAIN,
|
||||||
|
media_player.SERVICE_MEDIA_PLAY_PAUSE),
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -97,23 +126,26 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state')
|
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state')
|
||||||
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
|
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
|
||||||
|
|
||||||
self.config_children_only = \
|
self.config_children_only = {
|
||||||
{'name': 'test', 'platform': 'universal',
|
'name': 'test', 'platform': 'universal',
|
||||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||||
media_player.ENTITY_ID_FORMAT.format('mock2')]}
|
media_player.ENTITY_ID_FORMAT.format('mock2')]
|
||||||
self.config_children_and_attr = \
|
}
|
||||||
{'name': 'test', 'platform': 'universal',
|
self.config_children_and_attr = {
|
||||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
'name': 'test', 'platform': 'universal',
|
||||||
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||||
'attributes': {
|
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
||||||
'is_volume_muted': self.mock_mute_switch_id,
|
'attributes': {
|
||||||
'state': self.mock_state_switch_id}}
|
'is_volume_muted': self.mock_mute_switch_id,
|
||||||
|
'state': self.mock_state_switch_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_check_config_children_only(self):
|
def test_config_children_only(self):
|
||||||
"""Check config with only children."""
|
"""Check config with only children."""
|
||||||
config_start = copy(self.config_children_only)
|
config_start = copy(self.config_children_only)
|
||||||
del config_start['platform']
|
del config_start['platform']
|
||||||
@@ -125,7 +157,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual(config_start, self.config_children_only)
|
self.assertEqual(config_start, self.config_children_only)
|
||||||
|
|
||||||
def test_check_config_children_and_attr(self):
|
def test_config_children_and_attr(self):
|
||||||
"""Check config with children and attributes."""
|
"""Check config with children and attributes."""
|
||||||
config_start = copy(self.config_children_and_attr)
|
config_start = copy(self.config_children_and_attr)
|
||||||
del config_start['platform']
|
del config_start['platform']
|
||||||
@@ -136,13 +168,13 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual(config_start, self.config_children_and_attr)
|
self.assertEqual(config_start, self.config_children_and_attr)
|
||||||
|
|
||||||
def test_check_config_no_name(self):
|
def test_config_no_name(self):
|
||||||
"""Check config with no Name entry."""
|
"""Check config with no Name entry."""
|
||||||
response = universal.validate_config({'platform': 'universal'})
|
response = universal.validate_config({'platform': 'universal'})
|
||||||
|
|
||||||
self.assertFalse(response)
|
self.assertFalse(response)
|
||||||
|
|
||||||
def test_check_config_bad_children(self):
|
def test_config_bad_children(self):
|
||||||
"""Check config with bad children entry."""
|
"""Check config with bad children entry."""
|
||||||
config_no_children = {'name': 'test', 'platform': 'universal'}
|
config_no_children = {'name': 'test', 'platform': 'universal'}
|
||||||
config_bad_children = {'name': 'test', 'children': {},
|
config_bad_children = {'name': 'test', 'children': {},
|
||||||
@@ -156,7 +188,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual([], config_bad_children['children'])
|
self.assertEqual([], config_bad_children['children'])
|
||||||
|
|
||||||
def test_check_config_bad_commands(self):
|
def test_config_bad_commands(self):
|
||||||
"""Check config with bad commands entry."""
|
"""Check config with bad commands entry."""
|
||||||
config = {'name': 'test', 'commands': [], 'platform': 'universal'}
|
config = {'name': 'test', 'commands': [], 'platform': 'universal'}
|
||||||
|
|
||||||
@@ -164,7 +196,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual({}, config['commands'])
|
self.assertEqual({}, config['commands'])
|
||||||
|
|
||||||
def test_check_config_bad_attributes(self):
|
def test_config_bad_attributes(self):
|
||||||
"""Check config with bad attributes."""
|
"""Check config with bad attributes."""
|
||||||
config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
|
config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
|
||||||
|
|
||||||
@@ -172,7 +204,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual({}, config['attributes'])
|
self.assertEqual({}, config['attributes'])
|
||||||
|
|
||||||
def test_check_config_bad_key(self):
|
def test_config_bad_key(self):
|
||||||
"""Check config with bad key."""
|
"""Check config with bad key."""
|
||||||
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
|
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
|
||||||
|
|
||||||
@@ -183,6 +215,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
def test_platform_setup(self):
|
def test_platform_setup(self):
|
||||||
"""Test platform setup."""
|
"""Test platform setup."""
|
||||||
config = {'name': 'test', 'platform': 'universal'}
|
config = {'name': 'test', 'platform': 'universal'}
|
||||||
|
bad_config = {'platform': 'universal'}
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
def add_devices(new_entities):
|
def add_devices(new_entities):
|
||||||
@@ -190,8 +223,10 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
for dev in new_entities:
|
for dev in new_entities:
|
||||||
entities.append(dev)
|
entities.append(dev)
|
||||||
|
|
||||||
universal.setup_platform(self.hass, config, add_devices)
|
universal.setup_platform(self.hass, bad_config, add_devices)
|
||||||
|
self.assertEqual(0, len(entities))
|
||||||
|
|
||||||
|
universal.setup_platform(self.hass, config, add_devices)
|
||||||
self.assertEqual(1, len(entities))
|
self.assertEqual(1, len(entities))
|
||||||
self.assertEqual('test', entities[0].name)
|
self.assertEqual('test', entities[0].name)
|
||||||
|
|
||||||
@@ -263,6 +298,15 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(config['name'], ump.name)
|
self.assertEqual(config['name'], ump.name)
|
||||||
|
|
||||||
|
def test_polling(self):
|
||||||
|
"""Test should_poll property."""
|
||||||
|
config = self.config_children_only
|
||||||
|
universal.validate_config(config)
|
||||||
|
|
||||||
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
||||||
|
|
||||||
|
self.assertEqual(False, ump.should_poll)
|
||||||
|
|
||||||
def test_state_children_only(self):
|
def test_state_children_only(self):
|
||||||
"""Test media player state with only children."""
|
"""Test media player state with only children."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
@@ -388,8 +432,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
||||||
ump.update()
|
ump.update()
|
||||||
|
|
||||||
self.mock_mp_1._supported_media_commands = \
|
self.mock_mp_1._supported_media_commands = universal.SUPPORT_VOLUME_SET
|
||||||
universal.SUPPORT_VOLUME_SET
|
|
||||||
self.mock_mp_1._state = STATE_PLAYING
|
self.mock_mp_1._state = STATE_PLAYING
|
||||||
self.mock_mp_1.update_ha_state()
|
self.mock_mp_1.update_ha_state()
|
||||||
ump.update()
|
ump.update()
|
||||||
@@ -400,7 +443,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertEqual(check_flags, ump.supported_media_commands)
|
self.assertEqual(check_flags, ump.supported_media_commands)
|
||||||
|
|
||||||
def test_service_call_to_child(self):
|
def test_service_call_to_child(self):
|
||||||
"""Test a service call that should be routed to a child."""
|
"""Test service calls that should be routed to a child."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
universal.validate_config(config)
|
universal.validate_config(config)
|
||||||
|
|
||||||
@@ -413,13 +456,53 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
ump.update()
|
ump.update()
|
||||||
|
|
||||||
ump.turn_off()
|
ump.turn_off()
|
||||||
self.assertEqual(1, len(self.mock_mp_2.turn_off_service_calls))
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_off']))
|
||||||
|
|
||||||
|
ump.turn_on()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_on']))
|
||||||
|
|
||||||
|
ump.mute_volume(True)
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['mute_volume']))
|
||||||
|
|
||||||
|
ump.set_volume_level(0.5)
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['set_volume_level']))
|
||||||
|
|
||||||
|
ump.media_play()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_play']))
|
||||||
|
|
||||||
|
ump.media_pause()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_pause']))
|
||||||
|
|
||||||
|
ump.media_previous_track()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_previous_track']))
|
||||||
|
|
||||||
|
ump.media_next_track()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_next_track']))
|
||||||
|
|
||||||
|
ump.media_seek(100)
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_seek']))
|
||||||
|
|
||||||
|
ump.play_media('movie', 'batman')
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['play_media']))
|
||||||
|
|
||||||
|
ump.volume_up()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_up']))
|
||||||
|
|
||||||
|
ump.volume_down()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_down']))
|
||||||
|
|
||||||
|
ump.media_play_pause()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_play_pause']))
|
||||||
|
|
||||||
def test_service_call_to_command(self):
|
def test_service_call_to_command(self):
|
||||||
"""Test service call to command."""
|
"""Test service call to command."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
config['commands'] = \
|
config['commands'] = {'turn_off': {
|
||||||
{'turn_off': {'service': 'test.turn_off', 'data': {}}}
|
'service': 'test.turn_off', 'data': {}}}
|
||||||
universal.validate_config(config)
|
universal.validate_config(config)
|
||||||
|
|
||||||
service = mock_service(self.hass, 'test', 'turn_off')
|
service = mock_service(self.hass, 'test', 'turn_off')
|
||||||
|
@@ -44,10 +44,6 @@ class TestMQTT(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertTrue(mqtt.MQTT_CLIENT.stop.called)
|
self.assertTrue(mqtt.MQTT_CLIENT.stop.called)
|
||||||
|
|
||||||
def test_setup_fails_if_no_broker_config(self):
|
|
||||||
"""Test for setup failure if broker configuration is missing."""
|
|
||||||
self.assertFalse(mqtt.setup(self.hass, {mqtt.DOMAIN: {}}))
|
|
||||||
|
|
||||||
def test_setup_fails_if_no_connect_broker(self):
|
def test_setup_fails_if_no_connect_broker(self):
|
||||||
"""Test for setup failure if connection to broker is missing."""
|
"""Test for setup failure if connection to broker is missing."""
|
||||||
with mock.patch('homeassistant.components.mqtt.MQTT',
|
with mock.patch('homeassistant.components.mqtt.MQTT',
|
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 tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from homeassistant import core
|
|
||||||
import homeassistant.components.notify as notify
|
import homeassistant.components.notify as notify
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
@@ -13,12 +15,27 @@ class TestCommandLine(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = core.HomeAssistant()
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
"""Stop down everything that was started."""
|
"""Stop down everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_bad_config(self):
|
||||||
|
"""Test set up the platform with bad/missing config."""
|
||||||
|
self.assertFalse(notify.setup(self.hass, {
|
||||||
|
'notify': {
|
||||||
|
'name': 'test',
|
||||||
|
'platform': 'bad_platform',
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
self.assertFalse(notify.setup(self.hass, {
|
||||||
|
'notify': {
|
||||||
|
'name': 'test',
|
||||||
|
'platform': 'command_line',
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
def test_command_line_output(self):
|
def test_command_line_output(self):
|
||||||
"""Test the command line output."""
|
"""Test the command line output."""
|
||||||
with tempfile.TemporaryDirectory() as tempdirname:
|
with tempfile.TemporaryDirectory() as tempdirname:
|
||||||
@@ -41,7 +58,7 @@ class TestCommandLine(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('homeassistant.components.notify.command_line._LOGGER.error')
|
@patch('homeassistant.components.notify.command_line._LOGGER.error')
|
||||||
def test_error_for_none_zero_exit_code(self, mock_error):
|
def test_error_for_none_zero_exit_code(self, mock_error):
|
||||||
"""Test if an error if logged for non zero exit codes."""
|
"""Test if an error is logged for non zero exit codes."""
|
||||||
self.assertTrue(notify.setup(self.hass, {
|
self.assertTrue(notify.setup(self.hass, {
|
||||||
'notify': {
|
'notify': {
|
||||||
'name': 'test',
|
'name': 'test',
|
||||||
|
@@ -30,6 +30,12 @@ class TestNotifyDemo(unittest.TestCase):
|
|||||||
""""Stop down everything that was started."""
|
""""Stop down everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_sending_none_message(self):
|
||||||
|
"""Test send with None as message."""
|
||||||
|
notify.send_message(self.hass, None)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertTrue(len(self.events) == 0)
|
||||||
|
|
||||||
def test_sending_templated_message(self):
|
def test_sending_templated_message(self):
|
||||||
"""Send a templated message."""
|
"""Send a templated message."""
|
||||||
self.hass.states.set('sensor.temperature', 10)
|
self.hass.states.set('sensor.temperature', 10)
|
||||||
|
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.states.set('sensor.test_state', 'Works')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
state = self.hass.states.get('sensor.test_template_sensor')
|
state = self.hass.states.get('sensor.test_template_sensor')
|
||||||
assert state.state == 'error'
|
assert state.state == 'unknown'
|
||||||
|
|
||||||
def test_template_attribute_missing(self):
|
def test_template_attribute_missing(self):
|
||||||
"""Test missing attribute template."""
|
"""Test missing attribute template."""
|
||||||
@@ -71,7 +71,7 @@ class TestTemplateSensor:
|
|||||||
})
|
})
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.test_template_sensor')
|
state = self.hass.states.get('sensor.test_template_sensor')
|
||||||
assert state.state == 'error'
|
assert state.state == 'unknown'
|
||||||
|
|
||||||
def test_invalid_name_does_not_create(self):
|
def test_invalid_name_does_not_create(self):
|
||||||
"""Test invalid name."""
|
"""Test invalid name."""
|
||||||
|
@@ -6,6 +6,7 @@ import unittest
|
|||||||
|
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
import homeassistant.components.switch as switch
|
import homeassistant.components.switch as switch
|
||||||
|
import homeassistant.components.switch.command_line as command_line
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
@@ -155,3 +156,21 @@ class TestCommandSwitch(unittest.TestCase):
|
|||||||
|
|
||||||
state = self.hass.states.get('switch.test')
|
state = self.hass.states.get('switch.test')
|
||||||
self.assertEqual(STATE_ON, state.state)
|
self.assertEqual(STATE_ON, state.state)
|
||||||
|
|
||||||
|
def test_assumed_state_should_be_true_if_command_state_is_false(self):
|
||||||
|
"""Test with state value."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
# Set state command to false
|
||||||
|
statecmd = False
|
||||||
|
|
||||||
|
no_state_device = command_line.CommandSwitch(self.hass, "Test", "echo",
|
||||||
|
"echo", statecmd, None)
|
||||||
|
self.assertTrue(no_state_device.assumed_state)
|
||||||
|
|
||||||
|
# Set state command
|
||||||
|
statecmd = 'cat {}'
|
||||||
|
|
||||||
|
state_device = command_line.CommandSwitch(self.hass, "Test", "echo",
|
||||||
|
"echo", statecmd, None)
|
||||||
|
self.assertFalse(state_device.assumed_state)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user