regenmaschine/controller.py
"""Define a RainMachine controller class."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from datetime import datetime, timedelta
from typing import Any
from yarl import URL
from regenmaschine.endpoints.api import API
from regenmaschine.endpoints.diagnostics import Diagnostics
from regenmaschine.endpoints.machine import Machine
from regenmaschine.endpoints.parser import Parser
from regenmaschine.endpoints.program import Program
from regenmaschine.endpoints.provision import Provision
from regenmaschine.endpoints.restriction import Restriction
from regenmaschine.endpoints.stats import Stats
from regenmaschine.endpoints.watering import Watering
from regenmaschine.endpoints.zone import Zone
URL_BASE_LOCAL = "{0}://{1}:{2}/api/4"
URL_BASE_REMOTE = "https://api.rainmachine.com/{0}/api/4"
class Controller: # pylint: disable=too-few-public-methods,too-many-instance-attributes
"""Define the controller."""
def __init__(self, request: Callable[..., Awaitable[dict[str, Any]]]) -> None:
"""Initialize.
Args:
request: The request method from the Client object.
"""
self._access_token: str | None = None
self._access_token_expiration: datetime | None = None
self._client_request = request
self._host: str = ""
self._base_url: URL = URL("")
self._use_ssl = True
self.api_version: str = ""
self.hardware_version: str = ""
self.mac: str = ""
self.name: str = ""
self.software_version: str = ""
# API endpoints:
self.api = API(self)
self.diagnostics = Diagnostics(self)
self.machine = Machine(self)
self.parsers = Parser(self)
self.programs = Program(self)
self.provisioning = Provision(self)
self.restrictions = Restriction(self)
self.stats = Stats(self)
self.watering = Watering(self)
self.zones = Zone(self)
async def request(
self, method: str, endpoint: str, **kwargs: dict[str, Any]
) -> dict[str, Any]:
"""Wrap the generic request method to add access token, etc.
Args:
method: An HTTP method.
endpoint: An API URL endpoint.
**kwargs: Additional kwargs to send with the request.
Returns:
An API response payload.
"""
return await self._client_request(
method,
self._base_url.joinpath(endpoint),
access_token=self._access_token,
access_token_expiration=self._access_token_expiration,
use_ssl=self._use_ssl,
**kwargs,
)
class LocalController(Controller):
"""Define a controller accessed over the LAN."""
def __init__(
self,
request: Callable[..., Awaitable[dict[str, Any]]],
host: str,
port: int,
use_ssl: bool = True,
) -> None:
"""Initialize.
Args:
request: The request method from the Client object.
host: The IP address or hostname of the controller.
port: The port that serves the controller's API.
use_ssl: Whether to use SSL/TLS on the request.
"""
super().__init__(request)
if use_ssl:
self._host = URL_BASE_LOCAL.format("https", host, port)
else:
self._host = URL_BASE_LOCAL.format("http", host, port)
self._base_url = URL(self._host)
self._use_ssl = use_ssl
async def login(self, password: str) -> None:
"""Authenticate against the device (locally).
Args:
password: The controller password.
"""
auth_resp = await self._client_request(
"post",
self._base_url.joinpath("auth/login"),
json={"pwd": password, "remember": 1},
)
self._access_token: str = auth_resp["access_token"]
self._access_token_expiration: datetime = datetime.now() + timedelta(
seconds=int(auth_resp["expires_in"]) - 10
)
class RemoteController(Controller):
"""Define a controller accessed over RainMachine's cloud."""
async def login(
self, stage_1_access_token: str, sprinkler_id: str, password: str
) -> None:
"""Authenticate against the device (remotely).
Args:
stage_1_access_token: The first-stage access token from the remote cloud.
sprinkler_id: A unique ID for the controller.
password: The account password.
"""
auth_resp: dict = await self._client_request(
"post",
URL("https://my.rainmachine.com/devices/login-sprinkler"),
access_token=stage_1_access_token,
json={"sprinklerId": sprinkler_id, "pwd": password},
)
self._access_token = auth_resp["access_token"]
self._host = URL_BASE_REMOTE.format(sprinkler_id)
self._base_url = URL(self._host)