Skip to content
Merged
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
77 changes: 50 additions & 27 deletions custom_components/aguaiot/aguaiot.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
API_PATH_DEVICE_BUFFER_READING = "/deviceGetBufferReading"
API_PATH_DEVICE_JOB_STATUS = "/deviceJobStatus/"
API_PATH_DEVICE_WRITING = "/deviceRequestWriting"
DEFAULT_TIMEOUT_VALUE = 30

HEADER_ACCEPT = "application/json, text/javascript, */*; q=0.01"
HEADER_CONTENT_TYPE = "application/json"
Expand All @@ -43,6 +42,8 @@ def __init__(
air_temp_fix=False,
reading_error_fix=False,
language="ENG",
http_timeout=30,
buffer_read_timeout=30,
):
self.api_url = api_url.rstrip("/")
self.customer_code = customer_code
Expand All @@ -58,6 +59,8 @@ def __init__(
self.refresh_token = None
self.devices = list()
self.async_client = async_client
self.http_timeout = http_timeout
self.buffer_read_timeout = buffer_read_timeout

# Vendor specific fixes
self.air_temp_fix = air_temp_fix
Expand Down Expand Up @@ -113,23 +116,23 @@ async def register_app_id(self):
json=payload,
headers=self._headers(),
follow_redirects=False,
timeout=DEFAULT_TIMEOUT_VALUE,
timeout=self.http_timeout,
)
_LOGGER.debug(
"RESPONSE Register app - CODE: %s DATA: %s",
response.status_code,
response.text,
)
except httpx.TransportError as e:
raise ConnectionError(f"Connection error to {url}: {e}")
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")

if response.status_code != 201:
_LOGGER.error(
"Failed to register app id. Code: %s, Response: %s",
response.status_code,
response.text,
)
raise UnauthorizedError("Failed to register app id")
raise AguaIOTUnauthorized("Failed to register app id")

return True

Expand Down Expand Up @@ -162,23 +165,23 @@ async def login(self):
json=payload,
headers=headers,
follow_redirects=False,
timeout=DEFAULT_TIMEOUT_VALUE,
timeout=self.http_timeout,
)
_LOGGER.debug(
"RESPONSE Login - CODE: %s DATA: %s",
response.status_code,
response.text,
)
except httpx.TransportError as e:
raise ConnectionError(f"Connection error to {url}: {e}")
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")

if response.status_code != 200:
_LOGGER.error(
"Failed to login. Code: %s, Response: %s",
response.status_code,
response.text,
)
raise UnauthorizedError("Failed to login, please check credentials")
raise AguaIOTUnauthorized("Failed to login, please check credentials")

res = response.json()
self.token = res["token"]
Expand Down Expand Up @@ -208,15 +211,15 @@ async def do_refresh_token(self):
json=payload,
headers=self._headers(),
follow_redirects=False,
timeout=DEFAULT_TIMEOUT_VALUE,
timeout=self.http_timeout,
)
_LOGGER.debug(
"RESPONSE Refresh token - CODE: %s DATA: %s",
response.status_code,
response.text,
)
except httpx.TransportError as e:
raise ConnectionError(f"Connection error to {url}: {e}")
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")

if response.status_code != 201:
_LOGGER.warning("Refresh auth token failed, forcing new login...")
Expand Down Expand Up @@ -269,7 +272,6 @@ async def fetch_device_information(self):
"""Fetch device information of heating devices"""
for dev in self.devices:
await dev.update_mapping()
await dev.update()

async def update(self):
for dev in self.devices:
Expand All @@ -293,7 +295,7 @@ async def handle_webcall(self, method, url, payload):
json=payload,
headers=headers,
follow_redirects=False,
timeout=DEFAULT_TIMEOUT_VALUE,
timeout=self.http_timeout,
)
else:
async with self.async_client as client:
Expand All @@ -302,7 +304,7 @@ async def handle_webcall(self, method, url, payload):
params=payload,
headers=headers,
follow_redirects=False,
timeout=DEFAULT_TIMEOUT_VALUE,
timeout=self.http_timeout,
)
_LOGGER.debug(
"RESPONSE %s - CODE: %s DATA: %s",
Expand All @@ -311,7 +313,7 @@ async def handle_webcall(self, method, url, payload):
response.text,
)
except httpx.TransportError as e:
raise ConnectionError(f"Connection error to {url}: {e}")
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")

if response.status_code == 401:
await self.do_refresh_token()
Expand Down Expand Up @@ -391,23 +393,37 @@ async def __update_device_information(self):
"BufferId": 1,
}

res = await self.__aguaiot.handle_webcall("POST", url, payload)
if res is False:
res_req = await self.__aguaiot.handle_webcall("POST", url, payload)
if res_req is False:
raise AguaIOTError("Error while making device buffer read request.")

id_request = res["idRequest"]
url = self.__aguaiot.api_url + API_PATH_DEVICE_JOB_STATUS + id_request
async def buffer_read_loop(id_request):
url = self.__aguaiot.api_url + API_PATH_DEVICE_JOB_STATUS + id_request

payload = {}
for _ in range(10):
await asyncio.sleep(1)
sleep_secs = 1
while True:
res_get = await self.__aguaiot.handle_webcall("GET", url, {})
if (
"jobAnswerStatus" in res_get
and res_get["jobAnswerStatus"] == "completed"
):
return res_get

res = await self.__aguaiot.handle_webcall("GET", url, payload)
if "jobAnswerStatus" in res and res["jobAnswerStatus"] == "completed":
break
await asyncio.sleep(sleep_secs)
sleep_secs += 1

if res is False or res["jobAnswerStatus"] != "completed":
raise AguaIOTError("Timeout on reply to device buffer read.")
try:
res = await asyncio.wait_for(
buffer_read_loop(res_req["idRequest"]),
self.__aguaiot.buffer_read_timeout,
)
except asyncio.TimeoutError:
raise AguaIOTTimeout(
f"Timeout on waiting device buffer read to complete within {self.__aguaiot.buffer_read_timeout} seconds."
)

if res is False:
raise AguaIOTError("Error while reading device buffer response.")

current_i = 0
information_dict = dict()
Expand Down Expand Up @@ -659,14 +675,21 @@ def __init__(self, message):
Exception.__init__(self, message)


class UnauthorizedError(AguaIOTError):
class AguaIOTUnauthorized(AguaIOTError):
"""Unauthorized"""

def __init__(self, message):
super().__init__(message)


class ConnectionError(AguaIOTError):
class AguaIOTConnectionError(AguaIOTError):
"""Connection error"""

def __init__(self, message):
super().__init__(message)


class AguaIOTTimeout(AguaIOTError):
"""Connection error"""

def __init__(self, message):
Expand Down
41 changes: 27 additions & 14 deletions custom_components/aguaiot/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import uuid

from .aguaiot import (
ConnectionError,
AguaIOTConnectionError,
AguaIOTError,
UnauthorizedError,
AguaIOTUnauthorized,
aguaiot,
)
import voluptuous as vol
Expand All @@ -30,6 +30,10 @@
CONF_BRAND_ID,
CONF_BRAND,
CONF_LANGUAGE,
CONF_AIR_TEMP_FIX,
CONF_READING_ERROR_FIX,
CONF_HTTP_TIMEOUT,
CONF_BUFFER_READ_TIMEOUT,
DOMAIN,
ENDPOINTS,
)
Expand Down Expand Up @@ -91,14 +95,14 @@ async def async_step_user(self, user_input=None):
async_client=get_async_client(self.hass),
)
await agua.connect()
except UnauthorizedError as e:
except AguaIOTUnauthorized as e:
_LOGGER.error("Agua IOT Unauthorized: %s", e)
errors["base"] = "unauthorized"
except ConnectionError as e:
_LOGGER.error("Connection error to Agua IOT: %s", e)
except AguaIOTConnectionError as e:
_LOGGER.error("Agua IOT Connection error: %s", e)
errors["base"] = "connection_error"
except AguaIOTError as e:
_LOGGER.error("Unknown Agua IOT error: %s", e)
_LOGGER.error("Agua IOT error: %s", e)
errors["base"] = "unknown_error"

if "base" not in errors:
Expand Down Expand Up @@ -175,14 +179,15 @@ async def async_step_user(self, user_input=None):

try:
await agua.connect()
except UnauthorizedError as e:
await agua.update()
except AguaIOTUnauthorized as e:
_LOGGER.error("Agua IOT Unauthorized: %s", e)
return False
except ConnectionError as e:
_LOGGER.error("Connection error to Agua IOT: %s", e)
except AguaIOTConnectionError as e:
_LOGGER.error("Agua IOT Connection error: %s", e)
return False
except AguaIOTError as e:
_LOGGER.error("Unknown Agua IOT error: %s", e)
_LOGGER.error("Agua IOT error: %s", e)
return False

languages = ["ENG"]
Expand All @@ -197,13 +202,21 @@ async def async_step_user(self, user_input=None):

schema = {
vol.Optional(
"air_temp_fix",
default=self.config_entry.options.get("air_temp_fix", False),
CONF_AIR_TEMP_FIX,
default=self.config_entry.options.get(CONF_AIR_TEMP_FIX, False),
): bool,
vol.Optional(
"reading_error_fix",
default=self.config_entry.options.get("reading_error_fix", False),
CONF_READING_ERROR_FIX,
default=self.config_entry.options.get(CONF_READING_ERROR_FIX, False),
): bool,
vol.Optional(
CONF_HTTP_TIMEOUT,
default=self.config_entry.options.get(CONF_HTTP_TIMEOUT, 30),
): vol.All(vol.Coerce(int), vol.Range(max=60)),
vol.Optional(
CONF_BUFFER_READ_TIMEOUT,
default=self.config_entry.options.get(CONF_BUFFER_READ_TIMEOUT, 30),
): vol.All(vol.Coerce(int), vol.Range(max=60)),
vol.Optional(
CONF_LANGUAGE,
default=self.config_entry.options.get(CONF_LANGUAGE, "ENG"),
Expand Down
4 changes: 4 additions & 0 deletions custom_components/aguaiot/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class AguaIOTCanalizationEntityDescription(ClimateEntityDescription):
CONF_BRAND_ID = "brand_id"
CONF_BRAND = "brand"
CONF_LANGUAGE = "language"
CONF_AIR_TEMP_FIX = "air_temp_fix"
CONF_READING_ERROR_FIX = "reading_error_fix"
CONF_HTTP_TIMEOUT = "http_timeout"
CONF_BUFFER_READ_TIMEOUT = "buffer_read_timeout"

AIR_VARIANTS = ["air", "air2", "air3", "air_palm"]
WATER_VARIANTS = ["water", "h2o", "h2o_mandata"]
Expand Down
48 changes: 28 additions & 20 deletions custom_components/aguaiot/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
from homeassistant.helpers.httpx_client import get_async_client

from .aguaiot import (
ConnectionError,
AguaIOTConnectionError,
AguaIOTError,
UnauthorizedError,
AguaIOTUnauthorized,
AguaIOTTimeout,
aguaiot,
)

Expand All @@ -26,6 +27,10 @@
CONF_BRAND_ID,
CONF_BRAND,
CONF_LANGUAGE,
CONF_AIR_TEMP_FIX,
CONF_READING_ERROR_FIX,
CONF_HTTP_TIMEOUT,
CONF_BUFFER_READ_TIMEOUT,
DOMAIN,
UPDATE_INTERVAL,
)
Expand Down Expand Up @@ -59,9 +64,11 @@ def __init__(
login_api_url = config_entry.data.get(CONF_LOGIN_API_URL)
brand_id = config_entry.data.get(CONF_BRAND_ID)
brand = config_entry.data.get(CONF_BRAND)
air_temp_fix = config_entry.options.get("air_temp_fix", False)
reading_error_fix = config_entry.options.get("reading_error_fix", False)
air_temp_fix = config_entry.options.get(CONF_AIR_TEMP_FIX, False)
reading_error_fix = config_entry.options.get(CONF_READING_ERROR_FIX, False)
language = config_entry.options.get(CONF_LANGUAGE)
http_timeout = config_entry.options.get(CONF_HTTP_TIMEOUT)
buffer_read_timeout = config_entry.options.get(CONF_BUFFER_READ_TIMEOUT)

self.agua = aguaiot(
api_url=api_url,
Expand All @@ -76,32 +83,33 @@ def __init__(
air_temp_fix=air_temp_fix,
reading_error_fix=reading_error_fix,
language=language,
http_timeout=http_timeout,
buffer_read_timeout=buffer_read_timeout,
)

async def _async_setup(self) -> None:
"""Connect to the AguaIOT platform"""
try:
await self.agua.connect()
except UnauthorizedError as e:
_LOGGER.error("Agua IOT Unauthorized: %s", e)
raise UpdateFailed("Agua IOT Unauthorized: %s", e) from e
except ConnectionError as e:
_LOGGER.error("Connection error to Agua IOT: %s", e)
raise UpdateFailed("Connection error to Agua IOT: %s", e) from e
await self.agua.update()
except AguaIOTUnauthorized as e:
raise UpdateFailed(f"Agua IOT Unauthorized: {e}") from e
except AguaIOTConnectionError as e:
raise UpdateFailed(f"Agua IOT Connection error: {e}") from e
except AguaIOTTimeout as e:
raise UpdateFailed(f"Agua IOT Timeout: {e}") from e
except AguaIOTError as e:
_LOGGER.error("Unknown Agua IOT error: %s", e)
raise UpdateFailed("Unknown Agua IOT error: %s", e) from e
raise UpdateFailed(f"Agua IOT error: {e}") from e

async def _async_update_data(self) -> None:
"""Get the latest data."""
try:
await self.agua.update()
except UnauthorizedError as e:
_LOGGER.error("Agua IOT Unauthorized: %s", e)
raise UpdateFailed("Agua IOT Unauthorized: %s", e) from e
except ConnectionError as e:
_LOGGER.error("Connection error to Agua IOT: %s", e)
raise UpdateFailed("Connection error to Agua IOT: %s", e) from e
except AguaIOTUnauthorized as e:
raise UpdateFailed(f"Agua IOT Unauthorized: {e}") from e
except AguaIOTConnectionError as e:
raise UpdateFailed(f"Agua IOT Connection error: {e}") from e
except AguaIOTTimeout as e:
raise UpdateFailed(f"Agua IOT Timeout: {e}") from e
except AguaIOTError as e:
_LOGGER.error("Unknown Agua IOT error: %s", e)
raise UpdateFailed("Unknown Agua IOT error: %s", e) from e
raise UpdateFailed(f"Agua IOT error: {e}") from e
Loading