Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion homeassistant/components/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,9 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
This method is a coroutine.
"""
removes = []
info = {
'name': name
}

for conf in trigger_configs:
platform = await async_prepare_setup_platform(
Expand All @@ -408,7 +411,7 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
if platform is None:
return None

remove = await platform.async_trigger(hass, conf, action)
remove = await platform.async_trigger(hass, conf, action, info)

if not remove:
_LOGGER.error("Error setting up trigger %s", name)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data_schema = vol.Schema(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/geo_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def source_match(state, source):
return state and state.attributes.get('source') == source


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
source = config.get(CONF_SOURCE).lower()
zone_entity_id = config.get(CONF_ZONE)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/homeassistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/litejet.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/numeric_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
_LOGGER = logging.getLogger(__name__)


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
}), cv.key_dependency(CONF_FOR, CONF_TO))


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/sun.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
if CONF_AT in config:
at_time = config.get(CONF_AT)
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/automation/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID
import homeassistant.helpers.config_validation as cv

from . import DOMAIN as AUTOMATION_DOMAIN

DEPENDENCIES = ('webhook',)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -39,10 +41,11 @@ async def _handle_webhook(action, hass, webhook_id, request):
hass.async_run_job(action, {'trigger': result})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Trigger based on incoming webhooks."""
webhook_id = config.get(CONF_WEBHOOK_ID)
hass.components.webhook.async_register(
AUTOMATION_DOMAIN, automation_info['name'],
webhook_id, partial(_handle_webhook, action))

@callback
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/automation/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
})


async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dialogflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async def handle_webhook(hass, webhook_id, request):
async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
DOMAIN, 'DialogFlow', entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ifttt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async def handle_webhook(hass, webhook_id, request):
async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
DOMAIN, 'IFTTT', entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mailgun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def verify_webhook(hass, token=None, timestamp=None, signature=None):
async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
DOMAIN, 'Mailgun', entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/twilio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async def handle_webhook(hass, webhook_id, request):
async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
DOMAIN, 'Twilio', entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True


Expand Down
40 changes: 35 additions & 5 deletions homeassistant/components/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,39 @@
import logging

from aiohttp.web import Response
import voluptuous as vol

from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.auth.util import generate_secret
from homeassistant.components import websocket_api
from homeassistant.components.http.view import HomeAssistantView

DOMAIN = 'webhook'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)


WS_TYPE_LIST = 'webhook/list'
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_LIST,
})


@callback
@bind_hass
def async_register(hass, webhook_id, handler):
def async_register(hass, domain, name, webhook_id, handler):
"""Register a webhook."""
handlers = hass.data.setdefault(DOMAIN, {})

if webhook_id in handlers:
raise ValueError('Handler is already defined!')

handlers[webhook_id] = handler
handlers[webhook_id] = {
'domain': domain,
'name': name,
'handler': handler
}


@callback
Expand All @@ -53,6 +65,10 @@ def async_generate_url(hass, webhook_id):
async def async_setup(hass, config):
"""Initialize the webhook component."""
hass.http.register_view(WebhookView)
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list,
SCHEMA_WS_LIST
)
return True


Expand All @@ -67,19 +83,33 @@ async def post(self, request, webhook_id):
"""Handle webhook call."""
hass = request.app['hass']
handlers = hass.data.setdefault(DOMAIN, {})
handler = handlers.get(webhook_id)
webhook = handlers.get(webhook_id)

# Always respond successfully to not give away if a hook exists or not.
if handler is None:
if webhook is None:
_LOGGER.warning(
'Received message for unregistered webhook %s', webhook_id)
return Response(status=200)

try:
response = await handler(hass, webhook_id, request)
response = await webhook['handler'](hass, webhook_id, request)
if response is None:
response = Response(status=200)
return response
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error processing webhook %s", webhook_id)
return Response(status=200)


@callback
def websocket_list(hass, connection, msg):
"""Return a list of webhooks."""
handlers = hass.data.setdefault(DOMAIN, {})
result = [{
'webhook_id': webhook_id,
'domain': info['domain'],
'name': info['name'],
} for webhook_id, info in handlers.items()]

connection.send_message(
websocket_api.result_message(msg['id'], result))
36 changes: 32 additions & 4 deletions tests/components/test_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ async def handle(*args):
"""Handle webhook."""
hooks.append(args)

hass.components.webhook.async_register(webhook_id, handle)
hass.components.webhook.async_register(
'test', "Test hook", webhook_id, handle)

resp = await mock_client.post('/api/webhook/{}'.format(webhook_id))
assert resp.status == 200
Expand Down Expand Up @@ -51,7 +52,7 @@ async def test_posting_webhook_nonexisting(hass, mock_client):

async def test_posting_webhook_invalid_json(hass, mock_client):
"""Test posting to a nonexisting webhook."""
hass.components.webhook.async_register('hello', None)
hass.components.webhook.async_register('test', "Test hook", 'hello', None)
resp = await mock_client.post('/api/webhook/hello', data='not-json')
assert resp.status == 200

Expand All @@ -65,7 +66,8 @@ async def handle(*args):
"""Handle webhook."""
hooks.append((args[0], args[1], await args[2].text()))

hass.components.webhook.async_register(webhook_id, handle)
hass.components.webhook.async_register(
'test', "Test hook", webhook_id, handle)

resp = await mock_client.post('/api/webhook/{}'.format(webhook_id), json={
'data': True
Expand All @@ -86,11 +88,37 @@ async def handle(*args):
"""Handle webhook."""
hooks.append(args)

hass.components.webhook.async_register(webhook_id, handle)
hass.components.webhook.async_register(
'test', "Test hook", webhook_id, handle)

resp = await mock_client.post('/api/webhook/{}'.format(webhook_id))
assert resp.status == 200
assert len(hooks) == 1
assert hooks[0][0] is hass
assert hooks[0][1] == webhook_id
assert await hooks[0][2].text() == ''


async def test_listing_webhook(hass, hass_ws_client, hass_access_token):
"""Test unregistering a webhook."""
assert await async_setup_component(hass, 'webhook', {})
client = await hass_ws_client(hass, hass_access_token)

hass.components.webhook.async_register(
'test', "Test hook", "my-id", None)

await client.send_json({
'id': 5,
'type': 'webhook/list',
})

msg = await client.receive_json()
assert msg['id'] == 5
assert msg['success']
assert msg['result'] == [
{
'webhook_id': 'my-id',
'domain': 'test',
'name': 'Test hook'
}
]
57 changes: 29 additions & 28 deletions tests/components/twilio/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,36 @@
from tests.common import MockDependency


@MockDependency('twilio', 'rest')
@MockDependency('twilio', 'twiml')
async def test_config_flow_registers_webhook(hass, aiohttp_client):
"""Test setting up Twilio and sending webhook."""
with patch('homeassistant.util.get_local_ip', return_value='example.com'):
result = await hass.config_entries.flow.async_init('twilio', context={
'source': 'user'
with MockDependency('twilio', 'rest'), MockDependency('twilio', 'twiml'):
with patch('homeassistant.util.get_local_ip',
return_value='example.com'):
result = await hass.config_entries.flow.async_init(
'twilio', context={
'source': 'user'
})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result

result = await hass.config_entries.flow.async_configure(
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
webhook_id = result['result'].data['webhook_id']

twilio_events = []

@callback
def handle_event(event):
"""Handle Twilio event."""
twilio_events.append(event)

hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event)

client = await aiohttp_client(hass.http.app)
await client.post('/api/webhook/{}'.format(webhook_id), data={
'hello': 'twilio'
})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result

result = await hass.config_entries.flow.async_configure(
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
webhook_id = result['result'].data['webhook_id']

twilio_events = []

@callback
def handle_event(event):
"""Handle Twilio event."""
twilio_events.append(event)

hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event)

client = await aiohttp_client(hass.http.app)
await client.post('/api/webhook/{}'.format(webhook_id), data={
'hello': 'twilio'
})

assert len(twilio_events) == 1
assert twilio_events[0].data['webhook_id'] == webhook_id
assert twilio_events[0].data['hello'] == 'twilio'
assert len(twilio_events) == 1
assert twilio_events[0].data['webhook_id'] == webhook_id
assert twilio_events[0].data['hello'] == 'twilio'