From 5817f388047fe67bf40652e3f263d90d40c10b7f Mon Sep 17 00:00:00 2001 From: Maik Toepfer Date: Sat, 12 Feb 2022 23:42:58 +0100 Subject: [PATCH 1/2] initial commit --- .gitignore | 1 + fritzconnection/cli/fritzwol.py | 73 ++++++++++++++++++++++ fritzconnection/tests/cli/test_fritzwol.py | 41 ++++++++++++ setup.py | 1 + 4 files changed, 116 insertions(+) create mode 100644 fritzconnection/cli/fritzwol.py create mode 100644 fritzconnection/tests/cli/test_fritzwol.py diff --git a/.gitignore b/.gitignore index 6794d77..756e519 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ include lib_ .idea x_* +.venv diff --git a/fritzconnection/cli/fritzwol.py b/fritzconnection/cli/fritzwol.py new file mode 100644 index 0000000..e7bd857 --- /dev/null +++ b/fritzconnection/cli/fritzwol.py @@ -0,0 +1,73 @@ +""" +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 WakeOnLan: + def __init__(self, fc, fh): + self._fc = fc + self._fh = fh + + def wakeup(self, host_name): + print(f"Waking up host '{host_name}'...") + mac = self._get_mac_address(host_name) + self._wakeup_host(mac) + print("Done") + + def _get_mac_address(self, host_name): + mac = [host['mac'] + for host in self._fh.get_hosts_info() if host['name'] == host_name] + if not mac: + raise WakeOnLanException( + f"Host '{host_name}' is unknown at Fritzbox.") + return mac[0] + + 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}") + + +class WakeOnLanException(Exception): + pass + + +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: + WakeOnLan(fc, FritzHosts(fc)).wakeup(args.host) + except WakeOnLanException as e: + print(e) + exit(1) + + +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 94db42a..59c5753 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ def get_version(): "fritzphonebook = fritzconnection.cli.fritzphonebook:main", "fritzstatus = fritzconnection.cli.fritzstatus:main", "fritzwlan = fritzconnection.cli.fritzwlan:main", + "fritzwol = fritzconnection.cli.fritzwol:main", ] }, ) From c7be031cee886bcbd703c34fa38729dcd4e764c0 Mon Sep 17 00:00:00 2001 From: Maik Toepfer Date: Sun, 5 May 2024 15:27:28 +0200 Subject: [PATCH 2/2] review feedback --- fritzconnection/cli/fritzwol.py | 43 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/fritzconnection/cli/fritzwol.py b/fritzconnection/cli/fritzwol.py index e7bd857..266aafb 100644 --- a/fritzconnection/cli/fritzwol.py +++ b/fritzconnection/cli/fritzwol.py @@ -17,29 +17,41 @@ 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 + self.fc = fc + self.fh = fh def wakeup(self, host_name): - print(f"Waking up host '{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) - print("Done") def _get_mac_address(self, host_name): - mac = [host['mac'] - for host in self._fh.get_hosts_info() if host['name'] == host_name] - if not mac: - raise WakeOnLanException( + 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.") - return mac[0] def _wakeup_host(self, mac): try: - self._fc.call_action( + self.fc.call_action( 'Hosts1', 'X_AVM-DE_WakeOnLANByMACAddress', NewMACAddress=mac) @@ -47,10 +59,6 @@ def _wakeup_host(self, mac): raise WakeOnLanException(f"Error sending Wake on LAN command: {e}") -class WakeOnLanException(Exception): - pass - - def _add_arguments(parser): parser.add_argument('host', help='name of host to be woken up') @@ -63,10 +71,13 @@ def main(): fc = get_instance(FritzConnection, args) print_header(fc) try: - WakeOnLan(fc, FritzHosts(fc)).wakeup(args.host) + wol = WakeOnLan(fc, FritzHosts(fc)) + print(f"Waking up host '{args.host}'...") + wol.wakeup(args.host) + print("Done") + except WakeOnLanException as e: print(e) - exit(1) if __name__ == '__main__':