Merge pull request #3715 from home-assistant/dev

0.30
This commit is contained in:
Paulus Schoutsen 2016-10-08 09:58:08 -07:00 committed by GitHub
commit 4239a2b844
180 changed files with 4528 additions and 1877 deletions

View File

@ -16,6 +16,9 @@ omit =
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
@ -46,6 +49,9 @@ omit =
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
@ -77,7 +83,7 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/zwave/*
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
@ -135,6 +141,7 @@ omit =
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/fan/mqtt.py
@ -213,6 +220,7 @@ omit =
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dte_energy_bridge.py
@ -222,7 +230,6 @@ omit =
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
@ -255,17 +262,20 @@ omit =
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yahoo_finance.py
homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py

View File

@ -15,7 +15,7 @@
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
If code communicates with devices, web services, or a:
If the code communicates with devices, web services, or third-party tools:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).

View File

@ -340,8 +340,8 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Home-Assistant.tex', 'Home-Assistant Documentation',
'Home-Assistant Team', 'manual'),
(master_doc, 'home-assistant.tex', 'Home Assistant Documentation',
'Home Assistant Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -382,7 +382,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'home-assistant', 'Home-Assistant Documentation',
(master_doc, 'home-assistant', 'Home Assistant Documentation',
[author], 1)
]
@ -397,8 +397,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Home-Assistant', 'Home-Assistant Documentation',
author, 'Home-Assistant', 'One line description of project.',
(master_doc, 'Home-Assistant', 'Home Assistant Documentation',
author, 'Home Assistant', 'Open-source home automation platform.',
'Miscellaneous'),
]

View File

@ -4,6 +4,7 @@ Allow to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import logging
import os
@ -23,12 +24,15 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'
@ -36,6 +40,7 @@ CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONF_INITIAL_STATE = 'initial_state'
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
@ -43,9 +48,7 @@ CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
DEFAULT_HIDE_ENTITY = False
METHOD_TRIGGER = 'trigger'
METHOD_IF_ACTION = 'if_action'
DEFAULT_INITIAL_STATE = True
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
@ -55,21 +58,14 @@ SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
def _platform_validator(method, schema):
"""Generate platform validator for different steps."""
def validator(config):
def _platform_validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
if not hasattr(platform, method):
raise vol.Invalid('invalid method platform')
if not hasattr(platform, schema):
if not hasattr(platform, 'TRIGGER_SCHEMA'):
return config
return getattr(platform, schema)(config)
return validator
return getattr(platform, 'TRIGGER_SCHEMA')(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
@ -78,33 +74,19 @@ _TRIGGER_SCHEMA = vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
}, extra=vol.ALLOW_EXTRA),
_platform_validator(METHOD_TRIGGER, 'TRIGGER_SCHEMA')
_platform_validator
),
]
)
_CONDITION_SCHEMA = vol.Any(
CONDITION_USE_TRIGGER_VALUES,
vol.All(
cv.ensure_list,
[
vol.All(
vol.Schema({
CONF_PLATFORM: str,
CONF_CONDITION: str,
}, extra=vol.ALLOW_EXTRA),
cv.has_at_least_one_key(CONF_PLATFORM, CONF_CONDITION),
),
]
)
)
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Optional(CONF_INITIAL_STATE,
default=DEFAULT_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})
@ -163,9 +145,11 @@ def reload(hass):
def setup(hass, config):
"""Setup the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
success = _process_config(hass, config, component)
success = run_coroutine_threadsafe(
_async_process_config(hass, config, component), hass.loop).result()
if not success:
return False
@ -173,22 +157,37 @@ def setup(hass, config):
descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@asyncio.coroutine
def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
entity.trigger(service_call.data.get(ATTR_VARIABLES))
hass.loop.create_task(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
def service_handler(service_call):
"""Handle automation service calls."""
@asyncio.coroutine
def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
method = 'async_{}'.format(service_call.service)
for entity in component.extract_from_service(service_call):
getattr(entity, service_call.service)()
hass.loop.create_task(getattr(entity, method)())
@asyncio.coroutine
def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
for entity in component.extract_from_service(service_call):
if entity.is_on:
hass.loop.create_task(entity.async_turn_off())
else:
hass.loop.create_task(entity.async_turn_on())
@asyncio.coroutine
def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
conf = component.prepare_reload()
conf = yield from hass.loop.run_in_executor(
None, component.prepare_reload)
if conf is None:
return
_process_config(hass, conf, component)
hass.loop.create_task(_async_process_config(hass, conf, component))
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
@ -198,8 +197,12 @@ def setup(hass, config):
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE):
hass.services.register(DOMAIN, service, service_handler,
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
descriptions.get(SERVICE_TOGGLE),
schema=SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
hass.services.register(DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)
@ -209,15 +212,17 @@ def setup(hass, config):
class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
# pylint: disable=abstract-method
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, name, attach_triggers, cond_func, action, hidden):
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
"""Initialize an automation entity."""
self._name = name
self._attach_triggers = attach_triggers
self._detach_triggers = attach_triggers(self.trigger)
self._async_attach_triggers = async_attach_triggers
self._async_detach_triggers = None
self._cond_func = cond_func
self._action = action
self._enabled = True
self._async_action = async_action
self._enabled = False
self._last_triggered = None
self._hidden = hidden
@ -248,41 +253,65 @@ class AutomationEntity(ToggleEntity):
"""Return True if entity is on."""
return self._enabled
def turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self._enabled:
return
self._detach_triggers = self._attach_triggers(self.trigger)
self._enabled = True
self.update_ha_state()
yield from self.async_enable()
self.hass.loop.create_task(self.async_update_ha_state())
def turn_off(self, **kwargs) -> None:
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
return
self._detach_triggers()
self._detach_triggers = None
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
self.update_ha_state()
self.hass.loop.create_task(self.async_update_ha_state())
def trigger(self, variables):
"""Trigger automation."""
if self._cond_func(variables):
self._action(variables)
@asyncio.coroutine
def async_trigger(self, variables, skip_condition=False):
"""Trigger automation.
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
yield from self._async_action(variables)
self._last_triggered = utcnow()
self.update_ha_state()
self.hass.loop.create_task(self.async_update_ha_state())
def remove(self):
"""Remove automation from HASS."""
self.turn_off()
run_coroutine_threadsafe(self.async_turn_off(),
self.hass.loop).result()
super().remove()
@asyncio.coroutine
def async_enable(self):
"""Enable this automation entity.
def _process_config(hass, config, component):
"""Process config and add automations."""
success = False
This method is a coroutine.
"""
if self._enabled:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self.async_trigger)
self._enabled = True
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process config and add automations.
This method is a coroutine.
"""
entities = []
tasks = []
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
@ -293,10 +322,11 @@ def _process_config(hass, config, component):
hidden = config_block[CONF_HIDE_ENTITY]
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)
if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)
cond_func = _async_process_if(hass, config, config_block)
if cond_func is None:
continue
@ -305,101 +335,78 @@ def _process_config(hass, config, component):
"""Condition will always pass."""
return True
attach_triggers = partial(_process_trigger, hass, config,
async_attach_triggers = partial(
_async_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action,
hidden)
component.add_entities((entity,))
success = True
entity = AutomationEntity(name, async_attach_triggers, cond_func,
action, hidden)
if config_block[CONF_INITIAL_STATE]:
tasks.append(hass.loop.create_task(entity.async_enable()))
entities.append(entity)
return success
yield from asyncio.gather(*tasks, loop=hass.loop)
yield from hass.loop.run_in_executor(
None, component.add_entities, entities)
return len(entities) > 0
def _get_action(hass, config, name):
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
@asyncio.coroutine
def action(variables=None):
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
script_obj.run(variables)
logbook.async_log_entry(hass, name, 'has been triggered', DOMAIN)
hass.loop.create_task(script_obj.async_run(variables))
return action
def _process_if(hass, config, p_config):
def _async_process_if(hass, config, p_config):
"""Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
# Deprecated since 0.19 - 5/5/2016
if cond_type != DEFAULT_CONDITION_TYPE:
_LOGGER.warning('Using condition_type: "or" is deprecated. Please use '
'"condition: or" instead.')
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
if use_trigger:
if_configs = p_config[CONF_TRIGGER]
checks = []
for if_config in if_configs:
# Deprecated except for used by use_trigger_values
# since 0.19 - 5/5/2016
if CONF_PLATFORM in if_config:
if not use_trigger:
_LOGGER.warning("Please switch your condition configuration "
"to use 'condition' instead of 'platform'.")
if_config = dict(if_config)
if_config[CONF_CONDITION] = if_config.pop(CONF_PLATFORM)
# To support use_trigger_values with state trigger accepting
# multiple entity_ids to monitor.
if_entity_id = if_config.get(ATTR_ENTITY_ID)
if isinstance(if_entity_id, list) and len(if_entity_id) == 1:
if_config[ATTR_ENTITY_ID] = if_entity_id[0]
try:
checks.append(condition.from_config(if_config))
checks.append(condition.async_from_config(if_config, False))
except HomeAssistantError as ex:
# Invalid conditions are allowed if we base it on trigger
if use_trigger:
_LOGGER.warning('Ignoring invalid condition: %s', ex)
else:
_LOGGER.warning('Invalid condition: %s', ex)
return None
if cond_type == CONDITION_TYPE_AND:
def if_action(variables=None):
"""AND all conditions."""
return all(check(hass, variables) for check in checks)
else:
def if_action(variables=None):
"""OR all conditions."""
return any(check(hass, variables) for check in checks)
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers."""
@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers.
This method is a coroutine.
"""
removes = []
for conf in trigger_configs:
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
platform = yield from hass.loop.run_in_executor(
None, prepare_setup_platform, hass, config, DOMAIN,
conf.get(CONF_PLATFORM))
if platform is None:
continue
remove = platform.trigger(hass, conf, action)
if platform is None:
return None
remove = platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up rule %s", name)
_LOGGER.error("Error setting up trigger %s", name)
continue
_LOGGER.info("Initialized rule %s", name)
_LOGGER.info("Initialized trigger %s", name)
removes.append(remove)
if not removes:
@ -411,17 +418,3 @@ def _process_trigger(hass, config, trigger_configs, name, action):
remove()
return remove_triggers
def _resolve_platform(method, hass, config, platform):
"""Find the automation platform."""
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
if platform is None or not hasattr(platform, method):
_LOGGER.error("Unknown automation platform specified for %s: %s",
method, platform)
return None
return platform

View File

@ -4,11 +4,11 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
@ -24,21 +24,21 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
@asyncio.coroutine
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
hass.async_add_job(action, {
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
return hass.bus.listen(event_type, handle_event)
return hass.bus.async_listen(event_type, handle_event)

View File

@ -6,6 +6,7 @@ at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
@ -21,15 +22,16 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@callback
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'mqtt',
'topic': msg_topic,
@ -38,4 +40,4 @@ def trigger(hass, config, action):
}
})
return mqtt.subscribe(hass, topic, mqtt_automation_listener)
return mqtt.async_subscribe(hass, topic, mqtt_automation_listener)

View File

@ -8,10 +8,11 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import condition, config_validation as cv
TRIGGER_SCHEMA = vol.All(vol.Schema({
@ -25,7 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)
@ -34,7 +35,7 @@ def trigger(hass, config, action):
if value_template is not None:
value_template.hass = hass
# pylint: disable=unused-argument
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if to_s is None:
@ -50,19 +51,19 @@ def trigger(hass, config, action):
}
# If new one doesn't match, nothing to do
if not condition.numeric_state(
if not condition.async_numeric_state(
hass, to_s, below, above, value_template, variables):
return
# Only match if old didn't exist or existed but didn't match
# Written as: skip if old one did exist and matched
if from_s is not None and condition.numeric_state(
if from_s is not None and condition.async_numeric_state(
hass, from_s, below, above, value_template, variables):
return
variables['trigger']['from_state'] = from_s
variables['trigger']['to_state'] = to_s
action(variables)
hass.async_run_job(action, variables)
return track_state_change(hass, entity_id, state_automation_listener)
return async_track_state_change(hass, entity_id, state_automation_listener)

View File

@ -6,9 +6,11 @@ at https://home-assistant.io/components/automation/#state-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
from homeassistant.helpers.event import track_state_change, track_point_in_time
from homeassistant.helpers.event import (
async_track_state_change, async_track_point_in_utc_time)
import homeassistant.helpers.config_validation as cv
CONF_ENTITY_ID = "entity_id"
@ -32,22 +34,23 @@ TRIGGER_SCHEMA = vol.All(
)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
time_delta = config.get(CONF_FOR)
remove_state_for_cancel = None
remove_state_for_listener = None
async_remove_state_for_cancel = None
async_remove_state_for_listener = None
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal remove_state_for_cancel, remove_state_for_listener
nonlocal async_remove_state_for_cancel, async_remove_state_for_listener
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'state',
'entity_id': entity,
@ -61,35 +64,37 @@ def trigger(hass, config, action):
call_action()
return
@callback
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
remove_state_for_cancel()
async_remove_state_for_cancel()
call_action()
@callback
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s.state == to_s.state:
return
remove_state_for_listener()
remove_state_for_cancel()
async_remove_state_for_listener()
async_remove_state_for_cancel()
remove_state_for_listener = track_point_in_time(
async_remove_state_for_listener = async_track_point_in_utc_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
remove_state_for_cancel = track_state_change(
async_remove_state_for_cancel = async_track_state_change(
hass, entity, state_for_cancel_listener)
unsub = track_state_change(hass, entity_id, state_automation_listener,
from_state, to_state)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
def remove():
"""Remove state listeners."""
def async_remove():
"""Remove state listeners async."""
unsub()
# pylint: disable=not-callable
if remove_state_for_cancel is not None:
remove_state_for_cancel()
if async_remove_state_for_cancel is not None:
async_remove_state_for_cancel()
if remove_state_for_listener is not None:
remove_state_for_listener()
if async_remove_state_for_listener is not None:
async_remove_state_for_listener()
return remove
return async_remove

View File

@ -9,9 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE)
from homeassistant.helpers.event import track_sunrise, track_sunset
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun']
@ -25,14 +26,15 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
@callback
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'sun',
'event': event,
@ -42,6 +44,6 @@ def trigger(hass, config, action):
# Do something to call action
if event == SUN_EVENT_SUNRISE:
return track_sunrise(hass, call_action, offset)
return async_track_sunrise(hass, call_action, offset)
else:
return track_sunset(hass, call_action, offset)
return async_track_sunset(hass, call_action, offset)

View File

@ -4,14 +4,14 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
@ -23,7 +23,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
@ -31,7 +31,7 @@ def trigger(hass, config, action):
# Local variable to keep track of if the action has already been triggered
already_triggered = False
@asyncio.coroutine
@callback
def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
@ -40,7 +40,7 @@ def trigger(hass, config, action):
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
hass.async_add_job(action, {
hass.async_run_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
@ -51,5 +51,5 @@ def trigger(hass, config, action):
elif not template_result:
already_triggered = False
return track_state_change(hass, value_template.extract_entities(),
return async_track_state_change(hass, value_template.extract_entities(),
state_changed_listener)

View File

@ -8,9 +8,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_AFTER, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.event import async_track_time_change
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
@ -28,7 +29,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
CONF_SECONDS, CONF_AFTER))
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
if CONF_AFTER in config:
after = config.get(CONF_AFTER)
@ -38,14 +39,15 @@ def trigger(hass, config, action):
minutes = config.get(CONF_MINUTES)
seconds = config.get(CONF_SECONDS)
@callback
def time_automation_listener(now):
"""Listen for time changes and calls action."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'time',
'now': now,
},
})
return track_time_change(hass, time_automation_listener,
return async_track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)

View File

@ -6,9 +6,10 @@ at https://home-assistant.io/components/automation/#zone-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import (
condition, config_validation as cv, location)
@ -25,12 +26,13 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
event = config.get(CONF_EVENT)
@callback
def zone_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if from_s and not location.has_location(from_s) or \
@ -47,7 +49,7 @@ def trigger(hass, config, action):
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'zone',
'entity_id': entity,
@ -58,5 +60,5 @@ def trigger(hass, config, action):
},
})
return track_state_change(hass, entity_id, zone_automation_listener,
return async_track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)

View File

@ -1,5 +1,5 @@
"""
Support for exposed aREST RESTful API of a device.
Support for an exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/
@ -8,31 +8,32 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.const import CONF_RESOURCE, CONF_PIN
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_PIN): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
if None in (resource, pin):
_LOGGER.error('Not all required config keys present: %s',
', '.join((CONF_RESOURCE, CONF_PIN)))
return False
sensor_class = config.get(CONF_SENSOR_CLASS)
try:
response = requests.get(resource, timeout=10).json()
@ -49,11 +50,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
arest = ArestData(resource, pin)
add_devices([ArestBinarySensor(
arest,
resource,
config.get('name', response['name']),
sensor_class,
pin)])
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
sensor_class, pin)])
# pylint: disable=too-many-instance-attributes, too-many-arguments

View File

@ -0,0 +1,91 @@
"""
Support for monitoring the state of Digital Ocean droplets.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.digital_ocean/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'motion'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Digital Ocean droplet sensor."""
digital_ocean = get_component('digital_ocean')
droplets = config.get(CONF_DROPLETS)
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
add_devices(dev)
class DigitalOceanBinarySensor(BinarySensorDevice):
"""Representation of a Digital Ocean droplet sensor."""
def __init__(self, do, droplet_id):
"""Initialize a new Digital Ocean sensor."""
self._digital_ocean = do
self._droplet_id = droplet_id
self._state = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self.data.name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.data.status == 'active'
@property
def sensor_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
@property
def state_attributes(self):
"""Return the state attributes of the Digital Ocean droplet."""
return {
ATTR_CREATED_AT: self.data.created_at,
ATTR_DROPLET_ID: self.data.id,
ATTR_DROPLET_NAME: self.data.name,
ATTR_FEATURES: self.data.features,
ATTR_IPV4_ADDRESS: self.data.ip_address,
ATTR_IPV6_ADDRESS: self.data.ip_v6_address,
ATTR_MEMORY: self.data.memory,
ATTR_REGION: self.data.region['name'],
ATTR_VCPUS: self.data.vcpus,
}
def update(self):
"""Update state of sensor."""
self._digital_ocean.update()
for droplet in self._digital_ocean.data:
if droplet.id == self._droplet_id:
self.data = droplet

View File

@ -16,6 +16,7 @@ DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"IPShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",

View File

@ -1,41 +1,56 @@
"""
Support for exposing nx584 elements as sensors.
Support for exposing NX584 elements as sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.nx584/
https://home-assistant.io/components/binary_sensor.nx584/
"""
import logging
import threading
import time
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
SENSOR_CLASSES, BinarySensorDevice)
SENSOR_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
CONF_ZONE_TYPES = 'zone_types'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXCLUDE_ZONES, default=[]):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup nx584 binary sensor platform."""
"""Setup the NX584 binary sensor platform."""
from nx584 import client as nx584_client
host = config.get('host', 'localhost:5007')
exclude = config.get('exclude_zones', [])
zone_types = config.get('zone_types', {})
if not all(isinstance(zone, int) for zone in exclude):
_LOGGER.error('Invalid excluded zone specified (use zone number)')
return False
if not all(isinstance(zone, int) and ztype in SENSOR_CLASSES
for zone, ztype in zone_types.items()):
_LOGGER.error('Invalid zone_types entry')
return False
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
exclude = config.get(CONF_EXCLUDE_ZONES)
zone_types = config.get(CONF_ZONE_TYPES)
try:
client = nx584_client.Client('http://%s' % host)
client = nx584_client.Client('http://{}:{}'.format(host, port))
zones = client.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
@ -43,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
version = [int(v) for v in client.get_version().split('.')]
if version < [1, 1]:
_LOGGER.error('NX584 is too old to use for sensors (>=0.2 required)')
_LOGGER.error("NX584 is too old to use for sensors (>=0.2 required)")
return False
zone_sensors = {
@ -57,13 +72,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
watcher = NX584Watcher(client, zone_sensors)
watcher.start()
else:
_LOGGER.warning('No zones found on NX584')
_LOGGER.warning("No zones found on NX584")
return True
class NX584ZoneSensor(BinarySensorDevice):
"""Represents a NX584 zone as a sensor."""
"""Representation of a NX584 zone as a sensor."""
def __init__(self, zone, zone_type):
"""Initialize the nx594 binary sensor."""
@ -96,7 +110,7 @@ class NX584Watcher(threading.Thread):
"""Event listener thread to process NX584 events."""
def __init__(self, client, zone_sensors):
"""Initialize nx584 watcher thread."""
"""Initialize NX584 watcher thread."""
super(NX584Watcher, self).__init__()
self.daemon = True
self._client = client
@ -130,5 +144,5 @@ class NX584Watcher(threading.Thread):
try:
self._run()
except requests.exceptions.ConnectionError:
_LOGGER.error('Failed to reach NX584 server')
_LOGGER.error("Failed to reach NX584 server")
time.sleep(10)

View File

@ -5,15 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/
"""
import logging
import json
import voluptuous as vol
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -24,16 +28,21 @@ DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): cv.string,
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
# pylint: disable=unused-variable
# pylint: disable=unused-variable, too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the REST binary sensor."""
name = config.get(CONF_NAME)
@ -41,11 +50,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
method = config.get(CONF_METHOD)
payload = config.get(CONF_PAYLOAD)
verify_ssl = config.get(CONF_VERIFY_SSL)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = json.loads(config.get(CONF_HEADERS, '{}'))
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl)
if username and password:
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
auth = HTTPDigestAuth(username, password)
else:
auth = HTTPBasicAuth(username, password)
else:
auth = None
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:

View File

@ -4,10 +4,12 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
@ -81,9 +83,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self.update()
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.update_ha_state(True)
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_bsensor_state_listener)
@ -107,10 +110,11 @@ class BinarySensorTemplate(BinarySensorDevice):
"""No polling needed."""
return False
def update(self):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state."""
try:
self._state = self._template.render().lower() == 'true'
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):

View File

@ -20,7 +20,10 @@ SENSOR_TYPES = {
"vibration": "vibration",
"loudness": "sound",
"liquid_detected": "moisture",
"motion": "motion"
"motion": "motion",
"presence": "occupancy",
"co_detected": "gas",
"smoke_detected": "smoke"
}
@ -35,6 +38,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor)])
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink binary sensor."""
@ -58,17 +64,25 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def is_on(self):
"""Return true if the binary sensor is on."""
if self.capability == "loudness":
return self.wink.loudness_boolean()
state = self.wink.loudness_boolean()
elif self.capability == "vibration":
return self.wink.vibration_boolean()
state = self.wink.vibration_boolean()
elif self.capability == "brightness":
return self.wink.brightness_boolean()
state = self.wink.brightness_boolean()
elif self.capability == "liquid_detected":
return self.wink.liquid_boolean()
state = self.wink.liquid_boolean()
elif self.capability == "motion":
return self.wink.motion_boolean()
state = self.wink.motion_boolean()
elif self.capability == "presence":
state = self.wink.presence_boolean()
elif self.capability == "co_detected":
state = self.wink.co_detected_boolean()
elif self.capability == "smoke_detected":
state = self.wink.smoke_detected_boolean()
else:
return self.wink.state()
state = self.wink.state()
return state
@property
def sensor_class(self):

View File

@ -36,8 +36,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
# Make sure that we have values for the key before converting to int
@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
return
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])

View File

@ -58,6 +58,12 @@ ATTR_OPERATION_LIST = "operation_list"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_LIST = "swing_list"
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH,
]
_LOGGER = logging.getLogger(__name__)
SET_AWAY_MODE_SCHEMA = vol.Schema({
@ -73,6 +79,7 @@ SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
@ -116,8 +123,10 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
# pylint: disable=too-many-arguments
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None):
target_temp_high=None, target_temp_low=None,
operation_mode=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
@ -125,6 +134,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
(ATTR_OPERATION_MODE, operation_mode)
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
@ -235,10 +245,20 @@ def setup(hass, config):
def temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
kwargs = service.data
for climate in target_climate:
climate.set_temperature(**kwargs)
for climate in target_climate:
kwargs = {}
for value, temp in service.data.items():
if value in CONVERTIBLE_ATTRIBUTE:
kwargs[value] = convert_temperature(
temp,
hass.config.units.temperature_unit,
climate.unit_of_measurement
)
else:
kwargs[value] = temp
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)

View File

@ -13,7 +13,6 @@ from homeassistant.components.climate import (
ATTR_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
from homeassistant.util.temperature import convert as convert_temperature
DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
@ -127,12 +126,9 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
target_temp_high = convert_temperature(kwargs.get(
ATTR_TARGET_TEMP_HIGH), self._unit, TEMP_CELSIUS)
target_temp_low = convert_temperature(kwargs.get(
ATTR_TARGET_TEMP_LOW), self._unit, TEMP_CELSIUS)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp_low is not None and target_temp_high is not None:
if self.device.mode == 'range':
temp = (target_temp_low, target_temp_high)

View File

@ -34,6 +34,18 @@ set_temperature:
description: New target temperature for hvac
example: 25
target_temp_high:
description: New target high tempereature for hvac
example: 26
target_temp_low:
description: New target low temperature for hvac
example: 20
operation_mode:
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
example: 'Heat'
set_humidity:
description: Set target humidity of climate device

View File

@ -8,9 +8,9 @@ https://home-assistant.io/components/climate.zwave/
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components.climate import (
ClimateDevice, ATTR_OPERATION_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@ -28,12 +28,6 @@ HORSTMANN = 0x0059
HORSTMANN_HRT4_ZW = 0x3
HORSTMANN_HRT4_ZW_THERMOSTAT = (HORSTMANN, HORSTMANN_HRT4_ZW)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
WORKAROUND_HRT4_ZW = 'hrt4_zw'
@ -67,19 +61,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
return
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
# pylint: disable=too-many-arguments, abstract-method
# pylint: disable=abstract-method
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
# pylint: disable=too-many-public-methods, too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
from openzwave.network import ZWaveNetwork
@ -130,7 +124,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Callback on data change for the registered node/value pair."""
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._index_operation = SET_TEMP_TO_INDEX.get(
self._current_operation)
@ -139,14 +133,16 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@ -154,17 +150,27 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = int(self._current_temperature)
break
if self.current_operation is not None and \
self.current_operation != 'Off':
if self._index_operation != value.index:
@ -232,16 +238,26 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
# pylint: disable=too-many-branches, too-many-statements
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
return
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
_LOGGER.debug("set_temperature operation_mode=%s", operation_mode)
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if operation_mode is not None:
setpoint_mode = SET_TEMP_TO_INDEX.get(operation_mode)
if value.index != setpoint_mode:
continue
_LOGGER.debug("setpoint_mode=%s", setpoint_mode)
value.data = temperature
break
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None:
if self._hrt4_zw and self.current_operation == 'Off':
# HRT4-ZW can change setpoint when off.
@ -279,17 +295,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE).
values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE and \
value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_MODE and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
@ -297,7 +317,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break

View File

@ -29,6 +29,10 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Register the process service."""
@ -48,8 +52,8 @@ def setup(hass, config):
name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(name, entities,
score_cutoff=65)[2]
entity_ids = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
if not entity_ids:
logger.error(
@ -70,6 +74,7 @@ def setup(hass, config):
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA)
return True

View File

@ -0,0 +1,103 @@
"""
Support for MySensors covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.const import STATE_ON, STATE_OFF
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for covers."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsCover))
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
"""Representation of the value of a MySensors Cover child node."""
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
return self.gateway.optimistic
@property
def is_closed(self):
"""Return True if cover is closed."""
set_req = self.gateway.const.SetReq
if set_req.V_DIMMER in self._values:
return self._values.get(set_req.V_DIMMER) == 0
else:
return self._values.get(set_req.V_LIGHT) == STATE_OFF
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
set_req = self.gateway.const.SetReq
return self._values.get(set_req.V_DIMMER)
def open_cover(self, **kwargs):
"""Move the cover up."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_UP, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DOWN, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.update_ha_state()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DIMMER, position)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_STOP, 1)

View File

@ -12,9 +12,6 @@ from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.cover import CoverDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
@ -32,17 +29,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if (value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL and
value.index == 0):
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL) \
and value.index == 0:
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
elif (value.command_class == zwave.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.TYPE_BOOL and \
value.genre != zwave.GENRE_USER:
elif node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_BINARY) or \
node.has_command_class(zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.const.TYPE_BOOL and \
value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
@ -59,6 +56,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
# pylint: disable=no-member
self._lozwmgr = libopenzwave.PyManager()
self._lozwmgr.create()
self._node = value.node
@ -88,9 +86,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Callback on data change for the registered node/value pair."""
# Position value
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Level':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
@property
@ -118,22 +117,24 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.pressButton(value.value_id)
break
def close_cover(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Up' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Close':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
@ -144,11 +145,12 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break

View File

@ -46,7 +46,6 @@ CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180 # seconds
CONF_SCAN_INTERVAL = 'interval_seconds'
DEFAULT_SCAN_INTERVAL = 12
@ -70,8 +69,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [
vol.Schema({
vol.Optional(CONF_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(
CONF_CONSIDER_HOME, default=timedelta(seconds=180)): vol.All(
cv.time_period, cv.positive_timedelta)
}, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA)
DISCOVERY_PLATFORMS = {
@ -118,9 +119,8 @@ def setup(hass: HomeAssistantType, config: ConfigType):
return False
else:
conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta(
seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME))
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
consider_home = conf[CONF_CONSIDER_HOME]
track_new = conf[CONF_TRACK_NEW]
devices = load_config(yaml_path, hass, consider_home)
@ -282,7 +282,7 @@ class Device(Entity):
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None,
picture: str=None, gravatar: str=None,
away_hide: bool=False) -> None:
hide_if_away: bool=False) -> None:
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@ -307,7 +307,7 @@ class Device(Entity):
else:
self.config_picture = picture
self.away_hide = away_hide
self.away_hide = hide_if_away
@property
def name(self):
@ -398,15 +398,29 @@ class Device(Entity):
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
"""Load devices from YAML configuration file."""
dev_schema = vol.Schema({
vol.Required('name'): cv.string,
vol.Optional('track', default=False): cv.boolean,
vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string,
vol.Upper)),
vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean,
vol.Optional('gravatar', default=None): vol.Any(None, cv.string),
vol.Optional('picture', default=None): vol.Any(None, cv.string),
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
cv.time_period, cv.positive_timedelta)
})
try:
return [
Device(hass, consider_home, device.get('track', False),
str(dev_id).lower(), None if device.get('mac') is None
else str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get('gravatar'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
result = []
devices = load_yaml_config_file(path)
for dev_id, device in devices.items():
try:
device = dev_schema(device)
device['dev_id'] = cv.slug(dev_id)
except vol.Invalid as exp:
log_exception(exp, dev_id, devices)
else:
result.append(Device(hass, **device))
return result
except (HomeAssistantError, FileNotFoundError):
# When YAML file could not be loaded/did not contain a dict
return []

View File

@ -85,7 +85,9 @@ class FritzBoxScanner(object):
def get_device_name(self, mac):
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)['NewHostName']
ret = self.fritz_box.get_specific_host_entry(mac).get(
'NewHostName'
)
if ret == {}:
return None
return ret

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/device_tracker.owntracks/
import json
import logging
import threading
import base64
from collections import defaultdict
import voluptuous as vol
@ -18,53 +19,121 @@ from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REGIONS_ENTERED = defaultdict(list)
MOBILE_BEACONS_ACTIVE = defaultdict(list)
BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
REQUIREMENTS = ['libnacl==1.5.0']
_LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
BEACON_DEV_ID = 'beacon'
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
DEPENDENCIES = ['mqtt']
EVENT_TOPIC = 'owntracks/+/+/event'
LOCATION_TOPIC = 'owntracks/+/+'
LOCK = threading.Lock()
MOBILE_BEACONS_ACTIVE = defaultdict(list)
REGIONS_ENTERED = defaultdict(list)
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
VALIDATE_WAYPOINTS = 'waypoints'
WAYPOINT_LAT_KEY = 'lat'
WAYPOINT_LON_KEY = 'lon'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
vol.Optional(CONF_WAYPOINT_IMPORT, default=True): cv.boolean,
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(cv.ensure_list, [cv.string])
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(
cv.ensure_list, [cv.string]),
vol.Optional(CONF_SECRET): vol.Any(
vol.Schema({vol.Optional(cv.string): cv.string}),
cv.string)
})
def get_cipher():
"""Return decryption function and length of key."""
from libnacl import crypto_secretbox_KEYBYTES as KEYLEN
from libnacl.secret import SecretBox
def decrypt(ciphertext, key):
"""Decrypt ciphertext using key."""
return SecretBox(key).decrypt(ciphertext)
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker."""
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
try:
keylen, decrypt = get_cipher()
except OSError:
_LOGGER.warning('Ignoring encrypted payload '
'because libsodium not installed.')
return None
if isinstance(secret, dict):
key = secret.get(topic)
else:
key = secret
if key is None:
_LOGGER.warning('Ignoring encrypted payload '
'because no decryption key known '
'for topic %s.', topic)
return None
key = key.encode("utf-8")
key = key[:keylen]
key = key.ljust(keylen, b'\0')
try:
ciphertext = base64.b64decode(ciphertext)
message = decrypt(ciphertext, key)
message = message.decode("utf-8")
_LOGGER.debug("Decrypted payload: %s", message)
return message
except ValueError:
_LOGGER.warning('Ignoring encrypted payload '
'because unable to decrypt using key '
'for topic %s.', topic)
return None
def validate_payload(topic, payload, data_type):
"""Validate the OwnTracks payload."""
# pylint: disable=too-many-return-statements
def validate_payload(payload, data_type):
"""Validate OwnTracks payload."""
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error('Unable to parse payload as JSON: %s', payload)
return None
if isinstance(data, dict) and \
data.get('_type') == 'encrypted' and \
'data' in data:
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
else:
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug('Skipping %s update for following data '
'because of missing or malformatted data: %s',
@ -90,7 +159,7 @@ def setup_scanner(hass, config, see):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(payload, VALIDATE_LOCATION)
data = validate_payload(topic, payload, VALIDATE_LOCATION)
if not data:
return
@ -111,7 +180,7 @@ def setup_scanner(hass, config, see):
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
data = validate_payload(payload, VALIDATE_TRANSITION)
data = validate_payload(topic, payload, VALIDATE_TRANSITION)
if not data:
return
@ -206,7 +275,7 @@ def setup_scanner(hass, config, see):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(payload, VALIDATE_WAYPOINTS)
data = validate_payload(topic, payload, VALIDATE_WAYPOINTS)
if not data:
return

View File

@ -0,0 +1,100 @@
"""
Support for Volvo On Call.
http://www.volvocars.com/intl/own/owner-info/volvo-on-call
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.volvooncall/
"""
import logging
from datetime import timedelta
from urllib.parse import urljoin
import voluptuous as vol
import requests
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME)
from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA)
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
SERVICE_URL = 'https://vocapi.wirelesscar.net/customerapi/rest/v3.0/'
HEADERS = {"X-Device-Id": "Device",
"X-OS-Type": "Android",
"X-Originator-Type": "App"}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_scanner(hass, config, see):
"""Validate the configuration and return a scanner."""
session = requests.Session()
session.headers.update(HEADERS)
session.auth = (config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
interval = max(MIN_TIME_BETWEEN_SCANS.seconds,
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
def query(ref, rel=SERVICE_URL):
"""Perform a query to the online service."""
url = urljoin(rel, ref)
_LOGGER.debug("Request for %s", url)
res = session.get(url, timeout=15)
res.raise_for_status()
_LOGGER.debug("Received %s", res.json())
return res.json()
def update(now):
"""Update status from the online service."""
try:
_LOGGER.debug("Updating")
status = query("status", vehicle_url)
position = query("position", vehicle_url)
see(dev_id=dev_id,
host_name=host_name,
gps=(position["position"]["latitude"],
position["position"]["longitude"]),
attributes=dict(
tank_volume=attributes["fuelTankVolume"],
washer_fluid=status["washerFluidLevel"],
brake_fluid=status["brakeFluid"],
service_warning=status["serviceWarningStatus"],
fuel=status["fuelAmount"],
odometer=status["odometer"],
range=status["distanceToEmpty"]))
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not query server: %s", error)
finally:
track_point_in_utc_time(hass, update,
now + timedelta(seconds=interval))
try:
_LOGGER.info('Logging in to service')
user = query("customeraccounts")
rel = query(user["accountVehicleRelations"][0])
vehicle_url = rel["vehicle"] + '/'
attributes = query("attributes", vehicle_url)
dev_id = "volvo_" + attributes["registrationNumber"]
host_name = "%s %s/%s" % (attributes["registrationNumber"],
attributes["vehicleType"],
attributes["modelYear"])
update(utcnow())
return True
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not log in to service. "
"Please check configuration: "
"%s", error)
return False

View File

@ -0,0 +1,86 @@
"""
Support for Digital Ocean.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/digital_ocean/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.9.0']
_LOGGER = logging.getLogger(__name__)
ATTR_CREATED_AT = 'created_at'
ATTR_DROPLET_ID = 'droplet_id'
ATTR_DROPLET_NAME = 'droplet_name'
ATTR_FEATURES = 'features'
ATTR_IPV4_ADDRESS = 'ipv4_address'
ATTR_IPV6_ADDRESS = 'ipv6_address'
ATTR_MEMORY = 'memory'
ATTR_REGION = 'region'
ATTR_VCPUS = 'vcpus'
CONF_DROPLETS = 'droplets'
DIGITAL_OCEAN = None
DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor']
DOMAIN = 'digital_ocean'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
"""Setup the Digital Ocean component."""
conf = config[DOMAIN]
access_token = conf.get(CONF_ACCESS_TOKEN)
global DIGITAL_OCEAN
DIGITAL_OCEAN = DigitalOcean(access_token)
if not DIGITAL_OCEAN.manager.get_account():
_LOGGER.error("No Digital Ocean account found for the given API Token")
return False
return True
class DigitalOcean(object):
"""Handle all communication with the Digital Ocean API."""
def __init__(self, access_token):
"""Initialize the Digital Ocean connection."""
import digitalocean
self._access_token = access_token
self.data = None
self.manager = digitalocean.Manager(token=self._access_token)
def get_droplet_id(self, droplet_name):
"""Get the status of a Digital Ocean droplet."""
droplet_id = None
all_droplets = self.manager.get_all_droplets()
for droplet in all_droplets:
if droplet_name == droplet.name:
droplet_id = droplet.id
return droplet_id
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Use the data from Digital Ocean API."""
self.data = self.manager.get_all_droplets()

View File

@ -9,6 +9,8 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
@ -33,6 +35,10 @@ SERVICE_HANDLERS = {
'directv': ('media_player', 'directv'),
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Start a discovery service."""

View File

@ -25,6 +25,8 @@ from homeassistant.components.light import (
from homeassistant.components.http import (
HomeAssistantView, HomeAssistantWSGI
)
# pylint: disable=unused-import
from homeassistant.components.http import REQUIREMENTS # noqa
import homeassistant.helpers.config_validation as cv
DOMAIN = 'emulated_hue'

View File

@ -1,14 +1,14 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "78862c0984279b6876f594ffde45177c",
"frontend.html": "c1753e1ce530f978036742477c96d2fd",
"mdi.html": "6bd013a8252e19b3c1f1de52994cfbe4",
"panels/ha-panel-dev-event.html": "c4a5f70eece9f92616a65e8d26be803e",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
"panels/ha-panel-dev-service.html": "07e83c6b7f79d78a59258f6dba477b54",
"panels/ha-panel-dev-state.html": "fd8eb946856b1346a87a51d0c86854ff",
"panels/ha-panel-dev-template.html": "7cff8a2ef3f44fdaf357a0d41696bf6d",
"core.js": "9b3e5ab4eac7e3b074e0daf3f619a638",
"frontend.html": "5854807d361de26fe93ad474010f19d2",
"mdi.html": "46a76f877ac9848899b8ed382427c16f",
"panels/ha-panel-dev-event.html": "550bf85345c454274a40d15b2795a002",
"panels/ha-panel-dev-info.html": "ec613406ce7e20d93754233d55625c8a",
"panels/ha-panel-dev-service.html": "c7974458ebc33412d95497e99b785e12",
"panels/ha-panel-dev-state.html": "4be627b74e683af14ef779d8203ec674",
"panels/ha-panel-dev-template.html": "d23943fa0370f168714da407c90091a2",
"panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 0a4454c68f3c29c77cd60f4315d410d8b3737543
Subproject commit db109f5dda043182a7e9647b161851e83be9b91e

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:24px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/balloob/home-assistant" target="_blank">server</a><a href="https://github.com/balloob/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/balloob/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p>Path to configuration.yaml: [[hassConfigDir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a><a href="https://github.com/home-assistant/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},hassConfigDir:{type:String,bindNuclear:function(r){return r.configGetters.configDir}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.3.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -22,15 +22,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY and \
value.command_class != zwave.COMMAND_CLASS_BARRIER_OPERATOR:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_BINARY and \
value.command_class != zwave.const.COMMAND_CLASS_BARRIER_OPERATOR:
return
if value.type != zwave.TYPE_BOOL:
if value.type != zwave.const.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)

View File

@ -23,7 +23,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.14"]
REQUIREMENTS = ["pyhomematic==0.1.16"]
HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5
@ -52,17 +52,22 @@ SERVICE_VIRTUALKEY = 'virtualkey'
SERVICE_SET_VALUE = 'set_value'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: ['Switch', 'SwitchPowermeter'],
DISCOVER_LIGHTS: ['Dimmer'],
DISCOVER_SENSORS: ['SwitchPowermeter', 'Motion', 'MotionV2',
'RemoteMotion', 'ThermostatWall', 'AreaThermostat',
'RotaryHandleSensor', 'WaterSensor', 'PowermeterGas',
'LuxSensor', 'WeatherSensor', 'WeatherStation'],
DISCOVER_CLIMATE: ['Thermostat', 'ThermostatWall', 'MAXThermostat'],
DISCOVER_BINARY_SENSORS: ['ShutterContact', 'Smoke', 'SmokeV2', 'Motion',
'MotionV2', 'RemoteMotion', 'WeatherSensor',
'TiltSensor'],
DISCOVER_COVER: ['Blind']
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch',
'IPSwitchPowermeter', 'KeyMatic'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer'],
DISCOVER_SENSORS: [
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion',
'ThermostatWall', 'AreaThermostat', 'RotaryHandleSensor',
'WaterSensor', 'PowermeterGas', 'LuxSensor', 'WeatherSensor',
'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor',
'TemperatureSensor', 'CO2Sensor'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2'],
DISCOVER_BINARY_SENSORS: [
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact'],
DISCOVER_COVER: ['Blind', 'KeyBlind']
}
HM_IGNORE_DISCOVERY_NODE = [
@ -87,11 +92,12 @@ HM_PRESS_EVENTS = [
'PRESS_SHORT',
'PRESS_LONG',
'PRESS_CONT',
'PRESS_LONG_RELEASE'
'PRESS_LONG_RELEASE',
'PRESS',
]
HM_IMPULSE_EVENTS = [
'SEQUENCE_OK'
'SEQUENCE_OK',
]
_LOGGER = logging.getLogger(__name__)
@ -111,6 +117,15 @@ CONF_RESOLVENAMES = 'resolvenames'
CONF_DELAY = 'delay'
CONF_VARIABLES = 'variables'
DEFAULT_LOCAL_IP = "0.0.0.0"
DEFAULT_LOCAL_PORT = 0
DEFAULT_RESOLVENAMES = False
DEFAULT_REMOTE_PORT = 2001
DEFAULT_USERNAME = "Admin"
DEFAULT_PASSWORD = ""
DEFAULT_VARIABLES = False
DEFAULT_DELAY = 0.5
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): "homematic",
@ -122,16 +137,16 @@ DEVICE_SCHEMA = vol.Schema({
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_LOCAL_IP): cv.string,
vol.Optional(CONF_LOCAL_PORT, default=8943): cv.port,
vol.Required(CONF_REMOTE_IP): cv.string,
vol.Optional(CONF_REMOTE_PORT, default=2001): cv.port,
vol.Optional(CONF_RESOLVENAMES, default=False):
vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string,
vol.Optional(CONF_LOCAL_PORT, default=DEFAULT_LOCAL_PORT): cv.port,
vol.Optional(CONF_REMOTE_PORT, default=DEFAULT_REMOTE_PORT): cv.port,
vol.Optional(CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES):
vol.In(CONF_RESOLVENAMES_OPTIONS),
vol.Optional(CONF_USERNAME, default="Admin"): cv.string,
vol.Optional(CONF_PASSWORD, default=""): cv.string,
vol.Optional(CONF_DELAY, default=0.5): vol.Coerce(float),
vol.Optional(CONF_VARIABLES, default=False): cv.boolean,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): vol.Coerce(float),
vol.Optional(CONF_VARIABLES, default=DEFAULT_VARIABLES): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
@ -323,14 +338,18 @@ def _get_devices(device_type, keys):
metadata.update(device.SENSORNODE)
elif device_type == DISCOVER_BINARY_SENSORS:
metadata.update(device.BINARYNODE)
else:
metadata.update({None: device.ELEMENT})
params = _create_params_list(device, metadata, device_type)
if params:
if metadata:
# Generate options for 1...n elements with 1...n params
for channel in range(1, device.ELEMENT + 1):
_LOGGER.debug("Handling %s:%i", key, channel)
if channel in params:
for param in params[channel]:
for param, channels in metadata.items():
if param in HM_IGNORE_DISCOVERY_NODE:
continue
# add devices
_LOGGER.debug("Handling %s: %s", param, channels)
for channel in channels:
name = _create_ha_name(
name=device.NAME,
channel=channel,
@ -343,7 +362,7 @@ def _get_devices(device_type, keys):
ATTR_CHANNEL: channel
}
if param is not None:
device_dict.update({ATTR_PARAM: param})
device_dict[ATTR_PARAM] = param
# Add new device
try:
@ -352,54 +371,12 @@ def _get_devices(device_type, keys):
except vol.MultipleInvalid as err:
_LOGGER.error("Invalid device config: %s",
str(err))
else:
_LOGGER.debug("Channel %i not in params", channel)
else:
_LOGGER.debug("Got no params for %s", key)
_LOGGER.debug("%s autodiscovery: %s", device_type, str(device_arr))
return device_arr
def _create_params_list(hmdevice, metadata, device_type):
"""Create a list from HMDevice with all possible parameters in config."""
params = {}
merge = False
# use merge?
if device_type in (DISCOVER_SENSORS, DISCOVER_BINARY_SENSORS):
merge = True
# Search in sensor and binary metadata per elements
for channel in range(1, hmdevice.ELEMENT + 1):
param_chan = []
for node, meta_chan in metadata.items():
try:
# Is this attribute ignored?
if node in HM_IGNORE_DISCOVERY_NODE:
continue
if meta_chan == 'c' or meta_chan is None:
# Only channel linked data
param_chan.append(node)
elif channel == 1:
# First channel can have other data channel
param_chan.append(node)
except (TypeError, ValueError):
_LOGGER.error("Exception generating %s (%s)",
hmdevice.ADDRESS, str(metadata))
# default parameter is merge is off
if len(param_chan) == 0 and not merge:
param_chan.append(None)
# Add to channel
if len(param_chan) > 0:
params.update({channel: param_chan})
_LOGGER.debug("Create param list for %s with: %s", hmdevice.ADDRESS,
str(params))
return params
def _create_ha_name(name, channel, param):
"""Generate a unique object name."""
# HMDevice is a simple device
@ -484,12 +461,12 @@ def _hm_service_virtualkey(call):
hmdevice = HOMEMATIC.devices.get(address)
# if param exists for this device
if param not in hmdevice.ACTIONNODE:
if hmdevice is None or param not in hmdevice.ACTIONNODE:
_LOGGER.error("%s not datapoint in hm device %s", param, address)
return
# channel exists?
if channel > hmdevice.ELEMENT:
if channel in hmdevice.ACTIONNODE[param]:
_LOGGER.error("%i is not a channel in hm device %s", channel, address)
return
@ -743,18 +720,21 @@ class HMDevice(Entity):
self._hmdevice.ATTRIBUTENODE,
self._hmdevice.WRITENODE, self._hmdevice.EVENTNODE,
self._hmdevice.ACTIONNODE):
for node, channel in metadata.items():
for node, channels in metadata.items():
# Data is needed for this instance
if node in self._data:
# chan is current channel
if channel == 'c' or channel is None:
if len(channels) == 1:
channel = channels[0]
else:
channel = self._channel
# Prepare for subscription
try:
if int(channel) >= 0:
channels_to_sub.update({int(channel): True})
except (ValueError, TypeError):
_LOGGER("Invalid channel in metadata from %s",
_LOGGER.error("Invalid channel in metadata from %s",
self._name)
# Set callbacks

View File

@ -11,18 +11,20 @@ import mimetypes
import threading
import re
import ssl
import voluptuous as vol
import homeassistant.remote as rem
from homeassistant import util
from homeassistant.const import (
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE_JSON,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.core import split_entity_id
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
from homeassistant.components import persistent_notification
DOMAIN = 'http'
REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11')
@ -37,6 +39,7 @@ CONF_SSL_KEY = 'ssl_key'
CONF_CORS_ORIGINS = 'cors_allowed_origins'
DATA_API_PASSWORD = 'api_password'
NOTIFICATION_ID_LOGIN = 'http-login'
# TLS configuation follows the best-practice guidelines specified here:
# https://wiki.mozilla.org/Security/Server_Side_TLS
@ -106,7 +109,7 @@ def setup(hass, config):
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
development = str(conf.get(CONF_DEVELOPMENT, '')) == '1'
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY)
cors_origins = conf.get(CONF_CORS_ORIGINS, [])
@ -365,8 +368,8 @@ class HomeAssistantWSGI(object):
context.load_cert_chain(self.ssl_certificate, self.ssl_key)
self.server.ssl_adapter = ContextSSLAdapter(context)
threading.Thread(target=self.server.start, daemon=True,
name='WSGI-server').start()
threading.Thread(
target=self.server.start, daemon=True, name='WSGI-server').start()
def stop(self):
"""Stop the wsgi server."""
@ -391,10 +394,10 @@ class HomeAssistantWSGI(object):
resp = ex.get_response(request.environ)
if request.accept_mimetypes.accept_json:
resp.data = json.dumps({
"result": "error",
"message": str(ex),
'result': 'error',
'message': str(ex),
})
resp.mimetype = "application/json"
resp.mimetype = CONTENT_TYPE_JSON
return resp
def base_app(self, environ, start_response):
@ -403,11 +406,11 @@ class HomeAssistantWSGI(object):
response = self.dispatch_request(request)
if self.cors_origins:
cors_check = (environ.get("HTTP_ORIGIN") in self.cors_origins)
cors_check = (environ.get('HTTP_ORIGIN') in self.cors_origins)
cors_headers = ", ".join(ALLOWED_CORS_HEADERS)
if cors_check:
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = \
environ.get("HTTP_ORIGIN")
environ.get('HTTP_ORIGIN')
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS] = \
cors_headers
@ -425,7 +428,7 @@ class HomeAssistantWSGI(object):
# Strip out any cachebusting MD5 fingerprints
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
if fingerprinted:
environ['PATH_INFO'] = "{}.{}".format(*fingerprinted.groups())
environ['PATH_INFO'] = '{}.{}'.format(*fingerprinted.groups())
return app(environ, start_response)
@ -489,6 +492,10 @@ class HomeAssistantView(object):
if self.requires_auth and not authenticated:
_LOGGER.warning('Login attempt or request with an invalid '
'password from %s', request.remote_addr)
persistent_notification.create(
self.hass,
'Invalid password used from {}'.format(request.remote_addr),
'Login attempt failed', NOTIFICATION_ID_LOGIN)
raise Unauthorized()
request.authenticated = authenticated
@ -512,12 +519,9 @@ class HomeAssistantView(object):
def json(self, result, status_code=200):
"""Return a JSON response."""
msg = json.dumps(
result,
sort_keys=True,
cls=rem.JSONEncoder
).encode('UTF-8')
return self.Response(msg, mimetype="application/json",
status=status_code)
result, sort_keys=True, cls=rem.JSONEncoder).encode('UTF-8')
return self.Response(
msg, mimetype=CONTENT_TYPE_JSON, status=status_code)
def json_message(self, error, status_code=200):
"""Return a JSON message response."""

View File

@ -9,8 +9,7 @@ https://home-assistant.io/components/hvac.zwave/
import logging
from homeassistant.components.hvac import DOMAIN
from homeassistant.components.hvac import HvacDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS)
@ -23,12 +22,6 @@ REMOTEC = 0x5254
REMOTEC_ZXT_120 = 0x8377
REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120, 0)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
DEVICE_MAPPINGS = {
@ -50,8 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
return
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveHvac(value)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
@ -107,25 +100,29 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if int(value.data) != 0:
self._target_temperature = int(value.data)
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE)
.values()):
self._current_operation = value.data
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_operation_state = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@ -133,8 +130,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
self._current_operation_state)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == 112 and value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
@ -199,8 +197,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_temperature(self, temperature):
"""Set new target temperature."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.command_class != 67:
continue
if self._zxt_120:
@ -217,8 +216,9 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
if value.command_class == 68 and value.index == 0:
value.data = bytes(fan, 'utf-8')
break
@ -226,7 +226,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
@ -235,7 +235,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break

View File

@ -28,6 +28,7 @@ DEFAULT_PORT = 8086
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
DOMAIN = 'influxdb'
TIMEOUT = 5
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@ -69,7 +70,8 @@ def setup(hass, config):
try:
influx = InfluxDBClient(
host=host, port=port, username=username, password=password,
database=database, ssl=ssl, verify_ssl=verify_ssl)
database=database, ssl=ssl, verify_ssl=verify_ssl,
timeout=TIMEOUT)
influx.query("select * from /.*/ LIMIT 1;")
except exceptions.InfluxDBClientError as exc:
_LOGGER.error("Database host is not accessible due to '%s', please "

View File

@ -6,8 +6,14 @@ https://home-assistant.io/components/introduction/
"""
import logging
import voluptuous as vol
DOMAIN = 'introduction'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config=None):
"""Setup the introduction component."""

View File

@ -93,7 +93,8 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
vol.Coerce(tuple)),
ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
vol.Coerce(tuple)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=color_util.HASS_COLOR_MIN,
max=color_util.HASS_COLOR_MAX)),
ATTR_WHITE_VALUE: vol.All(int, vol.Range(min=0, max=255)),
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
ATTR_EFFECT: vol.In([EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE]),

View File

@ -17,8 +17,8 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.6.zip'
'#flux_led==0.6']
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.7.zip'
'#flux_led==0.7']
_LOGGER = logging.getLogger(__name__)

View File

@ -7,19 +7,35 @@ https://home-assistant.io/components/light.limitlessled/
# pylint: disable=abstract-method
import logging
import voluptuous as vol
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR,
ATTR_TRANSITION, EFFECT_COLORLOOP, EFFECT_WHITE, FLASH_LONG,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH,
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light)
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['limitlessled==1.0.2']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['limitlessled==1.0.0']
RGB_BOUNDARY = 40
DEFAULT_TRANSITION = 0
DEFAULT_PORT = 8899
DEFAULT_VERSION = 5
CONF_BRIDGES = 'bridges'
CONF_GROUPS = 'groups'
CONF_NUMBER = 'number'
CONF_TYPE = 'type'
CONF_VERSION = 'version'
DEFAULT_LED_TYPE = 'rgbw'
DEFAULT_PORT = 8899
DEFAULT_TRANSITION = 0
DEFAULT_VERSION = 5
LED_TYPE = ['rgbw', 'white']
RGB_BOUNDARY = 40
WHITE = [255, 255, 255]
SUPPORT_LIMITLESSLED_WHITE = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP |
@ -28,6 +44,25 @@ SUPPORT_LIMITLESSLED_RGB = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT |
SUPPORT_FLASH | SUPPORT_RGB_COLOR |
SUPPORT_TRANSITION)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_BRIDGES): vol.All(cv.ensure_list, [
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_VERSION,
default=DEFAULT_VERSION): cv.positive_int,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_GROUPS): vol.All(cv.ensure_list, [
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=DEFAULT_LED_TYPE):
vol.In(LED_TYPE),
vol.Required(CONF_NUMBER): cv.positive_int,
}
]),
},
]),
})
def rewrite_legacy(config):
"""Rewrite legacy configuration to new format."""

View File

@ -10,11 +10,12 @@ import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR,
Light)
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, SUPPORT_BRIGHTNESS,
SUPPORT_RGB_COLOR, SUPPORT_COLOR_TEMP, Light)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB)
CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB,
CONF_COLOR_TEMP)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
@ -31,6 +32,9 @@ CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template'
CONF_BRIGHTNESS_SCALE = 'brightness_scale'
CONF_COLOR_TEMP_STATE_TOPIC = 'color_temp_state_topic'
CONF_COLOR_TEMP_COMMAND_TOPIC = 'color_temp_command_topic'
CONF_COLOR_TEMP_VALUE_TEMPLATE = 'color_temp_value_template'
DEFAULT_NAME = 'MQTT Light'
DEFAULT_PAYLOAD_ON = 'ON'
@ -44,6 +48,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BRIGHTNESS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_BRIGHTNESS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
@ -70,12 +77,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_BRIGHTNESS_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
CONF_COLOR_TEMP_STATE_TOPIC,
CONF_COLOR_TEMP_COMMAND_TOPIC
)
},
{
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE)
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE)
},
config.get(CONF_QOS),
config.get(CONF_RETAIN),
@ -92,6 +102,7 @@ class MqttLight(Light):
"""MQTT light."""
# pylint: disable=too-many-arguments,too-many-instance-attributes
# pylint: disable=too-many-locals,too-many-branches
def __init__(self, hass, name, topic, templates, qos, retain, payload,
optimistic, brightness_scale):
"""Initialize MQTT light."""
@ -106,6 +117,8 @@ class MqttLight(Light):
optimistic or topic[CONF_RGB_STATE_TOPIC] is None
self._optimistic_brightness = (
optimistic or topic[CONF_BRIGHTNESS_STATE_TOPIC] is None)
self._optimistic_color_temp = (
optimistic or topic[CONF_COLOR_TEMP_STATE_TOPIC] is None)
self._brightness_scale = brightness_scale
self._state = False
self._supported_features = 0
@ -114,6 +127,9 @@ class MqttLight(Light):
self._supported_features |= (
topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None and
SUPPORT_BRIGHTNESS)
self._supported_features |= (
topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None and
SUPPORT_COLOR_TEMP)
for key, tpl in list(templates.items()):
if tpl is None:
@ -168,6 +184,21 @@ class MqttLight(Light):
else:
self._rgb = None
def color_temp_received(topic, payload, qos):
"""A new MQTT message for color temp has been received."""
self._color_temp = int(templates[CONF_COLOR_TEMP](payload))
self.update_ha_state()
if self._topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None:
mqtt.subscribe(
self._hass, self._topic[CONF_COLOR_TEMP_STATE_TOPIC],
color_temp_received, self._qos)
self._color_temp = 150
if self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
self._color_temp = 150
else:
self._color_temp = None
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
@ -178,6 +209,11 @@ class MqttLight(Light):
"""Return the RGB color value."""
return self._rgb
@property
def color_temp(self):
"""Return the color temperature in mired."""
return self._color_temp
@property
def should_poll(self):
"""No polling needed for a MQTT light."""
@ -230,6 +266,16 @@ class MqttLight(Light):
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
if ATTR_COLOR_TEMP in kwargs and \
self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
color_temp = int(kwargs[ATTR_COLOR_TEMP])
mqtt.publish(
self._hass, self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC],
color_temp, self._qos, self._retain)
if self._optimistic_color_temp:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
should_update = True
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload['on'], self._qos, self._retain)

View File

@ -50,19 +50,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.type != zwave.TYPE_BYTE:
if value.type != zwave.const.TYPE_BYTE:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
if node.has_command_class(zwave.COMMAND_CLASS_COLOR):
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR):
try:
add_devices([ZwaveColorLight(value)])
except ValueError as exception:
@ -195,8 +195,8 @@ class ZwaveColorLight(ZwaveDimmer):
raise ValueError("No matching color command found.")
for value_color_channels in value.node.get_values(
class_id=zwave.COMMAND_CLASS_COLOR, genre='System',
type="Int").values():
class_id=zwave.const.COMMAND_CLASS_SWITCH_COLOR,
genre='System', type="Int").values():
self._value_color_channels = value_color_channels
if self._value_color_channels is None:

View File

@ -1,28 +1,35 @@
"""
LIRC interface to receive signals from a infrared remote control.
This sensor will momentarily set state to various values as defined
in the .lintrc file which can be interpreted in home-assistant to
trigger various actions.
Sending signals to other IR receivers can be accomplished with the
shell_command component and the irsend command for now.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/lirc/
"""
# pylint: disable=import-error
import threading
import time
import logging
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
DOMAIN = "lirc"
REQUIREMENTS = ['python-lirc==1.2.1']
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:remote'
EVENT_IR_COMMAND_RECEIVED = 'ir_command_received'
BUTTON_NAME = 'button_name'
DOMAIN = 'lirc'
EVENT_IR_COMMAND_RECEIVED = 'ir_command_received'
ICON = 'mdi:remote'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup LIRC capability."""
@ -65,20 +72,19 @@ class LircInterface(threading.Thread):
def run(self):
"""Main loop of LIRC interface thread."""
import lirc
_LOGGER.debug('LIRC interface thread started')
_LOGGER.debug("LIRC interface thread started")
while not self.stopped.isSet():
try:
code = lirc.nextcode() # list; empty if no buttons pressed
except lirc.NextCodeError:
_LOGGER.warning('Encountered error reading '
'next code from LIRC')
_LOGGER.warning("Error reading next code from LIRC")
code = None
# interpret result from python-lirc
if code:
code = code[0]
_LOGGER.info('Got new LIRC code %s', code)
self.hass.bus.fire(EVENT_IR_COMMAND_RECEIVED,
{BUTTON_NAME: code})
_LOGGER.info("Got new LIRC code %s", code)
self.hass.bus.fire(
EVENT_IR_COMMAND_RECEIVED, {BUTTON_NAME: code})
else:
time.sleep(0.2)
lirc.deinit()

View File

@ -16,14 +16,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_DOOR_LOCK:
if value.command_class != zwave.const.COMMAND_CLASS_DOOR_LOCK:
return
if value.type != zwave.TYPE_BOOL:
if value.type != zwave.const.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)

View File

@ -4,6 +4,7 @@ Event parser and human readable log generator.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/logbook/
"""
import asyncio
import logging
from datetime import timedelta
from itertools import groupby
@ -20,6 +21,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
STATE_NOT_HOME, STATE_OFF, STATE_ON,
ATTR_HIDDEN)
from homeassistant.core import State, split_entity_id, DOMAIN as HA_DOMAIN
from homeassistant.util.async import run_callback_threadsafe
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'frontend']
@ -57,6 +59,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({
def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
run_callback_threadsafe(
hass.loop, async_log_entry, hass, name, message, domain, entity_id
).result()
def async_log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
data = {
ATTR_NAME: name,
@ -67,11 +76,12 @@ def log_entry(hass, name, message, domain=None, entity_id=None):
data[ATTR_DOMAIN] = domain
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.bus.fire(EVENT_LOGBOOK_ENTRY, data)
hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data)
def setup(hass, config):
"""Listen for download events to download files."""
@asyncio.coroutine
def log_message(service):
"""Handle sending notification message service calls."""
message = service.data[ATTR_MESSAGE]
@ -80,8 +90,8 @@ def setup(hass, config):
entity_id = service.data.get(ATTR_ENTITY_ID)
message.hass = hass
message = message.render()
log_entry(hass, name, message, domain, entity_id)
message = message.async_render()
async_log_entry(hass, name, message, domain, entity_id)
hass.wsgi.register_view(LogbookView(hass, config))

View File

@ -79,18 +79,11 @@ class LogitechMediaServer(object):
def _get_http_port(self):
"""Get http port from media server, it is used to get cover art."""
http_port = None
try:
http_port = self.query('pref', 'httpport', '?')
if not http_port:
_LOGGER.error("Unable to read data from server %s:%s",
_LOGGER.error("Failed to connect to server %s:%s",
self.host, self.port)
return
return http_port
except ConnectionError as ex:
_LOGGER.error("Failed to connect to server %s:%s - %s",
self.host, self.port, ex)
return
def create_players(self):
"""Create a list of SqueezeBoxDevices connected to the LMS."""
@ -104,6 +97,7 @@ class LogitechMediaServer(object):
def query(self, *parameters):
"""Send request and await response from server."""
try:
telnet = telnetlib.Telnet(self.host, self.port)
if self._username and self._password:
telnet.write('login {username} {password}\n'.format(
@ -118,6 +112,12 @@ class LogitechMediaServer(object):
.strip()
telnet.write(b'exit\n')
return urllib.parse.unquote(response)
except (OSError, ConnectionError) as error:
_LOGGER.error("Could not communicate with %s:%d: %s",
self.host,
self.port,
error)
return None
def get_player_status(self, player):
"""Get ithe status of a player."""
@ -128,6 +128,7 @@ class LogitechMediaServer(object):
# K (artwork_url): URL to remote artwork
tags = 'adK'
new_status = {}
try:
telnet = telnetlib.Telnet(self.host, self.port)
telnet.write('{player} status - 1 tags:{tags}\n'.format(
player=player,
@ -140,6 +141,11 @@ class LogitechMediaServer(object):
for item in response:
parts = urllib.parse.unquote(item).partition(':')
new_status[parts[0]] = parts[2]
except (OSError, ConnectionError) as error:
_LOGGER.error("Could not communicate with %s:%d: %s",
self.host,
self.port,
error)
return new_status

View File

@ -8,24 +8,31 @@ import logging
from datetime import timedelta
from urllib.parse import urlparse
import voluptuous as vol
import homeassistant.util as util
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL,
MediaPlayerDevice)
MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_CUSTOMIZE, STATE_OFF, STATE_PLAYING, STATE_PAUSED,
STATE_UNKNOWN)
STATE_UNKNOWN, CONF_NAME)
from homeassistant.loader import get_component
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/TheRealLink/pylgtv'
'/archive/v0.1.2.zip'
'#pylgtv==0.1.2']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
CONF_SOURCES = 'sources'
DEFAULT_NAME = 'LG WebOS Smart TV'
SUPPORT_WEBOSTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
@ -44,6 +51,17 @@ WEBOS_APPS_SHORT = {
'makotv': WEBOS_APP_MAKO
}
CUSTOMIZE_SCHEMA = vol.Schema({
vol.Optional(CONF_SOURCES):
vol.All(cv.ensure_list, [vol.In(WEBOS_APPS_SHORT)]),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -51,21 +69,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)
host = config.get(CONF_HOST)
if host is None:
_LOGGER.error('No host found in configuration')
_LOGGER.error("No host found in configuration")
return False
# Only act if we are not already configuring this host
if host in _CONFIGURING:
return
customize = config.get(CONF_CUSTOMIZE, {})
setup_tv(host, customize, hass, add_devices)
name = config.get(CONF_NAME)
customize = config.get(CONF_CUSTOMIZE)
setup_tv(host, name, customize, hass, add_devices)
def setup_tv(host, customize, hass, add_devices):
def setup_tv(host, name, customize, hass, add_devices):
"""Setup a phue bridge based on host parameter."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException
@ -79,15 +98,15 @@ def setup_tv(host, customize, hass, add_devices):
client.register()
except PyLGTVPairException:
_LOGGER.warning(
'Connected to LG WebOS TV at %s but not paired.', host)
"Connected to LG WebOS TV at %s but not paired", host)
return
except OSError:
_LOGGER.error('Unable to connect to host %s.', host)
_LOGGER.error("Unable to connect to host %s", host)
return
else:
# Not registered, request configuration.
_LOGGER.warning('LG WebOS TV at %s needs to be paired.', host)
request_configuration(host, customize, hass, add_devices)
request_configuration(host, name, customize, hass, add_devices)
return
# If we came here and configuring this host, mark as done.
@ -96,10 +115,10 @@ def setup_tv(host, customize, hass, add_devices):
configurator = get_component('configurator')
configurator.request_done(request_id)
add_devices([LgWebOSDevice(host, customize)])
add_devices([LgWebOSDevice(host, name, customize)])
def request_configuration(host, customize, hass, add_devices):
def request_configuration(host, name, customize, hass, add_devices):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
@ -112,7 +131,7 @@ def request_configuration(host, customize, hass, add_devices):
# pylint: disable=unused-argument
def lgtv_configuration_callback(data):
"""The actions to do when our configuration callback is called."""
setup_tv(host, customize, hass, add_devices)
setup_tv(host, name, customize, hass, add_devices)
_CONFIGURING[host] = configurator.request_config(
hass, 'LG WebOS TV', lgtv_configuration_callback,
@ -128,13 +147,13 @@ class LgWebOSDevice(MediaPlayerDevice):
"""Representation of a LG WebOS TV."""
# pylint: disable=too-many-public-methods
def __init__(self, host, customize):
def __init__(self, host, name, customize):
"""Initialize the webos device."""
from pylgtv import WebOsClient
self._client = WebOsClient(host)
self._customize = customize
self._name = 'LG WebOS TV Remote'
self._name = name
# Assume that the TV is not muted
self._muted = False
# Assume that the TV is in Play mode
@ -160,7 +179,7 @@ class LgWebOSDevice(MediaPlayerDevice):
self._app_list = {}
custom_sources = []
for source in self._customize.get('sources', []):
for source in self._customize.get(CONF_SOURCES, []):
app_id = WEBOS_APPS_SHORT.get(source, None)
if app_id:
custom_sources.append(app_id)

View File

@ -4,6 +4,7 @@ Support for MQTT message handling.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/
"""
import asyncio
import logging
import os
import socket
@ -14,8 +15,8 @@ import voluptuous as vol
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers.event import threaded_listener_factory
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
CONF_PLATFORM, CONF_SCAN_INTERVAL, CONF_VALUE_TEMPLATE)
@ -162,21 +163,28 @@ def publish_template(hass, topic, payload_template, qos=None, retain=None):
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
def async_subscribe(hass, topic, callback, qos=DEFAULT_QOS):
"""Subscribe to an MQTT topic."""
@asyncio.coroutine
def mqtt_topic_subscriber(event):
"""Match subscribed MQTT topic."""
if _match_topic(topic, event.data[ATTR_TOPIC]):
callback(event.data[ATTR_TOPIC], event.data[ATTR_PAYLOAD],
event.data[ATTR_QOS])
if not _match_topic(topic, event.data[ATTR_TOPIC]):
return
remove = hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED,
hass.async_run_job(callback, event.data[ATTR_TOPIC],
event.data[ATTR_PAYLOAD], event.data[ATTR_QOS])
async_remove = hass.bus.async_listen(EVENT_MQTT_MESSAGE_RECEIVED,
mqtt_topic_subscriber)
# Future: track subscriber count and unsubscribe in remove
MQTT_CLIENT.subscribe(topic, qos)
return remove
return async_remove
# pylint: disable=invalid-name
subscribe = threaded_listener_factory(async_subscribe)
def _setup_server(hass, config):

View File

@ -158,7 +158,8 @@ def setup(hass, config): # pylint: disable=too-many-locals
'No devices could be setup as gateways, check your configuration')
return False
for component in 'sensor', 'switch', 'light', 'binary_sensor', 'climate':
for component in ['sensor', 'switch', 'light', 'binary_sensor', 'climate',
'cover']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
@ -340,5 +341,7 @@ class MySensorsDeviceEntity(object):
set_req.V_LOCK_STATUS, set_req.V_TRIPPED):
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
elif value_type == set_req.V_DIMMER:
self._values[value_type] = int(value)
else:
self._values[value_type] = value

View File

@ -14,7 +14,7 @@ from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, CONF_STRUCTURE)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-nest==2.9.2']
REQUIREMENTS = ['python-nest==2.10.0']
DOMAIN = 'nest'

View File

@ -37,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config):
"""Setup the Netatmo devices."""
"""Set up the Netatmo devices."""
import lnetatmo
global NETATMO_AUTH
@ -47,7 +47,7 @@ def setup(hass, config):
config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD],
'read_station read_camera access_camera')
except HTTPError:
_LOGGER.error("Unable to connect to NatAtmo API")
_LOGGER.error("Unable to connect to Netatmo API")
return False
for component in 'camera', 'sensor':

View File

@ -42,7 +42,7 @@ PLATFORM_SCHEMA = vol.Schema({
NOTIFY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_TARGET): cv.string,
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_DATA): dict,
})

View File

@ -79,9 +79,6 @@ class AWSLambda(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
payload = {"message": message}

View File

@ -70,9 +70,6 @@ class AWSSNS(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
message_attributes = {k: {"StringValue": json.dumps(v),
"DataType": "String"}
for k, v in kwargs.items() if v}

View File

@ -69,9 +69,6 @@ class AWSSQS(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
message_body = {"message": message}

View File

@ -359,8 +359,6 @@ class HTML5NotificationService(BaseNotificationService):
if not targets:
targets = self.registrations.keys()
elif not isinstance(targets, list):
targets = [targets]
for target in targets:
info = self.registrations.get(target)

View File

@ -58,9 +58,6 @@ class MessageBirdNotificationService(BaseNotificationService):
_LOGGER.error('No target specified.')
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
try:
self.client.message_create(self.sender,

View File

@ -87,10 +87,6 @@ class PushBulletNotificationService(BaseNotificationService):
_LOGGER.info('Sent notification to self')
return
# Make list if not so
if not isinstance(targets, list):
targets = [targets]
# Main loop, Process all targets specified
for target in targets:
try:

View File

@ -18,45 +18,51 @@ REQUIREMENTS = ['pushetta==1.0.15']
CONF_CHANNEL_NAME = 'channel_name'
CONF_SEND_TEST_MSG = 'send_test_msg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_CHANNEL_NAME): cv.string,
vol.Optional(CONF_SEND_TEST_MSG, default=False): cv.boolean,
})
def get_service(hass, config):
"""Get the Pushetta notification service."""
from pushetta import Pushetta, exceptions
pushetta_service = PushettaNotificationService(config[CONF_API_KEY],
config[CONF_CHANNEL_NAME],
config[CONF_SEND_TEST_MSG])
try:
pushetta = Pushetta(config[CONF_API_KEY])
pushetta.pushMessage(config[CONF_CHANNEL_NAME],
"Home Assistant started")
except exceptions.TokenValidationError:
_LOGGER.error("Please check your access token")
return None
except exceptions.ChannelNotFoundError:
_LOGGER.error("Channel '%s' not found", config[CONF_CHANNEL_NAME])
return None
return PushettaNotificationService(config[CONF_API_KEY],
config[CONF_CHANNEL_NAME])
if pushetta_service.is_valid:
return pushetta_service
# pylint: disable=too-few-public-methods
class PushettaNotificationService(BaseNotificationService):
"""Implement the notification service for Pushetta."""
def __init__(self, api_key, channel_name):
def __init__(self, api_key, channel_name, send_test_msg):
"""Initialize the service."""
from pushetta import Pushetta
self._api_key = api_key
self._channel_name = channel_name
self.pushetta = Pushetta(self._api_key)
self.is_valid = True
self.pushetta = Pushetta(api_key)
if send_test_msg:
self.send_message("Home Assistant started")
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
from pushetta import exceptions
title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
try:
self.pushetta.pushMessage(self._channel_name,
"{} {}".format(title, message))
except exceptions.TokenValidationError:
_LOGGER.error("Please check your access token")
self.is_valid = False
except exceptions.ChannelNotFoundError:
_LOGGER.error("Channel '%s' not found", self._channel_name)
self.is_valid = False

View File

@ -61,7 +61,9 @@ class PushoverNotificationService(BaseNotificationService):
data['title'] = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
target = kwargs.get(ATTR_TARGET)
targets = kwargs.get(ATTR_TARGET)
for target in targets:
if target is not None:
data['device'] = target

View File

@ -75,8 +75,10 @@ class RestNotificationService(BaseNotificationService):
data[self._title_param_name] = kwargs.get(ATTR_TITLE,
ATTR_TITLE_DEFAULT)
if self._target_param_name is not None:
data[self._target_param_name] = kwargs.get(ATTR_TARGET)
if self._target_param_name is not None and ATTR_TARGET in kwargs:
# Target is a list as of 0.29 and we don't want to break existing
# integrations, so just return the first target in the list.
data[self._target_param_name] = kwargs[ATTR_TARGET][0]
if self._method == 'POST':
response = requests.post(self._resource, data=data, timeout=10)

View File

@ -11,9 +11,9 @@ notify:
example: 'Your Garage Door Friend'
target:
description: Target of the notification. Optional depending on the platform
description: An array of targets to send the notification to. Optional depending on the platform.
example: platform specific
data:
description: Extended information for notification. Optional depending on the platform
description: Extended information for notification. Optional depending on the platform.
example: platform specific

View File

@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.components.notify import (
PLATFORM_SCHEMA, BaseNotificationService)
ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import (
CONF_API_KEY, CONF_USERNAME, CONF_ICON)
import homeassistant.helpers.config_validation as cv
@ -68,16 +68,19 @@ class SlackNotificationService(BaseNotificationService):
"""Send a message to a user."""
import slacker
channel = kwargs.get('target') or self._default_channel
targets = kwargs.get(ATTR_TARGET, [self._default_channel])
data = kwargs.get('data')
attachments = data.get('attachments') if data else None
for target in targets:
try:
self.slack.chat.post_message(channel, message,
self.slack.chat.post_message(target, message,
as_user=self._as_user,
username=self._username,
icon_emoji=self._icon,
attachments=attachments,
link_names=True)
except slacker.Error as err:
_LOGGER.error("Could not send slack notification. Error: %s", err)
_LOGGER.error("Could not send slack notification. Error: %s",
err)

View File

@ -57,9 +57,6 @@ class TwilioSMSNotificationService(BaseNotificationService):
_LOGGER.info("At least 1 target is required")
return
if not isinstance(targets, list):
targets = [targets]
for target in targets:
self.client.messages.create(to=target, body=message,
from_=self.from_number)

View File

@ -27,7 +27,7 @@ DEPENDENCIES = ['ffmpeg']
REQUIREMENTS = [
'https://github.com/pvizeli/cloudapi/releases/download/1.0.2/'
'python-1.0.2.zip#cloud_api==1.0.2',
'ha-alpr==0.2']
'ha-alpr==0.3']
_LOGGER = logging.getLogger(__name__)
@ -68,7 +68,7 @@ DEFAULT_NAME = 'OpenAlpr'
DEFAULT_ENGINE = ENGINE_LOCAL
DEFAULT_RENDER = RENDER_FFMPEG
DEFAULT_BINARY = 'alpr'
DEFAULT_INTERVAL = 2
DEFAULT_INTERVAL = 10
DEFAULT_CONFIDENCE = 80.0
DEVICE_SCHEMA = vol.Schema({

View File

@ -12,9 +12,6 @@ from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.rollershutter import RollershutterDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
@ -32,10 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.index != 0:
return
@ -82,9 +79,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
"""Callback on data change for the registered node/value pair."""
# Position value
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Level':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
@property
@ -101,23 +99,26 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
def move_up(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.pressButton(value.value_id)
break
def move_down(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Up' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Close':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
@ -128,10 +129,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
def stop(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break

View File

@ -12,7 +12,7 @@ from homeassistant.helpers.entity import generate_entity_id
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = [
'https://github.com/sander76/powerviewApi/'
'archive/master.zip#powerviewApi==0.2']
'archive/cc6f75dd39160d4aaf46cb2ed9220136b924bcb4.zip#powerviewApi==0.2']
HUB_ADDRESS = 'address'

View File

@ -23,6 +23,7 @@ from homeassistant.helpers.script import Script
DOMAIN = "script"
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
DEPENDENCIES = ["group"]
CONF_SEQUENCE = "sequence"
@ -73,7 +74,8 @@ def toggle(hass, entity_id):
def setup(hass, config):
"""Load the scripts from the configuration."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_SCRIPTS)
def service_handler(service):
"""Execute a service call to script.<script name>."""
@ -124,7 +126,7 @@ class ScriptEntity(ToggleEntity):
def __init__(self, hass, object_id, name, sequence):
"""Initialize the script."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self.script = Script(hass, sequence, name, self.update_ha_state)
self.script = Script(hass, sequence, name, self.async_update_ha_state)
@property
def should_poll(self):

View File

@ -1,5 +1,5 @@
"""
The arest sensor will consume an exposed aREST API of a device.
Support for an exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arest/
@ -8,30 +8,48 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME,
CONF_RESOURCE, CONF_MONITORED_VARIABLES)
CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_RESOURCE,
CONF_MONITORED_VARIABLES, CONF_NAME, STATE_UNKNOWN)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import template
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
CONF_FUNCTIONS = 'functions'
CONF_PINS = 'pins'
DEFAULT_NAME = 'aREST sensor'
PIN_VARIABLE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PINS, default={}):
vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}),
vol.Optional(CONF_MONITORED_VARIABLES, default={}):
vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}),
})
# pylint: disable=too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST sensor."""
resource = config.get(CONF_RESOURCE)
var_conf = config.get(CONF_MONITORED_VARIABLES)
pins = config.get('pins', None)
if resource is None:
_LOGGER.error('Not all required config keys present: %s',
CONF_RESOURCE)
return False
pins = config.get(CONF_PINS)
try:
response = requests.get(resource, timeout=10).json()
@ -52,11 +70,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if value_template is None:
return lambda value: value
value_template = template.Template(value_template, hass)
value_template.hass = hass
def _render(value):
try:
return value_template.render({'value': value})
return value_template.async_render({'value': value})
except TemplateError:
_LOGGER.exception('Error parsing value')
return value
@ -66,33 +84,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
if var_conf is not None:
for variable in var_conf:
if variable['name'] not in response['variables']:
_LOGGER.error('Variable: "%s" does not exist',
variable['name'])
for variable, var_data in var_conf.items():
if variable not in response['variables']:
_LOGGER.error("Variable: '%s' does not exist", variable)
continue
renderer = make_renderer(variable.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(arest,
resource,
config.get('name', response['name']),
variable['name'],
variable=variable['name'],
unit_of_measurement=variable.get(
ATTR_UNIT_OF_MEASUREMENT),
renderer = make_renderer(var_data.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
variable, variable=variable,
unit_of_measurement=var_data.get(CONF_UNIT_OF_MEASUREMENT),
renderer=renderer))
if pins is not None:
for pinnum, pin in pins.items():
renderer = make_renderer(pin.get(CONF_VALUE_TEMPLATE))
dev.append(ArestSensor(ArestData(resource, pinnum),
resource,
config.get('name', response['name']),
pin.get('name'),
pin=pinnum,
unit_of_measurement=pin.get(
ATTR_UNIT_OF_MEASUREMENT),
renderer=renderer))
dev.append(ArestSensor(
ArestData(resource, pinnum), resource,
config.get(CONF_NAME, response[CONF_NAME]), pin.get(CONF_NAME),
pin=pinnum, unit_of_measurement=pin.get(
CONF_UNIT_OF_MEASUREMENT), renderer=renderer))
add_devices(dev)
@ -106,18 +117,17 @@ class ArestSensor(Entity):
"""Initialize the sensor."""
self.arest = arest
self._resource = resource
self._name = '{} {}'.format(location.title(), name.title()) \
or DEVICE_DEFAULT_NAME
self._name = '{} {}'.format(location.title(), name.title())
self._variable = variable
self._pin = pin
self._state = 'n/a'
self._state = STATE_UNKNOWN
self._unit_of_measurement = unit_of_measurement
self._renderer = renderer
self.update()
if self._pin is not None:
request = requests.get('{}/mode/{}/i'.format
(self._resource, self._pin), timeout=10)
request = requests.get(
'{}/mode/{}/i'.format(self._resource, self._pin), timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
@ -139,15 +149,19 @@ class ArestSensor(Entity):
if 'error' in values:
return values['error']
value = self._renderer(values.get('value',
values.get(self._variable,
'N/A')))
value = self._renderer(
values.get('value', values.get(self._variable, STATE_UNKNOWN)))
return value
def update(self):
"""Get the latest data from aREST API."""
self.arest.update()
@property
def available(self):
"""Could the device be accessed during the last update call."""
return self.arest.available
# pylint: disable=too-few-public-methods
class ArestData(object):
@ -158,6 +172,7 @@ class ArestData(object):
self._resource = resource
self._pin = pin
self.data = {}
self.available = True
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
@ -179,7 +194,8 @@ class ArestData(object):
response = requests.get('{}/digital/{}'.format(
self._resource, self._pin), timeout=10)
self.data = {'value': response.json()['return_value']}
self.available = True
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device %s. Is device offline?",
self._resource)
self.data = {'error': 'error fetching'}
self.available = False

View File

@ -1,8 +1,8 @@
"""
Support for Forecast.io weather service.
Support for Dark Sky weather service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.forecast/
https://home-assistant.io/components/sensor.darksky/
"""
import logging
from datetime import timedelta
@ -18,15 +18,14 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-forecastio==1.3.4']
REQUIREMENTS = ['python-forecastio==1.3.5']
_LOGGER = logging.getLogger(__name__)
CONF_UNITS = 'units'
CONF_UPDATE_INTERVAL = 'update_interval'
DEFAULT_NAME = 'Forecast.io'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
DEFAULT_NAME = 'Dark Sky'
# Sensor types are defined like so:
# Name, si unit, us unit, ca unit, uk unit, uk2 unit
@ -84,12 +83,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2'])
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']),
vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=120)): (
vol.All(cv.time_period, cv.positive_timedelta)),
})
# pylint: disable=too-many-arguments
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Forecast.io sensor."""
"""Setup the Dark Sky sensor."""
# Validate the configuration
if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
@ -105,9 +107,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# Create a data fetcher to support all of the configured sensors. Then make
# the first call to init the data and confirm we can connect.
try:
forecast_data = ForeCastData(
config.get(CONF_API_KEY, None), hass.config.latitude,
hass.config.longitude, units)
forecast_data = DarkSkyData(
api_key=config.get(CONF_API_KEY, None),
latitude=hass.config.latitude,
longitude=hass.config.longitude,
units=units,
interval=config.get(CONF_UPDATE_INTERVAL))
forecast_data.update_currently()
except ValueError as error:
_LOGGER.error(error)
@ -117,14 +122,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors = []
for variable in config[CONF_MONITORED_CONDITIONS]:
sensors.append(ForeCastSensor(forecast_data, variable, name))
sensors.append(DarkSkySensor(forecast_data, variable, name))
add_devices(sensors)
# pylint: disable=too-few-public-methods
class ForeCastSensor(Entity):
"""Implementation of a Forecast.io sensor."""
class DarkSkySensor(Entity):
"""Implementation of a Dark Sky sensor."""
def __init__(self, forecast_data, sensor_type, name):
"""Initialize the sensor."""
@ -175,10 +180,10 @@ class ForeCastSensor(Entity):
# pylint: disable=too-many-branches,too-many-statements
def update(self):
"""Get the latest data from Forecast.io and updates the states."""
"""Get the latest data from Dark Sky and updates the states."""
# Call the API for new forecast data. Each sensor will re-trigger this
# same exact call, but thats fine. We cache results for a short period
# of time to prevent hitting API limits. Note that forecast.io will
# same exact call, but that's fine. We cache results for a short period
# of time to prevent hitting API limits. Note that Dark Sky will
# charge users for too many calls in 1 day, so take care when updating.
self.forecast_data.update()
self.update_unit_of_measurement()
@ -192,7 +197,8 @@ class ForeCastSensor(Entity):
hourly = self.forecast_data.data_hourly
self._state = getattr(hourly, 'summary', '')
elif self.type in ['daily_summary',
'temperature_min', 'temperature_max',
'temperature_min',
'temperature_max',
'apparent_temperature_min',
'apparent_temperature_max',
'precip_intensity_max']:
@ -242,12 +248,11 @@ def convert_to_camel(data):
return components[0] + "".join(x.title() for x in components[1:])
class ForeCastData(object):
"""Gets the latest data from Forecast.io."""
class DarkSkyData(object):
"""Get the latest data from Darksky."""
# pylint: disable=too-many-instance-attributes
def __init__(self, api_key, latitude, longitude, units):
def __init__(self, api_key, latitude, longitude, units, interval):
"""Initialize the data object."""
self._api_key = api_key
self.latitude = latitude
@ -261,36 +266,38 @@ class ForeCastData(object):
self.data_hourly = None
self.data_daily = None
# Apply throttling to methods using configured interval
self.update = Throttle(interval)(self._update)
self.update_currently = Throttle(interval)(self._update_currently)
self.update_minutely = Throttle(interval)(self._update_minutely)
self.update_hourly = Throttle(interval)(self._update_hourly)
self.update_daily = Throttle(interval)(self._update_daily)
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from Forecast.io."""
def _update(self):
"""Get the latest data from Dark Sky."""
import forecastio
try:
self.data = forecastio.load_forecast(
self._api_key, self.latitude, self.longitude, units=self.units)
except (ConnectError, HTTPError, Timeout, ValueError) as error:
raise ValueError("Unable to init Forecast.io. - %s", error)
raise ValueError("Unable to init Dark Sky. %s", error)
self.unit_system = self.data.json['flags']['units']
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_currently(self):
def _update_currently(self):
"""Update currently data."""
self.data_currently = self.data.currently()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_minutely(self):
def _update_minutely(self):
"""Update minutely data."""
self.data_minutely = self.data.minutely()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_hourly(self):
def _update_hourly(self):
"""Update hourly data."""
self.data_hourly = self.data.hourly()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update_daily(self):
def _update_daily(self):
"""Update daily data."""
self.data_daily = self.data.daily()

View File

@ -202,8 +202,7 @@ class EmonCmsData(object):
"""Get the latest data."""
try:
req = requests.get(self._url, params={"apikey": self._apikey},
verify=False, allow_redirects=True,
timeout=5)
allow_redirects=True, timeout=5)
except requests.exceptions.RequestException as exception:
_LOGGER.error(exception)
return

View File

@ -1,5 +1,5 @@
"""
Support gahtering system information of hosts which are running glances.
Support gathering system information of hosts which are running glances.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.glances/
@ -24,6 +24,8 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'Glances'
DEFAULT_PORT = '61208'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],
@ -34,11 +36,11 @@ SENSOR_TYPES = {
'swap_use_percent': ['Swap Use', '%'],
'swap_use': ['Swap Use', 'GiB'],
'swap_free': ['Swap Free', 'GiB'],
'processor_load': ['CPU Load', None],
'process_running': ['Running', None],
'process_total': ['Total', None],
'process_thread': ['Thread', None],
'process_sleeping': ['Sleeping', None]
'processor_load': ['CPU Load', '15 min'],
'process_running': ['Running', 'Count'],
'process_total': ['Total', 'Count'],
'process_thread': ['Thread', 'Count'],
'process_sleeping': ['Sleeping', 'Count']
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -50,10 +52,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Glances sensor."""
@ -66,7 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
response = requests.get(url, timeout=10)
if not response.ok:
_LOGGER.error('Response status is "%s"', response.status_code)
_LOGGER.error("Response status is '%s'", response.status_code)
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", url)

View File

@ -36,14 +36,14 @@ TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ORIGIN): cv.string,
vol.Required(CONF_DESTINATION): cv.string,
vol.Required(CONF_DATA): cv.isfile,
vol.Required(CONF_DATA): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
# pylint: disable=too-many-locals
def get_next_departure(sched, start_station_id, end_station_id):
"""Get the next departure for the given sched."""
"""Get the next departure for the given schedule."""
origin_station = sched.stops_by_id(start_station_id)[0]
destination_station = sched.stops_by_id(end_station_id)[0]
@ -147,7 +147,7 @@ def get_next_departure(sched, start_station_id, end_station_id):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Get the GTFS sensor."""
"""Set up the GTFS sensor."""
gtfs_dir = hass.config.path(DEFAULT_PATH)
data = config.get(CONF_DATA)
origin = config.get(CONF_ORIGIN)

View File

@ -18,7 +18,8 @@ DEPENDENCIES = ['homematic']
HM_STATE_HA_CAST = {
"RotaryHandleSensor": {0: "closed", 1: "tilted", 2: "open"},
"WaterSensor": {0: "dry", 1: "wet", 2: "water"}
"WaterSensor": {0: "dry", 1: "wet", 2: "water"},
"CO2Sensor": {0: "normal", 1: "added", 2: "strong"},
}
HM_UNIT_HA_CAST = {
@ -38,6 +39,7 @@ HM_UNIT_HA_CAST = {
"WIND_DIRECTION_RANGE": "°",
"SUNSHINEDURATION": "#",
"AIR_PRESSURE": "hPa",
"FREQUENCY": "Hz",
}

View File

@ -104,7 +104,7 @@ class EmailReader:
self.connection.select()
if len(self._unread_ids) == 0:
search = "SINCE {0:%d-%b-%y}".format(datetime.date.today())
search = "SINCE {0:%d-%b-%Y}".format(datetime.date.today())
if self._last_id is not None:
search = "UID {}:*".format(self._last_id)

View File

@ -15,7 +15,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyloopenergy==0.0.14']
REQUIREMENTS = ['pyloopenergy==0.0.15']
CONF_ELEC = 'electricity'
CONF_GAS = 'gas'

View File

@ -11,7 +11,6 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -66,6 +65,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
})
map_sv_types[pres.S_LIGHT_LEVEL].append(set_req.V_LEVEL)
if float(gateway.protocol_version) >= 2.0:
map_sv_types.update({
pres.S_INFO: [set_req.V_TEXT],
pres.S_GAS: [set_req.V_FLOW, set_req.V_VOLUME],
pres.S_GPS: [set_req.V_POSITION],
pres.S_WATER_QUALITY: [set_req.V_TEMP, set_req.V_PH,
set_req.V_ORP, set_req.V_EC]
})
map_sv_types[pres.S_CUSTOM].append(set_req.V_CUSTOM)
map_sv_types[pres.S_POWER].extend(
[set_req.V_VAR, set_req.V_VA, set_req.V_POWER_FACTOR])
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsSensor))
@ -74,6 +85,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MySensorsSensor(mysensors.MySensorsDeviceEntity, Entity):
"""Representation of a MySensors Sensor child node."""
@property
def force_update(self):
"""Return True if state updates should be forced.
If True, a state change will be triggered anytime the state property is
updated, not just when the value changes.
"""
return True
@property
def state(self):
"""Return the state of the device."""
@ -104,4 +124,11 @@ class MySensorsSensor(mysensors.MySensorsDeviceEntity, Entity):
return self._values[
set_req.V_UNIT_PREFIX]
unit_map.update({set_req.V_PERCENTAGE: '%'})
if float(self.gateway.protocol_version) >= 2.0:
unit_map.update({
set_req.V_ORP: 'mV',
set_req.V_EC: 'μS/cm',
set_req.V_VAR: 'var',
set_req.V_VA: 'VA',
})
return unit_map.get(self.value_type)

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