Merge pull request #1821 from home-assistant/service-validations

Service validations
This commit is contained in:
Paulus Schoutsen 2016-04-13 22:38:14 -07:00
commit f5ee3e6e13
21 changed files with 257 additions and 133 deletions

View File

@ -7,12 +7,15 @@ https://home-assistant.io/components/alarm_control_panel/
import logging
import os
import voluptuous as vol
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@ -38,6 +41,11 @@ ATTR_TO_PROPERTY = [
ATTR_CODE_FORMAT
]
ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})
def setup(hass, config):
"""Track states and offer events for sensors."""
@ -51,10 +59,7 @@ def setup(hass, config):
"""Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
code = None
else:
code = service.data[ATTR_CODE]
code = service.data.get(ATTR_CODE)
method = SERVICE_TO_METHOD[service.service]
@ -68,8 +73,8 @@ def setup(hass, config):
for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, alarm_service_handler,
descriptions.get(service))
descriptions.get(service),
schema=ALARM_SERVICE_SCHEMA)
return True

View File

@ -4,10 +4,18 @@ Provides functionality to launch a web browser on the host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/browser/
"""
import voluptuous as vol
DOMAIN = "browser"
SERVICE_BROWSE_URL = "browse_url"
ATTR_URL = 'url'
ATTR_URL_DEFAULT = 'https://www.google.com'
SERVICE_BROWSE_URL_SCHEMA = vol.Schema({
vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url,
})
def setup(hass, config):
"""Listen for browse_url events."""
@ -15,8 +23,7 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
lambda service:
webbrowser.open(
service.data.get(
'url', 'https://www.google.com')))
webbrowser.open(service.data[ATTR_URL]),
schema=SERVICE_BROWSE_URL_SCHEMA)
return True

View File

@ -8,9 +8,12 @@ import logging
import re
import warnings
import voluptuous as vol
from homeassistant import core
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
DOMAIN = "conversation"
@ -18,6 +21,10 @@ SERVICE_PROCESS = "process"
ATTR_TEXT = "text"
SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REQUIREMENTS = ['fuzzywuzzy==0.8.0']
@ -32,11 +39,7 @@ def setup(hass, config):
def process(service):
"""Parse text into commands."""
if ATTR_TEXT not in service.data:
logger.error("Received process service call without a text")
return
text = service.data[ATTR_TEXT].lower()
text = service.data[ATTR_TEXT]
match = REGEX_TURN_COMMAND.match(text)
if not match:
@ -67,6 +70,6 @@ def setup(hass, config):
logger.error(
'Got unsupported command %s from text %s', command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
return True

View File

@ -10,8 +10,10 @@ import re
import threading
import requests
import voluptuous as vol
from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
from homeassistant.util import sanitize_filename
DOMAIN = "downloader"
@ -21,6 +23,11 @@ SERVICE_DOWNLOAD_FILE = "download_file"
ATTR_URL = "url"
ATTR_SUBDIR = "subdir"
SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({
vol.Required(ATTR_URL): vol.Url,
vol.Optional(ATTR_SUBDIR): cv.string,
})
CONF_DOWNLOAD_DIR = 'download_dir'
@ -48,10 +55,6 @@ def setup(hass, config):
def download_file(service):
"""Start thread to download file specified in the URL."""
if ATTR_URL not in service.data:
logger.error("Service called but 'url' parameter not specified.")
return
def do_download():
"""Download the file."""
try:
@ -127,7 +130,7 @@ def setup(hass, config):
threading.Thread(target=do_download).start()
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE,
download_file)
hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, download_file,
schema=SERVICE_DOWNLOAD_FILE_SCHEMA)
return True

View File

@ -7,10 +7,13 @@ at https://home-assistant.io/components/garage_door/
import logging
import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
ATTR_ENTITY_ID)
@ -29,6 +32,10 @@ DISCOVERY_PLATFORMS = {
wink.DISCOVER_GARAGE_DOORS: 'wink'
}
GARAGE_DOOR_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
_LOGGER = logging.getLogger(__name__)
@ -73,10 +80,11 @@ def setup(hass, config):
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_OPEN, handle_garage_door_service,
descriptions.get(SERVICE_OPEN))
descriptions.get(SERVICE_OPEN),
schema=GARAGE_DOOR_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_CLOSE, handle_garage_door_service,
descriptions.get(SERVICE_CLOSE))
descriptions.get(SERVICE_CLOSE),
schema=GARAGE_DOOR_SERVICE_SCHEMA)
return True

View File

@ -7,8 +7,10 @@ https://home-assistant.io/components/ifttt/
import logging
import requests
import voluptuous as vol
from homeassistant.helpers import validate_config
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -23,6 +25,13 @@ ATTR_VALUE3 = 'value3'
REQUIREMENTS = ['pyfttt==0.3']
SERVICE_TRIGGER_SCHEMA = vol.Schema({
vol.Required(ATTR_EVENT): cv.string,
vol.Optional(ATTR_VALUE1): cv.string,
vol.Optional(ATTR_VALUE2): cv.string,
vol.Optional(ATTR_VALUE3): cv.string,
})
def trigger(hass, event, value1=None, value2=None, value3=None):
"""Trigger a Maker IFTTT recipe."""
@ -44,12 +53,10 @@ def setup(hass, config):
def trigger_service(call):
"""Handle IFTTT trigger service calls."""
event = call.data.get(ATTR_EVENT)
event = call.data[ATTR_EVENT]
value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2)
value3 = call.data.get(ATTR_VALUE3)
if event is None:
return
try:
import pyfttt as pyfttt
@ -57,6 +64,7 @@ def setup(hass, config):
except requests.exceptions.RequestException:
_LOGGER.exception("Error communicating with IFTTT")
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service)
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service,
schema=SERVICE_TRIGGER_SCHEMA)
return True

View File

@ -6,8 +6,11 @@ at https://home-assistant.io/components/input_boolean/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
@ -22,6 +25,10 @@ CONF_NAME = "name"
CONF_INITIAL = "initial"
CONF_ICON = "icon"
TOGGLE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id):
"""Test if input_boolean is True."""
@ -75,8 +82,10 @@ def setup(hass, config):
else:
input_b.turn_off()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service,
schema=TOGGLE_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service,
schema=TOGGLE_SERVICE_SCHEMA)
component.add_entities(entities)

View File

@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_select/
"""
import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
@ -25,6 +28,11 @@ ATTR_OPTIONS = 'options'
SERVICE_SELECT_OPTION = 'select_option'
SERVICE_SELECT_OPTION_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_OPTION): cv.string,
})
def select_option(hass, entity_id, option):
"""Set input_select to False."""
@ -79,10 +87,11 @@ def setup(hass, config):
target_inputs = component.extract_from_service(call)
for input_select in target_inputs:
input_select.select_option(call.data.get(ATTR_OPTION))
input_select.select_option(call.data[ATTR_OPTION])
hass.services.register(DOMAIN, SERVICE_SELECT_OPTION,
select_option_service)
select_option_service,
schema=SERVICE_SELECT_OPTION_SCHEMA)
component.add_entities(entities)

View File

@ -6,7 +6,10 @@ at https://home-assistant.io/components/input_slider/
"""
import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
@ -29,6 +32,11 @@ ATTR_STEP = 'step'
SERVICE_SELECT_VALUE = 'select_value'
SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_VALUE): vol.Coerce(int),
})
def select_value(hass, entity_id, value):
"""Set input_slider to value."""
@ -81,10 +89,11 @@ def setup(hass, config):
target_inputs = component.extract_from_service(call)
for input_slider in target_inputs:
input_slider.select_value(call.data.get(ATTR_VALUE))
input_slider.select_value(call.data[ATTR_VALUE])
hass.services.register(DOMAIN, SERVICE_SELECT_VALUE,
select_value_service)
select_value_service,
schema=SERVICE_SELECT_VALUE_SCHEMA)
component.add_entities(entities)

View File

@ -4,6 +4,8 @@ Provides functionality to emulate keyboard presses on host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/keyboard/
"""
import voluptuous as vol
from homeassistant.const import (
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
@ -12,6 +14,8 @@ from homeassistant.const import (
DOMAIN = "keyboard"
REQUIREMENTS = ['pyuserinput==0.1.9']
TAP_KEY_SCHEMA = vol.Schema({})
def volume_up(hass):
"""Press the keyboard button for volume up."""
@ -52,26 +56,31 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
lambda service:
keyboard.tap_key(keyboard.volume_up_key))
keyboard.tap_key(keyboard.volume_up_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN,
lambda service:
keyboard.tap_key(keyboard.volume_down_key))
keyboard.tap_key(keyboard.volume_down_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE,
lambda service:
keyboard.tap_key(keyboard.volume_mute_key))
keyboard.tap_key(keyboard.volume_mute_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE,
lambda service:
keyboard.tap_key(keyboard.media_play_pause_key))
keyboard.tap_key(keyboard.media_play_pause_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
lambda service:
keyboard.tap_key(keyboard.media_next_track_key))
keyboard.tap_key(keyboard.media_next_track_key),
schema=TAP_KEY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
lambda service:
keyboard.tap_key(keyboard.media_prev_track_key))
keyboard.tap_key(keyboard.media_prev_track_key),
schema=TAP_KEY_SCHEMA)
return True

View File

@ -8,10 +8,13 @@ from datetime import timedelta
import logging
import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
@ -33,6 +36,11 @@ DISCOVERY_PLATFORMS = {
verisure.DISCOVER_LOCKS: 'verisure'
}
LOCK_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})
_LOGGER = logging.getLogger(__name__)
@ -75,10 +83,7 @@ def setup(hass, config):
"""Handle calls to the lock services."""
target_locks = component.extract_from_service(service)
if ATTR_CODE not in service.data:
code = None
else:
code = service.data[ATTR_CODE]
code = service.data.get(ATTR_CODE)
for item in target_locks:
if service.service == SERVICE_LOCK:
@ -92,10 +97,11 @@ def setup(hass, config):
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_UNLOCK, handle_lock_service,
descriptions.get(SERVICE_UNLOCK))
descriptions.get(SERVICE_UNLOCK),
schema=LOCK_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_LOCK, handle_lock_service,
descriptions.get(SERVICE_LOCK))
descriptions.get(SERVICE_LOCK),
schema=LOCK_SERVICE_SCHEMA)
return True

View File

@ -9,6 +9,8 @@ import re
from datetime import timedelta
from itertools import groupby
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, sun
from homeassistant.const import (
@ -18,6 +20,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.core import State
from homeassistant.helpers.entity import split_entity_id
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
DOMAIN = "logbook"
DEPENDENCIES = ['recorder', 'http']
@ -39,6 +42,13 @@ ATTR_MESSAGE = 'message'
ATTR_DOMAIN = 'domain'
ATTR_ENTITY_ID = 'entity_id'
LOG_MESSAGE_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_DOMAIN): cv.slug,
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
})
def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
@ -58,19 +68,17 @@ def setup(hass, config):
"""Listen for download events to download files."""
def log_message(service):
"""Handle sending notification message service calls."""
message = service.data.get(ATTR_MESSAGE)
name = service.data.get(ATTR_NAME)
domain = service.data.get(ATTR_DOMAIN, None)
entity_id = service.data.get(ATTR_ENTITY_ID, None)
if not message or not name:
return
message = service.data[ATTR_MESSAGE]
name = service.data[ATTR_NAME]
domain = service.data.get(ATTR_DOMAIN)
entity_id = service.data.get(ATTR_ENTITY_ID)
message = template.render(hass, message)
log_entry(hass, name, message, domain, entity_id)
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
hass.services.register(DOMAIN, 'log', log_message)
hass.services.register(DOMAIN, 'log', log_message,
schema=LOG_MESSAGE_SCHEMA)
return True

View File

@ -8,11 +8,13 @@ from functools import partial
import logging
import os
import voluptuous as vol
import homeassistant.bootstrap as bootstrap
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_per_platform, template
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
DOMAIN = "notify"
@ -32,6 +34,13 @@ ATTR_DATA = 'data'
SERVICE_NOTIFY = "notify"
NOTIFY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE, default=ATTR_TITLE_DEFAULT): cv.string,
vol.Optional(ATTR_TARGET): cv.string,
vol.Optional(ATTR_DATA): dict, # nobody seems to be using this (yet)
})
_LOGGER = logging.getLogger(__name__)
@ -71,13 +80,7 @@ def setup(hass, config):
def notify_message(notify_service, call):
"""Handle sending notification message service calls."""
message = call.data.get(ATTR_MESSAGE)
if message is None:
_LOGGER.error(
'Received call to %s without attribute %s',
call.service, ATTR_MESSAGE)
return
message = call.data[ATTR_MESSAGE]
title = template.render(
hass, call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT))
@ -91,7 +94,8 @@ def setup(hass, config):
service_call_handler = partial(notify_message, notify_service)
service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY)
hass.services.register(DOMAIN, service_notify, service_call_handler,
descriptions.get(SERVICE_NOTIFY))
descriptions.get(SERVICE_NOTIFY),
schema=NOTIFY_SERVICE_SCHEMA)
success = True
return success

View File

@ -7,10 +7,13 @@ https://home-assistant.io/components/rollershutter/
import os
import logging
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.components import group
from homeassistant.const import (
SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP,
@ -33,6 +36,10 @@ _LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
ROLLERSHUTTER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_open(hass, entity_id=None):
"""Return if the roller shutter is open based on the statemachine."""
@ -85,14 +92,16 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_MOVE_UP,
handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_UP))
descriptions.get(SERVICE_MOVE_UP),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_MOVE_DOWN,
handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_DOWN))
descriptions.get(SERVICE_MOVE_DOWN),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_STOP,
handle_rollershutter_service,
descriptions.get(SERVICE_STOP))
descriptions.get(SERVICE_STOP),
schema=ROLLERSHUTTER_SERVICE_SCHEMA)
return True

View File

@ -7,9 +7,12 @@ https://home-assistant.io/components/scene/
import logging
from collections import namedtuple
import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, CONF_PLATFORM)
from homeassistant.helpers import extract_domain_configs
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@ -19,6 +22,10 @@ STATE = 'scening'
CONF_ENTITIES = "entities"
SCENE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
@ -61,7 +68,8 @@ def setup(hass, config):
for scene in target_scenes:
scene.activate()
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service,
schema=SCENE_SERVICE_SCHEMA)
return True

View File

@ -109,6 +109,11 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(DOMAIN): {cv.slug: _SCRIPT_ENTRY_SCHEMA}
}, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema({})
SCRIPT_TURN_ONOFF_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id):
"""Return if the switch is on based on the statemachine."""
@ -149,7 +154,8 @@ def setup(hass, config):
alias = cfg.get(CONF_ALIAS, object_id)
script = Script(object_id, alias, cfg[CONF_SEQUENCE])
component.add_entities((script,))
hass.services.register(DOMAIN, object_id, service_handler)
hass.services.register(DOMAIN, object_id, service_handler,
schema=SCRIPT_SERVICE_SCHEMA)
def turn_on_service(service):
"""Call a service to turn script on."""
@ -168,10 +174,12 @@ def setup(hass, config):
for script in component.extract_from_service(service):
script.toggle()
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service)
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
return True

View File

@ -7,26 +7,26 @@ https://home-assistant.io/components/shell_command/
import logging
import subprocess
from homeassistant.util import slugify
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
DOMAIN = 'shell_command'
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
cv.slug: cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
SHELL_COMMAND_SCHEMA = vol.Schema({})
def setup(hass, config):
"""Setup the shell_command component."""
conf = config.get(DOMAIN)
if not isinstance(conf, dict):
_LOGGER.error('Expected configuration to be a dictionary')
return False
for name in conf.keys():
if name != slugify(name):
_LOGGER.error('Invalid service name: %s. Try %s',
name, slugify(name))
return False
conf = config.get(DOMAIN, {})
def service_handler(call):
"""Execute a shell command service."""
@ -38,6 +38,6 @@ def setup(hass, config):
_LOGGER.exception('Error running command')
for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler)
hass.services.register(DOMAIN, name, service_handler,
schema=SHELL_COMMAND_SCHEMA)
return True

View File

@ -8,10 +8,13 @@ from datetime import timedelta
import logging
import os
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
ATTR_ENTITY_ID)
@ -50,6 +53,10 @@ PROP_TO_ATTR = {
'today_power_mw': ATTR_TODAY_MWH,
}
SWITCH_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
_LOGGER = logging.getLogger(__name__)
@ -102,11 +109,14 @@ def setup(hass, config):
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service,
descriptions.get(SERVICE_TURN_OFF))
descriptions.get(SERVICE_TURN_OFF),
schema=SWITCH_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service,
descriptions.get(SERVICE_TURN_ON))
descriptions.get(SERVICE_TURN_ON),
schema=SWITCH_SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_switch_service,
descriptions.get(SERVICE_TOGGLE))
descriptions.get(SERVICE_TOGGLE),
schema=SWITCH_SERVICE_SCHEMA)
return True

View File

@ -7,14 +7,16 @@ https://home-assistant.io/components/thermostat/
import logging
import os
import voluptuous as vol
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.config import load_yaml_config_file
import homeassistant.util as util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.temperature import convert
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components import (ecobee, zwave)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELCIUS)
@ -48,6 +50,19 @@ DISCOVERY_PLATFORMS = {
zwave.DISCOVER_THERMOSTATS: 'zwave'
}
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FAN): cv.boolean,
})
def set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified thermostat away mode on."""
@ -97,13 +112,7 @@ def setup(hass, config):
"""Set away mode on target thermostats."""
target_thermostats = component.extract_from_service(service)
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
return
away_mode = service.data[ATTR_AWAY_MODE]
for thermostat in target_thermostats:
if away_mode:
@ -115,20 +124,14 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
descriptions.get(SERVICE_SET_AWAY_MODE))
descriptions.get(SERVICE_SET_AWAY_MODE),
schema=SET_AWAY_MODE_SCHEMA)
def temperature_set_service(service):
"""Set temperature on the target thermostats."""
target_thermostats = component.extract_from_service(service)
temperature = util.convert(
service.data.get(ATTR_TEMPERATURE), float)
if temperature is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE)
return
temperature = service.data[ATTR_TEMPERATURE]
for thermostat in target_thermostats:
thermostat.set_temperature(convert(
@ -139,19 +142,14 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
descriptions.get(SERVICE_SET_TEMPERATURE))
descriptions.get(SERVICE_SET_TEMPERATURE),
schema=SET_TEMPERATURE_SCHEMA)
def fan_mode_set_service(service):
"""Set fan mode on target thermostats."""
target_thermostats = component.extract_from_service(service)
fan_mode = service.data.get(ATTR_FAN)
if fan_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_FAN_MODE, ATTR_FAN)
return
fan_mode = service.data[ATTR_FAN]
for thermostat in target_thermostats:
if fan_mode:
@ -163,7 +161,8 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
descriptions.get(SERVICE_SET_FAN_MODE))
descriptions.get(SERVICE_SET_FAN_MODE),
schema=SET_FAN_MODE_SCHEMA)
return True

View File

@ -37,7 +37,7 @@ class TestComponentHistory(unittest.TestCase):
logbook.ATTR_NAME: 'Alarm',
logbook.ATTR_MESSAGE: 'is triggered',
logbook.ATTR_DOMAIN: 'switch',
logbook.ATTR_ENTITY_ID: 'test_switch'
logbook.ATTR_ENTITY_ID: 'switch.test_switch'
}, True)
self.hass.pool.block_till_done()
@ -48,7 +48,7 @@ class TestComponentHistory(unittest.TestCase):
self.assertEqual('is triggered', last_call.data.get(
logbook.ATTR_MESSAGE))
self.assertEqual('switch', last_call.data.get(logbook.ATTR_DOMAIN))
self.assertEqual('test_switch', last_call.data.get(
self.assertEqual('switch.test_switch', last_call.data.get(
logbook.ATTR_ENTITY_ID))
def test_service_call_create_log_book_entry_no_message(self):

View File

@ -5,6 +5,7 @@ import unittest
from unittest.mock import patch
from subprocess import SubprocessError
from homeassistant.bootstrap import _setup_component
from homeassistant.components import shell_command
from tests.common import get_test_home_assistant
@ -25,11 +26,11 @@ class TestShellCommand(unittest.TestCase):
"""Test if able to call a configured service."""
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
self.assertTrue(shell_command.setup(self.hass, {
'shell_command': {
assert _setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "date > {}".format(path)
}
}))
})
self.hass.services.call('shell_command', 'test_service',
blocking=True)
@ -38,16 +39,17 @@ class TestShellCommand(unittest.TestCase):
def test_config_not_dict(self):
"""Test if config is not a dict."""
self.assertFalse(shell_command.setup(self.hass, {
'shell_command': ['some', 'weird', 'list']
}))
assert not _setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: ['some', 'weird', 'list']
})
def test_config_not_valid_service_names(self):
"""Test if config contains invalid service names."""
self.assertFalse(shell_command.setup(self.hass, {
'shell_command': {
assert not _setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'this is invalid because space': 'touch bla.txt'
}}))
}
})
@patch('homeassistant.components.shell_command.subprocess.call',
side_effect=SubprocessError)
@ -56,11 +58,11 @@ class TestShellCommand(unittest.TestCase):
"""Test subprocess."""
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
self.assertTrue(shell_command.setup(self.hass, {
'shell_command': {
assert _setup_component(self.hass, shell_command.DOMAIN, {
shell_command.DOMAIN: {
'test_service': "touch {}".format(path)
}
}))
})
self.hass.services.call('shell_command', 'test_service',
blocking=True)