diff --git a/.gitignore b/.gitignore index 3b3529c..e003084 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ lib_ .idea x_* docs_out +.venv diff --git a/fritzconnection/cli/fritzwol.py b/fritzconnection/cli/fritzwol.py new file mode 100644 index 0000000..266aafb --- /dev/null +++ b/fritzconnection/cli/fritzwol.py @@ -0,0 +1,84 @@ +""" +fritzwol.py + +Module to wake up a single host via the Fritzbox built-in mechanism. +This can be helpful if the host to be woken up is in a different +broadcast domain/ subnet than the client which tries to wake up. +CLI interface. + +This module is part of the FritzConnection package. +https://github.com/kbr/fritzconnection +License: MIT (https://opensource.org/licenses/MIT) +Author: Maik Töpfer +""" + +from fritzconnection.core.exceptions import FritzConnectionException +from fritzconnection.core.fritzconnection import FritzConnection +from ..lib.fritzhosts import FritzHosts +from . utils import get_cli_arguments, get_instance, print_header + +class WakeOnLanException(Exception): + """ + Exception raised when encountering issues while attempting to start a device using "Wake on LAN" (WoL). + """ + pass + +class WakeOnLan: + """ + This class provides access to the "Start computer" (German "Computer starten") feature of a host via the command line. + This enables waking up computers in the local network which are on a different broadcast domain/ subnet. + """ + def __init__(self, fc, fh): + self.fc = fc + self.fh = fh + + def wakeup(self, host_name): + """ + Wake up the provided local computer. + + Raises: + WakeOnLanException: If the hostname is unknown at the Fritzbox or if there was an error sending the WoL command + """ + mac = self._get_mac_address(host_name) + self._wakeup_host(mac) + + def _get_mac_address(self, host_name): + for host in self.fh.get_hosts_info(): + if host["name"] == host_name: + return host["mac"] + raise WakeOnLanException( + f"Host '{host_name}' is unknown at Fritzbox.") + + def _wakeup_host(self, mac): + try: + self.fc.call_action( + 'Hosts1', + 'X_AVM-DE_WakeOnLANByMACAddress', + NewMACAddress=mac) + except FritzConnectionException as e: + raise WakeOnLanException(f"Error sending Wake on LAN command: {e}") + + +def _add_arguments(parser): + parser.add_argument('host', help='name of host to be woken up') + + +def main(): + args = get_cli_arguments(_add_arguments) + if not args.password: + print('Exit: password required.') + else: + fc = get_instance(FritzConnection, args) + print_header(fc) + try: + wol = WakeOnLan(fc, FritzHosts(fc)) + print(f"Waking up host '{args.host}'...") + wol.wakeup(args.host) + print("Done") + + except WakeOnLanException as e: + print(e) + + +if __name__ == '__main__': + main() diff --git a/fritzconnection/tests/cli/test_fritzwol.py b/fritzconnection/tests/cli/test_fritzwol.py new file mode 100644 index 0000000..2c85c45 --- /dev/null +++ b/fritzconnection/tests/cli/test_fritzwol.py @@ -0,0 +1,41 @@ +import pytest + +from fritzconnection.cli.fritzwol import WakeOnLan, WakeOnLanException +from unittest.mock import Mock + +from fritzconnection.core.exceptions import FritzConnectionException + + +@pytest.fixture +def fritz_connection(): + return Mock() + + +@pytest.fixture +def fritz_hosts(): + mock = Mock() + mock.get_hosts_info.return_value = [{'name': 'someHost', + 'mac': '00:00:00:00:00:00'}] + return mock + + +def test_wol_is_invoked(fritz_connection, fritz_hosts): + WakeOnLan(fritz_connection, fritz_hosts).wakeup("someHost") + fritz_hosts.get_hosts_info.assert_called_once() + fritz_connection.call_action.assert_called_once() + + +def test_host_not_found(fritz_connection, fritz_hosts): + with pytest.raises(WakeOnLanException): + WakeOnLan(fritz_connection, fritz_hosts).wakeup("unknownHost") + + fritz_connection.assert_not_called() + + +def test_call_action_causes_exception(fritz_connection, fritz_hosts): + fritz_connection.call_action.side_effect = FritzConnectionException + + with pytest.raises(WakeOnLanException): + WakeOnLan(fritz_connection, fritz_hosts).wakeup("somehost") + + fritz_hosts.get_hosts_info.assert_called_once() diff --git a/setup.py b/setup.py index 1a8ed4b..5048ea1 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def get_version(): "fritzphonebook = fritzconnection.cli.fritzphonebook:main", "fritzstatus = fritzconnection.cli.fritzstatus:main", "fritzwlan = fritzconnection.cli.fritzwlan:main", + "fritzwol = fritzconnection.cli.fritzwol:main", ] }, )