mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
commit
763a9ce8c6
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."""
|
||||||
|
|
||||||
def _event_listener(self, event):
|
|
||||||
if not hasattr(self, 'hass'):
|
|
||||||
return
|
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
@property
|
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||||
def should_poll(self):
|
template_bsensor_event_listener)
|
||||||
"""No polling needed."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sensor_class(self):
|
|
||||||
"""Return the sensor class of the sensor."""
|
|
||||||
return self._sensor_class
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
"""Return true if sensor is on."""
|
"""Return true if sensor is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_class(self):
|
||||||
|
"""Return the sensor class of the sensor."""
|
||||||
|
return self._sensor_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No polling needed."""
|
||||||
|
return False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data and update the state."""
|
"""Get the latest data and update the state."""
|
||||||
try:
|
try:
|
||||||
value = template.render(self._hass, self._template)
|
self._state = template.render(self.hass,
|
||||||
|
self._template).lower() == 'true'
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
if ex.args and ex.args[0].startswith(
|
if ex.args and ex.args[0].startswith(
|
||||||
"UndefinedError: 'None' has no attribute"):
|
"UndefinedError: 'None' has no attribute"):
|
||||||
@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
_LOGGER.warning(ex)
|
_LOGGER.warning(ex)
|
||||||
return
|
return
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
value = 'false'
|
self._state = False
|
||||||
self._state = value.lower() == 'true'
|
|
||||||
|
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
@ -1 +1 @@
|
|||||||
Subproject commit 0fed700045d6faba8eda8ec713ee9e6bc763507c
|
Subproject commit a4217e0f620366e47dde82f7ce2d8f7b2bb6a079
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.components.garage_door import GarageDoorDevice
|
from homeassistant.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,6 +217,10 @@ class HueLight(Light):
|
|||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
self.update_lights()
|
self.update_lights()
|
||||||
|
|
||||||
|
if self.allow_unreachable:
|
||||||
|
return self.info['state']['on']
|
||||||
|
else:
|
||||||
return self.info['state']['reachable'] and self.info['state']['on']
|
return self.info['state']['reachable'] and self.info['state']['on']
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
@ -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):
|
||||||
|
@ -50,7 +50,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
self._server = jsonrpc_requests.Server(
|
self._server = jsonrpc_requests.Server(
|
||||||
'{}/jsonrpc'.format(self._url),
|
'{}/jsonrpc'.format(self._url),
|
||||||
auth=auth)
|
auth=auth)
|
||||||
self._players = None
|
self._players = list()
|
||||||
self._properties = None
|
self._properties = None
|
||||||
self._item = None
|
self._item = None
|
||||||
self._app_properties = None
|
self._app_properties = None
|
||||||
@ -67,6 +67,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
try:
|
try:
|
||||||
return self._server.Player.GetActivePlayers()
|
return self._server.Player.GetActivePlayers()
|
||||||
except jsonrpc_requests.jsonrpc.TransportError:
|
except jsonrpc_requests.jsonrpc.TransportError:
|
||||||
|
if self._players is not None:
|
||||||
_LOGGER.warning('Unable to fetch kodi data')
|
_LOGGER.warning('Unable to fetch kodi data')
|
||||||
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
@ -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,17 +93,46 @@ def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
|
|||||||
MQTT_CLIENT.subscribe(topic, qos)
|
MQTT_CLIENT.subscribe(topic, qos)
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_server(hass, config):
|
||||||
|
"""Try to start embedded MQTT broker."""
|
||||||
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
|
# Only setup if embedded config passed in or no broker specified
|
||||||
|
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server = prepare_setup_platform(hass, config, DOMAIN, 'server')
|
||||||
|
|
||||||
|
if server is None:
|
||||||
|
_LOGGER.error('Unable to load embedded server.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
success, broker_config = server.start(hass, conf.get(CONF_EMBEDDED))
|
||||||
|
|
||||||
|
return success and broker_config
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Start the MQTT protocol service."""
|
"""Start the MQTT protocol service."""
|
||||||
if not validate_config(config, {DOMAIN: ['broker']}, _LOGGER):
|
# pylint: disable=too-many-locals
|
||||||
return False
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
|
||||||
|
|
||||||
broker = conf[CONF_BROKER]
|
|
||||||
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
|
||||||
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
|
client_id = util.convert(conf.get(CONF_CLIENT_ID), str)
|
||||||
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
keepalive = util.convert(conf.get(CONF_KEEPALIVE), int, DEFAULT_KEEPALIVE)
|
||||||
|
|
||||||
|
broker_config = _setup_server(hass, config)
|
||||||
|
|
||||||
|
# Only auto config if no server config was passed in
|
||||||
|
if broker_config and CONF_EMBEDDED not in conf:
|
||||||
|
broker, port, username, password, certificate, protocol = broker_config
|
||||||
|
elif not broker_config and (CONF_EMBEDDED in conf or
|
||||||
|
CONF_BROKER not in conf):
|
||||||
|
_LOGGER.error('Unable to start broker and auto-configure MQTT.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if CONF_BROKER in conf:
|
||||||
|
broker = conf[CONF_BROKER]
|
||||||
|
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||||
username = util.convert(conf.get(CONF_USERNAME), str)
|
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||||
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
|
certificate = util.convert(conf.get(CONF_CERTIFICATE), str)
|
||||||
|
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(
|
||||||
|
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,22 +67,22 @@ class SensorTemplate(Entity):
|
|||||||
def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
|
def __init__(self, hass, device_id, friendly_name, unit_of_measurement,
|
||||||
state_template):
|
state_template):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
|
|
||||||
self.hass = hass
|
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._template = state_template
|
self._template = state_template
|
||||||
self.update()
|
self._state = None
|
||||||
self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
|
||||||
|
|
||||||
def _event_listener(self, event):
|
self.update()
|
||||||
|
|
||||||
|
def template_sensor_event_listener(event):
|
||||||
"""Called when the target device changes state."""
|
"""Called when the target device changes state."""
|
||||||
if not hasattr(self, 'hass'):
|
|
||||||
return
|
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED, template_sensor_event_listener)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
@ -111,10 +108,10 @@ class SensorTemplate(Entity):
|
|||||||
try:
|
try:
|
||||||
self._state = template.render(self.hass, self._template)
|
self._state = template.render(self.hass, self._template)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
self._state = STATE_ERROR
|
|
||||||
if ex.args and ex.args[0].startswith(
|
if ex.args and ex.args[0].startswith(
|
||||||
"UndefinedError: 'None' has no attribute"):
|
"UndefinedError: 'None' has no attribute"):
|
||||||
# Common during HA startup - so just a warning
|
# Common during HA startup - so just a warning
|
||||||
_LOGGER.warning(ex)
|
_LOGGER.warning(ex)
|
||||||
return
|
return
|
||||||
|
self._state = None
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
|
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()
|
||||||
|
|
||||||
|
def template_switch_event_listener(event):
|
||||||
"""Called when the target device changes state."""
|
"""Called when the target device changes state."""
|
||||||
if not hasattr(self, 'hass'):
|
|
||||||
return
|
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||||
|
template_switch_event_listener)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the switch."""
|
"""Return the name of the switch."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""If switch is available."""
|
||||||
|
return self._state is not None
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Fire the on action."""
|
"""Fire the on action."""
|
||||||
call_from_config(self.hass, self._on_action, True)
|
call_from_config(self.hass, self._on_action, True)
|
||||||
@ -118,30 +127,19 @@ class SwitchTemplate(SwitchDevice):
|
|||||||
"""Fire the off action."""
|
"""Fire the off action."""
|
||||||
call_from_config(self.hass, self._off_action, True)
|
call_from_config(self.hass, self._off_action, True)
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if device is on."""
|
|
||||||
return self._value.lower() == 'true' or self._value == STATE_ON
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_off(self):
|
|
||||||
"""Return true if device is off."""
|
|
||||||
return self._value.lower() == 'false' or self._value == STATE_OFF
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""Return true if entity is available."""
|
|
||||||
return self.is_on or self.is_off
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state from the template."""
|
"""Update the state from the template."""
|
||||||
try:
|
try:
|
||||||
self._value = template.render(self.hass, self._template)
|
state = template.render(self.hass, self._template).lower()
|
||||||
if not self.available:
|
|
||||||
|
if state in _VALID_STATES:
|
||||||
|
self._state = state in ('true', STATE_ON)
|
||||||
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"`%s` is not a switch state, setting %s to unavailable",
|
'Received invalid switch is_on state: %s. Expected: %s',
|
||||||
self._value, self.entity_id)
|
state, ', '.join(_VALID_STATES))
|
||||||
|
self._state = None
|
||||||
|
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
self._value = STATE_ERROR
|
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
|
self._state = None
|
||||||
|
@ -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 = {
|
||||||
|
'name': 'test', 'platform': 'universal',
|
||||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||||
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
media_player.ENTITY_ID_FORMAT.format('mock2')],
|
||||||
'attributes': {
|
'attributes': {
|
||||||
'is_volume_muted': self.mock_mute_switch_id,
|
'is_volume_muted': self.mock_mute_switch_id,
|
||||||
'state': self.mock_state_switch_id}}
|
'state': self.mock_state_switch_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_check_config_children_only(self):
|
def test_config_children_only(self):
|
||||||
"""Check config with only children."""
|
"""Check config with only children."""
|
||||||
config_start = copy(self.config_children_only)
|
config_start = copy(self.config_children_only)
|
||||||
del config_start['platform']
|
del config_start['platform']
|
||||||
@ -125,7 +157,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual(config_start, self.config_children_only)
|
self.assertEqual(config_start, self.config_children_only)
|
||||||
|
|
||||||
def test_check_config_children_and_attr(self):
|
def test_config_children_and_attr(self):
|
||||||
"""Check config with children and attributes."""
|
"""Check config with children and attributes."""
|
||||||
config_start = copy(self.config_children_and_attr)
|
config_start = copy(self.config_children_and_attr)
|
||||||
del config_start['platform']
|
del config_start['platform']
|
||||||
@ -136,13 +168,13 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual(config_start, self.config_children_and_attr)
|
self.assertEqual(config_start, self.config_children_and_attr)
|
||||||
|
|
||||||
def test_check_config_no_name(self):
|
def test_config_no_name(self):
|
||||||
"""Check config with no Name entry."""
|
"""Check config with no Name entry."""
|
||||||
response = universal.validate_config({'platform': 'universal'})
|
response = universal.validate_config({'platform': 'universal'})
|
||||||
|
|
||||||
self.assertFalse(response)
|
self.assertFalse(response)
|
||||||
|
|
||||||
def test_check_config_bad_children(self):
|
def test_config_bad_children(self):
|
||||||
"""Check config with bad children entry."""
|
"""Check config with bad children entry."""
|
||||||
config_no_children = {'name': 'test', 'platform': 'universal'}
|
config_no_children = {'name': 'test', 'platform': 'universal'}
|
||||||
config_bad_children = {'name': 'test', 'children': {},
|
config_bad_children = {'name': 'test', 'children': {},
|
||||||
@ -156,7 +188,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual([], config_bad_children['children'])
|
self.assertEqual([], config_bad_children['children'])
|
||||||
|
|
||||||
def test_check_config_bad_commands(self):
|
def test_config_bad_commands(self):
|
||||||
"""Check config with bad commands entry."""
|
"""Check config with bad commands entry."""
|
||||||
config = {'name': 'test', 'commands': [], 'platform': 'universal'}
|
config = {'name': 'test', 'commands': [], 'platform': 'universal'}
|
||||||
|
|
||||||
@ -164,7 +196,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual({}, config['commands'])
|
self.assertEqual({}, config['commands'])
|
||||||
|
|
||||||
def test_check_config_bad_attributes(self):
|
def test_config_bad_attributes(self):
|
||||||
"""Check config with bad attributes."""
|
"""Check config with bad attributes."""
|
||||||
config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
|
config = {'name': 'test', 'attributes': [], 'platform': 'universal'}
|
||||||
|
|
||||||
@ -172,7 +204,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
self.assertEqual({}, config['attributes'])
|
self.assertEqual({}, config['attributes'])
|
||||||
|
|
||||||
def test_check_config_bad_key(self):
|
def test_config_bad_key(self):
|
||||||
"""Check config with bad key."""
|
"""Check config with bad key."""
|
||||||
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
|
config = {'name': 'test', 'asdf': 5, 'platform': 'universal'}
|
||||||
|
|
||||||
@ -183,6 +215,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
def test_platform_setup(self):
|
def test_platform_setup(self):
|
||||||
"""Test platform setup."""
|
"""Test platform setup."""
|
||||||
config = {'name': 'test', 'platform': 'universal'}
|
config = {'name': 'test', 'platform': 'universal'}
|
||||||
|
bad_config = {'platform': 'universal'}
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
def add_devices(new_entities):
|
def add_devices(new_entities):
|
||||||
@ -190,8 +223,10 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
for dev in new_entities:
|
for dev in new_entities:
|
||||||
entities.append(dev)
|
entities.append(dev)
|
||||||
|
|
||||||
universal.setup_platform(self.hass, config, add_devices)
|
universal.setup_platform(self.hass, bad_config, add_devices)
|
||||||
|
self.assertEqual(0, len(entities))
|
||||||
|
|
||||||
|
universal.setup_platform(self.hass, config, add_devices)
|
||||||
self.assertEqual(1, len(entities))
|
self.assertEqual(1, len(entities))
|
||||||
self.assertEqual('test', entities[0].name)
|
self.assertEqual('test', entities[0].name)
|
||||||
|
|
||||||
@ -263,6 +298,15 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(config['name'], ump.name)
|
self.assertEqual(config['name'], ump.name)
|
||||||
|
|
||||||
|
def test_polling(self):
|
||||||
|
"""Test should_poll property."""
|
||||||
|
config = self.config_children_only
|
||||||
|
universal.validate_config(config)
|
||||||
|
|
||||||
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
||||||
|
|
||||||
|
self.assertEqual(False, ump.should_poll)
|
||||||
|
|
||||||
def test_state_children_only(self):
|
def test_state_children_only(self):
|
||||||
"""Test media player state with only children."""
|
"""Test media player state with only children."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
@ -388,8 +432,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
||||||
ump.update()
|
ump.update()
|
||||||
|
|
||||||
self.mock_mp_1._supported_media_commands = \
|
self.mock_mp_1._supported_media_commands = universal.SUPPORT_VOLUME_SET
|
||||||
universal.SUPPORT_VOLUME_SET
|
|
||||||
self.mock_mp_1._state = STATE_PLAYING
|
self.mock_mp_1._state = STATE_PLAYING
|
||||||
self.mock_mp_1.update_ha_state()
|
self.mock_mp_1.update_ha_state()
|
||||||
ump.update()
|
ump.update()
|
||||||
@ -400,7 +443,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertEqual(check_flags, ump.supported_media_commands)
|
self.assertEqual(check_flags, ump.supported_media_commands)
|
||||||
|
|
||||||
def test_service_call_to_child(self):
|
def test_service_call_to_child(self):
|
||||||
"""Test a service call that should be routed to a child."""
|
"""Test service calls that should be routed to a child."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
universal.validate_config(config)
|
universal.validate_config(config)
|
||||||
|
|
||||||
@ -413,13 +456,53 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
ump.update()
|
ump.update()
|
||||||
|
|
||||||
ump.turn_off()
|
ump.turn_off()
|
||||||
self.assertEqual(1, len(self.mock_mp_2.turn_off_service_calls))
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_off']))
|
||||||
|
|
||||||
|
ump.turn_on()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_on']))
|
||||||
|
|
||||||
|
ump.mute_volume(True)
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['mute_volume']))
|
||||||
|
|
||||||
|
ump.set_volume_level(0.5)
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['set_volume_level']))
|
||||||
|
|
||||||
|
ump.media_play()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_play']))
|
||||||
|
|
||||||
|
ump.media_pause()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_pause']))
|
||||||
|
|
||||||
|
ump.media_previous_track()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_previous_track']))
|
||||||
|
|
||||||
|
ump.media_next_track()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_next_track']))
|
||||||
|
|
||||||
|
ump.media_seek(100)
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['media_seek']))
|
||||||
|
|
||||||
|
ump.play_media('movie', 'batman')
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['play_media']))
|
||||||
|
|
||||||
|
ump.volume_up()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_up']))
|
||||||
|
|
||||||
|
ump.volume_down()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_down']))
|
||||||
|
|
||||||
|
ump.media_play_pause()
|
||||||
|
self.assertEqual(
|
||||||
|
1, len(self.mock_mp_2.service_calls['media_play_pause']))
|
||||||
|
|
||||||
def test_service_call_to_command(self):
|
def test_service_call_to_command(self):
|
||||||
"""Test service call to command."""
|
"""Test service call to command."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
config['commands'] = \
|
config['commands'] = {'turn_off': {
|
||||||
{'turn_off': {'service': 'test.turn_off', 'data': {}}}
|
'service': 'test.turn_off', 'data': {}}}
|
||||||
universal.validate_config(config)
|
universal.validate_config(config)
|
||||||
|
|
||||||
service = mock_service(self.hass, 'test', 'turn_off')
|
service = mock_service(self.hass, 'test', 'turn_off')
|
||||||
|
@ -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)
|
||||||
|
@ -5,12 +5,9 @@ from homeassistant.components import rfxtrx as rfxtrx_core
|
|||||||
from homeassistant.components.switch import rfxtrx
|
from homeassistant.components.switch 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 TestSwitchRfxtrx(unittest.TestCase):
|
class TestSwitchRfxtrx(unittest.TestCase):
|
||||||
"""Test the Rfxtrx switch platform."""
|
"""Test the Rfxtrx switch platform."""
|
||||||
|
|
||||||
@ -22,6 +19,8 @@ class TestSwitchRfxtrx(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):
|
||||||
|
@ -104,6 +104,8 @@ class TestAPI(unittest.TestCase):
|
|||||||
_url(const.URL_API_STATES_ENTITY.format("test.test")),
|
_url(const.URL_API_STATES_ENTITY.format("test.test")),
|
||||||
headers=HA_HEADERS)
|
headers=HA_HEADERS)
|
||||||
|
|
||||||
|
self.assertEqual(req.headers['content-length'], str(len(req.content)))
|
||||||
|
|
||||||
data = ha.State.from_dict(req.json())
|
data = ha.State.from_dict(req.json())
|
||||||
|
|
||||||
state = hass.states.get("test.test")
|
state = hass.states.get("test.test")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user