Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WoL CLI with convenience features #195

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
96 changes: 96 additions & 0 deletions fritzconnection/cli/fritzwol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
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)
Authors: Maik Töpfer, Chris Bräucker
"""

from fritzconnection.core.exceptions import FritzArgumentError, FritzArrayIndexError, FritzAuthorizationError, FritzLookUpError
from ..lib.fritzhosts import FritzHosts
from . utils import (
get_cli_arguments,
get_instance,
print_header,
print_common_exception_message
)

class DeviceUnknownException(Exception):
"""Exception raised if the reference to the host does not resolve."""


def wake_host(fh, args):
"""
Either wakes a host directly by MAC address, which should even work for hosts not known.
Or it tries to find the given parameter in the device list to determine the MAC address.
"""
mac = args.host

if args.field == 'n':
try:
host = fh.get_generic_host_entry(int(args.host) - 1)
except (FritzArgumentError, FritzArrayIndexError) as err:
raise DeviceUnknownException("The index provided is invalid", args.host)
mac = host['NewMACAddress']

elif args.field == 'ip':
try:
host = fh.get_specific_host_entry_by_ip(args.host)
except (FritzArgumentError, FritzLookUpError) as err:
raise DeviceUnknownException("The IP provided is unknown", args.host)
mac = host['NewMACAddress']

elif args.field == 'name':
found = False
for entry in fh.get_generic_host_entries():
if entry['NewHostName'].lower() == args.host.lower():
mac = entry['NewMACAddress']
found = True
break

if not found:
raise DeviceUnknownException("The hostname provided is unknown", args.host)

fh.wakeonlan_host(mac)
print(f"Waking {mac}")



def add_arguments(parser):
parser.add_argument('field',
choices=('ip', 'name', 'mac', 'n'),
default='mac',
nargs='?',
help='Which host field to wake by. ' +
'Retrieve this data with the `fritzhosts` command. ' +
'\'mac\' sends the WoL package directly, without checking. ' +
'(default: mac)')
parser.add_argument('host', help='Field value of host to be woken up')


def execute():
arguments = get_cli_arguments(add_arguments)
fh = get_instance(FritzHosts, arguments)
print_header(fh)
wake_host(fh, arguments)


def main():
try:
execute()
except FritzAuthorizationError as err:
print_common_exception_message(err)
except FritzArgumentError:
print(f"Error: Invalid MAC address format")
except DeviceUnknownException as err:
print(f"Error: {err.args[0]}: {err.args[1]}")


if __name__ == '__main__':
main()
7 changes: 7 additions & 0 deletions fritzconnection/lib/fritzhosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ def set_wakeonlan_status(self, mac_address: str, status: bool = False) -> None:
}
self._action("X_AVM-DE_SetAutoWakeOnLANByMACAddress", arguments=args)

def wakeonlan_host(self, mac_address: str) -> None:
"""
Triggers sending a wake on lan message with the given `mac_address`
on the local network. This method has no return value.
"""
self._action("X_AVM-DE_WakeOnLANByMACAddress", NewMACAddress=mac_address)

def set_host_name(self, mac_address: str, name: str) -> None:
"""
Sets the hostname of the device with the given `mac_address` to
Expand Down
59 changes: 59 additions & 0 deletions fritzconnection/tests/cli/test_fritzwol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from unittest.mock import Mock
from argparse import Namespace

from fritzconnection.cli.fritzwol import wake_host


def test_calls_wakeonlan_host_with_macaddress_directly():
mac = 'C0:FF:EE:C0:FF:EE'
fritz_host = Mock()

wake_host(fritz_host, Namespace(field='mac', host=mac))
fritz_host.get_generic_host_entry.assert_not_called()
fritz_host.get_specific_host_entry_by_ip.assert_not_called()
fritz_host.get_generic_host_entries.assert_not_called()

fritz_host.wakeonlan_host.assert_called_with(mac)


def test_n_calls_generic_host_then_wakeonlan():
mac = 'C0:FF:EE:C0:FF:EE'
fritz_host = Mock()
fritz_host.get_generic_host_entry.return_value = {'NewMACAddress': mac}

wake_host(fritz_host, Namespace(field='n', host='1'))
fritz_host.get_generic_host_entry.assert_called_with(0)
fritz_host.get_specific_host_entry_by_ip.assert_not_called()
fritz_host.get_generic_host_entries.assert_not_called()

fritz_host.wakeonlan_host.assert_called_with(mac)


def test_ip_calls_specific_host_then_wakeonlan():
mac = 'C0:FF:EE:C0:FF:EE'
fritz_host = Mock()
fritz_host.get_specific_host_entry_by_ip.return_value = {'NewMACAddress': mac}

wake_host(fritz_host, Namespace(field='ip', host='127.0.0.1'))
fritz_host.get_generic_host_entry.assert_not_called()
fritz_host.get_specific_host_entry_by_ip.assert_called_with('127.0.0.1')
fritz_host.get_generic_host_entries.assert_not_called()

fritz_host.wakeonlan_host.assert_called_with(mac)



def test_name_calls_generic_host_entries_then_wakeonlan():
mac = 'C0:FF:EE:C0:FF:EE'
fritz_host = Mock()
fritz_host.get_generic_host_entries.return_value = [
{'NewHostName': 'otherhost', 'NewMACAddress': '11:22:33:44:55:66'},
{'NewHostName': 'thishost', 'NewMACAddress': mac}
]

wake_host(fritz_host, Namespace(field='name', host='thishost'))
fritz_host.get_generic_host_entry.assert_not_called()
fritz_host.get_specific_host_entry_by_ip.assert_not_called()
fritz_host.get_generic_host_entries.assert_called_once()

fritz_host.wakeonlan_host.assert_called_with(mac)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
},
)