Skip to content

Commit

Permalink
Add wake_on_lan class method
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Johnson <ben@binarylogic.com>
  • Loading branch information
binarylogic committed Apr 10, 2024
1 parent a65b73d commit bea1865
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 1 deletion.
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ classifiers =
Operating System :: OS Independent
Topic :: Software Development :: Libraries
Topic :: Home Automation

[options]
packages = find:
install_requires =
wakeonlan>=3.1
18 changes: 17 additions & 1 deletion tests/test_trinnov_altitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import pytest
import pytest_asyncio

from trinnov_altitude.exceptions import ConnectionFailedError, ConnectionTimeoutError
from trinnov_altitude.exceptions import (
ConnectionFailedError,
ConnectionTimeoutError,
InvalidMacAddressOUIError,
MalformedMacAddressError,
)
from trinnov_altitude.messages import Message, OKMessage
from trinnov_altitude.mocks import MockTrinnovAltitudeServer
from trinnov_altitude.trinnov_altitude import TrinnovAltitude
Expand All @@ -16,6 +21,17 @@ async def mock_server():
await server.stop_server()


@pytest.mark.asyncio
async def test_validate_mac():
with pytest.raises(MalformedMacAddressError):
TrinnovAltitude.validate_mac("malformed")

with pytest.raises(InvalidMacAddressOUIError):
TrinnovAltitude.validate_mac("c9:7f:32:2b:ea:f4")

assert TrinnovAltitude.validate_mac("c8:7f:54:7a:eb:c2")


@pytest.mark.asyncio
async def test_connect_failed(mock_server):
client = TrinnovAltitude(host="invalid")
Expand Down
19 changes: 19 additions & 0 deletions trinnov_altitude/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ class ConnectionTimeoutError(Exception):
"""Thrown when connecting to a processor times out."""


class InvalidMacAddressOUIError(Exception):
"""Exception raised for when the Mac address does not start with a valid Trinnov OUI."""

def __init__(self, mac_oui, valid_ouis):
valid_ouis_str = ", ".join(valid_ouis)
self.message = (
f"Invalid MAC address OUI {mac_oui}, must be one of {valid_ouis_str}"
)
super().__init__(self.message)


class MalformedMacAddressError(Exception):
"""Exception raised for malformed MAC addresses."""

def __init__(self, mac_address, message="Malformed MAC address provided: "):
self.message = message + mac_address
super().__init__(self.message)


class NotConnectedError(Exception):
"""Raised when an operation is performed that requires a connect and none is present."""

Expand Down
34 changes: 34 additions & 0 deletions trinnov_altitude/trinnov_altitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import asyncio
from collections.abc import Callable
import logging
import re
from wakeonlan import send_magic_packet

from trinnov_altitude import const, exceptions, messages


Expand All @@ -19,11 +22,42 @@ class TrinnovAltitude:
DEFAULT_PORT = 44100
DEFAULT_TIMEOUT = 2.0
ENCODING = "ascii"
VALID_OUIS = [
"c8:7f:54", # ASUSTek OUI (components inside Altitudes)
"64:98:9e", # Trinnov's OUI
]

# Use a sentinel value to signal that the DEFAULT_TIMEOUT should be used.
# This allows users to pass None and disable the timeout to wait indefinitely.
USE_DEFAULT_TIMEOUT = -1.0

@classmethod
def validate_mac(cls, mac_address):
"""
Validate, to the best of our abilities, that the Mac address is a
valid Trinnov Altitude Mac address.
."""

normalized_mac_address = mac_address.lower()

# Verify the format
pattern = re.compile(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
if pattern.match(normalized_mac_address) is None:
raise exceptions.MalformedMacAddressError(mac_address)

# Verify it starts with Trinnov associates OUIs
mac_oui = normalized_mac_address[:8]
if not any(mac_oui == oui for oui in cls.VALID_OUIS):
raise exceptions.InvalidMacAddressOUIError(mac_oui, cls.VALID_OUIS)

return True

@classmethod
def wake_on_lan(cls, mac_address):
"""Wake the processor via WoL."""
cls.validate_mac(mac_address)
send_magic_packet(mac_address)

def __init__(
self,
host: str,
Expand Down

0 comments on commit bea1865

Please sign in to comment.