diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 97e947fff06..eefcff5bd17 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -508,3 +508,15 @@ modbus: state: description: State to write example: false + +wake_on_lan: + send_magic_packet: + description: Send a 'magic packet' to wake up a device with 'Wake-On-LAN' capabilities. + + fields: + mac: + description: MAC address of the device to wake up. + example: 'aa:bb:cc:dd:ee:ff' + broadcast_address: + description: Optional broadcast IP where to send the magic packet. + example: '192.168.255.255' diff --git a/homeassistant/components/wake_on_lan.py b/homeassistant/components/wake_on_lan.py new file mode 100644 index 00000000000..ab72aa989d7 --- /dev/null +++ b/homeassistant/components/wake_on_lan.py @@ -0,0 +1,62 @@ +""" +Component to wake up devices sending Wake-On-LAN magic packets. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/wake_on_lan/ +""" +import asyncio +from functools import partial +import logging +import os + +import voluptuous as vol + +from homeassistant.config import load_yaml_config_file +from homeassistant.const import CONF_MAC +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['wakeonlan==0.2.2'] + +DOMAIN = "wake_on_lan" +_LOGGER = logging.getLogger(__name__) + +CONF_BROADCAST_ADDRESS = 'broadcast_address' + +SERVICE_SEND_MAGIC_PACKET = 'send_magic_packet' + +WAKE_ON_LAN_SEND_MAGIC_PACKET_SCHEMA = vol.Schema({ + vol.Required(CONF_MAC): cv.string, + vol.Optional(CONF_BROADCAST_ADDRESS): cv.string, +}) + + +@asyncio.coroutine +def async_setup(hass, config): + """Set up the wake on LAN component.""" + from wakeonlan import wol + + @asyncio.coroutine + def send_magic_packet(call): + """Send magic packet to wake up a device.""" + mac_address = call.data.get(CONF_MAC) + broadcast_address = call.data.get(CONF_BROADCAST_ADDRESS) + _LOGGER.info("Send magic packet to mac %s (broadcast: %s)", + mac_address, broadcast_address) + if broadcast_address is not None: + yield from hass.async_add_job( + partial(wol.send_magic_packet, mac_address, + ip_address=broadcast_address)) + else: + yield from hass.async_add_job( + partial(wol.send_magic_packet, mac_address)) + + descriptions = yield from hass.async_add_job( + load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + hass.services.async_register( + DOMAIN, SERVICE_SEND_MAGIC_PACKET, send_magic_packet, + description=descriptions.get(DOMAIN).get(SERVICE_SEND_MAGIC_PACKET), + schema=WAKE_ON_LAN_SEND_MAGIC_PACKET_SCHEMA) + + return True diff --git a/requirements_all.txt b/requirements_all.txt index f37c5f5b48a..d4e3add1774 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -918,6 +918,7 @@ vsure==1.3.7 # homeassistant.components.sensor.vasttrafik vtjp==0.1.14 +# homeassistant.components.wake_on_lan # homeassistant.components.media_player.panasonic_viera # homeassistant.components.media_player.samsungtv # homeassistant.components.media_player.webostv diff --git a/tests/components/test_wake_on_lan.py b/tests/components/test_wake_on_lan.py new file mode 100644 index 00000000000..abaf7dd6d14 --- /dev/null +++ b/tests/components/test_wake_on_lan.py @@ -0,0 +1,47 @@ +"""Tests for Wake On LAN component.""" +import asyncio +from unittest import mock + +import pytest + +from homeassistant.setup import async_setup_component +from homeassistant.components.wake_on_lan import ( + DOMAIN, SERVICE_SEND_MAGIC_PACKET) + + +@pytest.fixture +def mock_wakeonlan(): + """Mock mock_wakeonlan.""" + module = mock.MagicMock() + with mock.patch.dict('sys.modules', { + 'wakeonlan': module, + }): + yield module + + +@asyncio.coroutine +def test_send_magic_packet(hass, caplog, mock_wakeonlan): + """Test of send magic packet service call.""" + mac = "aa:bb:cc:dd:ee:ff" + bc_ip = "192.168.255.255" + + yield from async_setup_component(hass, DOMAIN, {}) + + yield from hass.services.async_call( + DOMAIN, SERVICE_SEND_MAGIC_PACKET, + {"mac": mac, "broadcast_address": bc_ip}, blocking=True) + assert len(mock_wakeonlan.mock_calls) == 1 + assert mock_wakeonlan.mock_calls[-1][1][0] == mac + assert mock_wakeonlan.mock_calls[-1][2]['ip_address'] == bc_ip + + yield from hass.services.async_call( + DOMAIN, SERVICE_SEND_MAGIC_PACKET, + {"broadcast_address": bc_ip}, blocking=True) + assert 'ERROR' in caplog.text + assert len(mock_wakeonlan.mock_calls) == 1 + + yield from hass.services.async_call( + DOMAIN, SERVICE_SEND_MAGIC_PACKET, {"mac": mac}, blocking=True) + assert len(mock_wakeonlan.mock_calls) == 2 + assert mock_wakeonlan.mock_calls[-1][1][0] == mac + assert not mock_wakeonlan.mock_calls[-1][2]