import logging from http import HTTPStatus from typing import Any import httpx from pyecoforest.models.device import Device from .const import ( API_ALARMS_OP, API_SET_POWER_OP, API_SET_STATE_OP, API_SET_TEMP_OP, API_STATS_OP, API_STATUS_OP, LOCAL_TIMEOUT, URL_CGI, ) from .exceptions import EcoforestAuthenticationRequired, EcoforestConnectionError from .ssl import NO_VERIFY_SSL_CONTEXT _LOGGER = logging.getLogger(__name__) class EcoforestApi: """Class for communicating with an ecoforest device.""" def __init__( self, host: str, auth: httpx.BasicAuth | None = None, client: httpx.AsyncClient | None = None, timeout: float | httpx.Timeout | None = None, ) -> None: self._host = host self._auth = auth # We use our own httpx client session so we can disable SSL verification, # the device use self-signed SSL certs. self._timeout = timeout or LOCAL_TIMEOUT self._client = client or httpx.AsyncClient( base_url=self._host, verify=NO_VERIFY_SSL_CONTEXT ) # nosec async def get(self) -> Device: """Retrieve ecoforest information from api.""" return Device.build( { "status": await self._status(), "stats": await self._stats(), "alarms": await self._alarms(), } ) async def turn(self, on: bool | None = False) -> Device: """Turn device on and off.""" await self._request( data={"idOperacion": API_SET_STATE_OP, "on_off": 1 if on else 0} ) return await self.get() async def set_temperature(self, target: float) -> Device: """Set device target temperature.""" await self._request( data={"idOperacion": API_SET_TEMP_OP, "temperatura": target} ) return await self.get() async def set_power(self, target: int) -> Device: """Set device target power.""" await self._request(data={"idOperacion": API_SET_POWER_OP, "potencia": target}) return await self.get() async def _request(self, data: dict[str, Any] | None = None) -> httpx.Response: """Make a request to the device.""" if _LOGGER.isEnabledFor(logging.DEBUG): _LOGGER.debug("Sending POST to %s with data %s", URL_CGI, data) try: response = await self._client.post( URL_CGI, auth=self._auth, timeout=self._timeout, data=data, ) response.raise_for_status() except httpx.TimeoutException as error: raise EcoforestConnectionError( "Timeout occurred while connecting to the device." ) from error except httpx.HTTPError as error: if error.response.status_code in ( HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN, ): raise EcoforestAuthenticationRequired( error.response.status_code ) from error raise EcoforestConnectionError( "Error occurred while communicating with device." ) from error parsed = self._parse(response.text) if _LOGGER.isEnabledFor(logging.DEBUG): _LOGGER.debug("Received from POST with data %s", parsed) return parsed async def _status(self) -> dict[str, str]: """Retrieve ecoforest status.""" return await self._request(data={"idOperacion": API_STATUS_OP}) async def _stats(self) -> dict[str, str]: """Retrieve ecoforest stats.""" return await self._request(data={"idOperacion": API_STATS_OP}) async def _alarms(self) -> dict[str, str]: """Retrieve ecoforest information from api.""" return await self._request( data={ "idOperacion": API_ALARMS_OP, } ) def _parse(self, response: str) -> dict[str, str]: """Parse request data and return as dictionary.""" # discard last line ? reply = {} for e in response.split("\n")[:-1]: pair = e.split("=") # discard lines without pairs if len(pair) == 2: reply[pair[0]] = pair[1] # Remove all white spaces from bad response from ecoforest ... reply = {x.translate({32: None}): y for x, y in reply.items()} return reply