Upload files to "custom_components/aguaiot"
This commit is contained in:
711
custom_components/aguaiot/aguaiot.py
Normal file
711
custom_components/aguaiot/aguaiot.py
Normal file
@@ -0,0 +1,711 @@
|
|||||||
|
"""py_agua_iot provides controlling heating devices connected via
|
||||||
|
the IOT Agua platform of Micronova
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import jwt
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import httpx
|
||||||
|
from simpleeval import simple_eval
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
API_PATH_APP_SIGNUP = "/appSignup"
|
||||||
|
API_PATH_LOGIN = "/userLogin"
|
||||||
|
API_PATH_REFRESH_TOKEN = "/refreshToken"
|
||||||
|
API_PATH_DEVICE_LIST = "/deviceList"
|
||||||
|
API_PATH_DEVICE_INFO = "/deviceGetInfo"
|
||||||
|
API_PATH_DEVICE_REGISTERS_MAP = "/deviceGetRegistersMap"
|
||||||
|
API_PATH_DEVICE_BUFFER_READING = "/deviceGetBufferReading"
|
||||||
|
API_PATH_DEVICE_JOB_STATUS = "/deviceJobStatus/"
|
||||||
|
API_PATH_DEVICE_WRITING = "/deviceRequestWriting"
|
||||||
|
|
||||||
|
HEADER_ACCEPT = "application/json, text/javascript, */*; q=0.01"
|
||||||
|
HEADER_CONTENT_TYPE = "application/json"
|
||||||
|
HEADER = {"Accept": HEADER_ACCEPT, "Content-Type": HEADER_CONTENT_TYPE}
|
||||||
|
|
||||||
|
|
||||||
|
class aguaiot(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
api_url,
|
||||||
|
customer_code,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
unique_id,
|
||||||
|
login_api_url=None,
|
||||||
|
brand_id=None,
|
||||||
|
brand=None,
|
||||||
|
application_version="1.9.7",
|
||||||
|
async_client=None,
|
||||||
|
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
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.unique_id = unique_id
|
||||||
|
self.brand_id = brand_id
|
||||||
|
self.brand = brand
|
||||||
|
self.login_api_url = login_api_url
|
||||||
|
self.application_version = application_version
|
||||||
|
self.token = None
|
||||||
|
self.token_expires = None
|
||||||
|
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
|
||||||
|
self.reading_error_fix = reading_error_fix
|
||||||
|
self.language = language
|
||||||
|
|
||||||
|
if not self.async_client:
|
||||||
|
self.async_client = httpx.AsyncClient()
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
await self.register_app_id()
|
||||||
|
await self.login()
|
||||||
|
await self.fetch_devices()
|
||||||
|
await self.fetch_device_information()
|
||||||
|
|
||||||
|
def _headers(self):
|
||||||
|
"""Correctly set headers for requests to Agua IOT."""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Accept": HEADER_ACCEPT,
|
||||||
|
"Content-Type": HEADER_CONTENT_TYPE,
|
||||||
|
"Origin": "file://",
|
||||||
|
"id_brand": self.brand_id if self.brand_id is not None else "1",
|
||||||
|
"customer_code": self.customer_code,
|
||||||
|
}
|
||||||
|
if self.brand is not None:
|
||||||
|
headers["brand"] = self.brand
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
async def register_app_id(self):
|
||||||
|
"""Register app id with Agua IOT"""
|
||||||
|
|
||||||
|
url = self.api_url + API_PATH_APP_SIGNUP
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"phone_type": "Android",
|
||||||
|
"phone_id": self.unique_id,
|
||||||
|
"phone_version": "1.0",
|
||||||
|
"language": "en",
|
||||||
|
"id_app": self.unique_id,
|
||||||
|
"push_notification_token": self.unique_id,
|
||||||
|
"push_notification_active": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"POST Register app - HEADERS: %s DATA: %s", self._headers(), payload
|
||||||
|
)
|
||||||
|
async with self.async_client as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers=self._headers(),
|
||||||
|
follow_redirects=False,
|
||||||
|
timeout=self.http_timeout,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"RESPONSE Register app - CODE: %s DATA: %s",
|
||||||
|
response.status_code,
|
||||||
|
response.text,
|
||||||
|
)
|
||||||
|
except httpx.TransportError as 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 AguaIOTUnauthorized("Failed to register app id")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def login(self):
|
||||||
|
"""Authenticate with email and password to Agua IOT"""
|
||||||
|
|
||||||
|
url = self.api_url + API_PATH_LOGIN
|
||||||
|
|
||||||
|
payload = {"email": self.email, "password": self.password}
|
||||||
|
extra_headers = {"local": "true", "Authorization": self.unique_id}
|
||||||
|
|
||||||
|
headers = self._headers()
|
||||||
|
headers.update(extra_headers)
|
||||||
|
|
||||||
|
if self.login_api_url is not None:
|
||||||
|
extra_login_headers = {
|
||||||
|
"applicationversion": self.application_version,
|
||||||
|
"url": API_PATH_LOGIN.lstrip("/"),
|
||||||
|
"userid": "null",
|
||||||
|
"aguaid": "null",
|
||||||
|
}
|
||||||
|
headers.update(extra_login_headers)
|
||||||
|
url = self.login_api_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug("POST Login - HEADERS: %s DATA: ***", headers)
|
||||||
|
async with self.async_client as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
follow_redirects=False,
|
||||||
|
timeout=self.http_timeout,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"RESPONSE Login - CODE: %s DATA: %s",
|
||||||
|
response.status_code,
|
||||||
|
response.text,
|
||||||
|
)
|
||||||
|
except httpx.TransportError as 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 AguaIOTUnauthorized("Failed to login, please check credentials")
|
||||||
|
|
||||||
|
res = response.json()
|
||||||
|
self.token = res["token"]
|
||||||
|
self.refresh_token = res["refresh_token"]
|
||||||
|
|
||||||
|
claimset = jwt.decode(
|
||||||
|
res["token"], options={"verify_signature": False}, algorithms=["none"]
|
||||||
|
)
|
||||||
|
self.token_expires = claimset.get("exp")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def do_refresh_token(self):
|
||||||
|
"""Refresh auth token for Agua IOT"""
|
||||||
|
|
||||||
|
url = self.api_url + API_PATH_REFRESH_TOKEN
|
||||||
|
|
||||||
|
payload = {"refresh_token": self.refresh_token}
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"POST Refresh token - HEADERS: %s DATA: %s", self._headers(), payload
|
||||||
|
)
|
||||||
|
async with self.async_client as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers=self._headers(),
|
||||||
|
follow_redirects=False,
|
||||||
|
timeout=self.http_timeout,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"RESPONSE Refresh token - CODE: %s DATA: %s",
|
||||||
|
response.status_code,
|
||||||
|
response.text,
|
||||||
|
)
|
||||||
|
except httpx.TransportError as e:
|
||||||
|
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
_LOGGER.warning("Refresh auth token failed, forcing new login...")
|
||||||
|
await self.login()
|
||||||
|
return
|
||||||
|
|
||||||
|
res = response.json()
|
||||||
|
self.token = res["token"]
|
||||||
|
|
||||||
|
claimset = jwt.decode(
|
||||||
|
res["token"], options={"verify_signature": False}, algorithms=["none"]
|
||||||
|
)
|
||||||
|
self.token_expires = claimset.get("exp")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def fetch_devices(self):
|
||||||
|
"""Fetch heating devices"""
|
||||||
|
url = self.api_url + API_PATH_DEVICE_LIST
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
res = await self.handle_webcall("POST", url, payload)
|
||||||
|
if res is False:
|
||||||
|
raise AguaIOTError("Error while fetching devices")
|
||||||
|
|
||||||
|
for dev in res["device"]:
|
||||||
|
url = self.api_url + API_PATH_DEVICE_INFO
|
||||||
|
|
||||||
|
payload = {"id_device": dev["id_device"], "id_product": dev["id_product"]}
|
||||||
|
res2 = await self.handle_webcall("POST", url, payload)
|
||||||
|
if res2 is False:
|
||||||
|
raise AguaIOTError("Error while fetching device info")
|
||||||
|
|
||||||
|
self.devices.append(
|
||||||
|
Device(
|
||||||
|
dev["id"],
|
||||||
|
dev["id_device"],
|
||||||
|
dev["id_product"],
|
||||||
|
dev["product_serial"],
|
||||||
|
dev["name"],
|
||||||
|
dev["is_online"],
|
||||||
|
dev["name_product"],
|
||||||
|
res2["device_info"][0]["id_registers_map"],
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fetch_device_information(self):
|
||||||
|
"""Fetch device information of heating devices"""
|
||||||
|
for dev in self.devices:
|
||||||
|
await dev.update_mapping()
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
for dev in self.devices:
|
||||||
|
await dev.update()
|
||||||
|
|
||||||
|
async def handle_webcall(self, method, url, payload):
|
||||||
|
if time.time() > self.token_expires:
|
||||||
|
await self.do_refresh_token()
|
||||||
|
|
||||||
|
extra_headers = {"local": "false", "Authorization": self.token}
|
||||||
|
|
||||||
|
headers = self._headers()
|
||||||
|
headers.update(extra_headers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug("%s %s - HEADERS: %s DATA: %s", method, url, headers, payload)
|
||||||
|
if method == "POST":
|
||||||
|
async with self.async_client as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
follow_redirects=False,
|
||||||
|
timeout=self.http_timeout,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
async with self.async_client as client:
|
||||||
|
response = await client.get(
|
||||||
|
url,
|
||||||
|
params=payload,
|
||||||
|
headers=headers,
|
||||||
|
follow_redirects=False,
|
||||||
|
timeout=self.http_timeout,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"RESPONSE %s - CODE: %s DATA: %s",
|
||||||
|
url,
|
||||||
|
response.status_code,
|
||||||
|
response.text,
|
||||||
|
)
|
||||||
|
except httpx.TransportError as e:
|
||||||
|
raise AguaIOTConnectionError(f"Connection error to {url}: {e}")
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
await self.do_refresh_token()
|
||||||
|
return await self.handle_webcall(method, url, payload)
|
||||||
|
elif response.status_code != 200:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Webcall failed. Code: %s, Response: %s",
|
||||||
|
response.status_code,
|
||||||
|
response.text,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
class Device(object):
|
||||||
|
"""Agua IOT heating device representation"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id,
|
||||||
|
id_device,
|
||||||
|
id_product,
|
||||||
|
product_serial,
|
||||||
|
name,
|
||||||
|
is_online,
|
||||||
|
name_product,
|
||||||
|
id_registers_map,
|
||||||
|
aguaiot,
|
||||||
|
):
|
||||||
|
self.id = id
|
||||||
|
self.id_device = id_device
|
||||||
|
self.id_product = id_product
|
||||||
|
self.product_serial = product_serial
|
||||||
|
self.name = name
|
||||||
|
self.is_online = is_online
|
||||||
|
self.name_product = name_product
|
||||||
|
self.id_registers_map = id_registers_map
|
||||||
|
self.__aguaiot = aguaiot
|
||||||
|
self.__register_map_dict = dict()
|
||||||
|
self.__information_dict = dict()
|
||||||
|
|
||||||
|
async def update_mapping(self):
|
||||||
|
await self.__update_device_registers_mapping()
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
await self.__update_device_information()
|
||||||
|
|
||||||
|
async def __update_device_registers_mapping(self):
|
||||||
|
url = self.__aguaiot.api_url + API_PATH_DEVICE_REGISTERS_MAP
|
||||||
|
registers = dict()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"id_device": self.id_device,
|
||||||
|
"id_product": self.id_product,
|
||||||
|
"last_update": "2018-06-03T08:59:54.043",
|
||||||
|
}
|
||||||
|
|
||||||
|
res = await self.__aguaiot.handle_webcall("POST", url, payload)
|
||||||
|
if res is False:
|
||||||
|
raise AguaIOTError("Error while fetching registers map")
|
||||||
|
|
||||||
|
for registers_map in res["device_registers_map"]["registers_map"]:
|
||||||
|
if registers_map["id"] == self.id_registers_map:
|
||||||
|
registers = {
|
||||||
|
reg["reg_key"].lower(): reg for reg in registers_map["registers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.__register_map_dict = registers
|
||||||
|
|
||||||
|
async def __update_device_information(self):
|
||||||
|
url = self.__aguaiot.api_url + API_PATH_DEVICE_BUFFER_READING
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"id_device": self.id_device,
|
||||||
|
"id_product": self.id_product,
|
||||||
|
"BufferId": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
res_req = await self.__aguaiot.handle_webcall("POST", url, payload)
|
||||||
|
if res_req is False:
|
||||||
|
raise AguaIOTError("Error while making device buffer read request.")
|
||||||
|
|
||||||
|
async def buffer_read_loop(id_request):
|
||||||
|
url = self.__aguaiot.api_url + API_PATH_DEVICE_JOB_STATUS + id_request
|
||||||
|
sleep_secs = 1
|
||||||
|
attempts = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(sleep_secs)
|
||||||
|
|
||||||
|
_LOGGER.debug("BUFFER READ (%s) ATTEMPT %s", id_request, attempts)
|
||||||
|
res_get = await self.__aguaiot.handle_webcall("GET", url, {})
|
||||||
|
_LOGGER.debug(
|
||||||
|
"BUFFER READ (%s) STATUS: %s",
|
||||||
|
id_request,
|
||||||
|
res_get.get("jobAnswerStatus"),
|
||||||
|
)
|
||||||
|
if res_get.get("jobAnswerStatus") != "waiting":
|
||||||
|
return res_get
|
||||||
|
|
||||||
|
sleep_secs += 1
|
||||||
|
attempts += 1
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = await asyncio.wait_for(
|
||||||
|
buffer_read_loop(res_req["idRequest"]),
|
||||||
|
self.__aguaiot.buffer_read_timeout,
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
raise AguaIOTUpdateError(
|
||||||
|
f"Timeout on waiting device buffer read to complete within {self.__aguaiot.buffer_read_timeout} seconds."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not res:
|
||||||
|
raise AguaIOTUpdateError("Error while reading device buffer response.")
|
||||||
|
|
||||||
|
if res.get("jobAnswerStatus") == "completed":
|
||||||
|
current_i = 0
|
||||||
|
information_dict = dict()
|
||||||
|
try:
|
||||||
|
for item in res["jobAnswerData"]["Items"]:
|
||||||
|
information_dict.update(
|
||||||
|
{item: res["jobAnswerData"]["Values"][current_i]}
|
||||||
|
)
|
||||||
|
current_i = current_i + 1
|
||||||
|
except KeyError:
|
||||||
|
raise AguaIOTUpdateError("Error in data received from device.")
|
||||||
|
|
||||||
|
self.__information_dict = information_dict
|
||||||
|
else:
|
||||||
|
raise AguaIOTUpdateError(
|
||||||
|
f"Received unexpected 'jobAnswerStatus' while reading buffers: {res.get('jobAnswerStatus')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __prepare_value_for_writing(self, item, value, limit_value_raw=False):
|
||||||
|
set_min = self.__register_map_dict[item]["set_min"]
|
||||||
|
set_max = self.__register_map_dict[item]["set_max"]
|
||||||
|
|
||||||
|
if not limit_value_raw and (float(value) < set_min or float(value) > set_max):
|
||||||
|
raise ValueError(f"Value must be between {set_min} and {set_max}: {value}")
|
||||||
|
|
||||||
|
formula = self.__register_map_dict[item]["formula_inverse"]
|
||||||
|
formula = formula.replace("#", str(value))
|
||||||
|
formula = formula.replace("Mod", "%")
|
||||||
|
eval_formula = simple_eval(
|
||||||
|
formula,
|
||||||
|
functions={"IF": lambda a, b, c: b if a else c, "int": lambda a: int(a)},
|
||||||
|
)
|
||||||
|
value = int(eval_formula)
|
||||||
|
|
||||||
|
if limit_value_raw and (float(value) < set_min or float(value) > set_max):
|
||||||
|
raise ValueError(
|
||||||
|
f"Raw value must be between {set_min} and {set_max}: {value}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.__register_map_dict[item]["is_hex"]:
|
||||||
|
value = int(f"0x{value}", 16)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
async def __request_writing(self, items):
|
||||||
|
url = self.__aguaiot.api_url + API_PATH_DEVICE_WRITING
|
||||||
|
|
||||||
|
set_items = []
|
||||||
|
set_masks = []
|
||||||
|
set_bits = []
|
||||||
|
set_endians = []
|
||||||
|
set_values = []
|
||||||
|
|
||||||
|
for key in items:
|
||||||
|
set_items.append(int(self.__register_map_dict[key]["offset"]))
|
||||||
|
set_masks.append(int(self.__register_map_dict[key]["mask"]))
|
||||||
|
set_values.append(items[key])
|
||||||
|
set_bits.append(8)
|
||||||
|
set_endians.append("L")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"id_device": self.id_device,
|
||||||
|
"id_product": self.id_product,
|
||||||
|
"Protocol": "RWMSmaster",
|
||||||
|
"BitData": set_bits,
|
||||||
|
"Endianess": set_endians,
|
||||||
|
"Items": set_items,
|
||||||
|
"Masks": set_masks,
|
||||||
|
"Values": set_values,
|
||||||
|
}
|
||||||
|
|
||||||
|
res = await self.__aguaiot.handle_webcall("POST", url, payload)
|
||||||
|
if res is False:
|
||||||
|
raise AguaIOTError("Error while request device writing")
|
||||||
|
|
||||||
|
id_request = res["idRequest"]
|
||||||
|
|
||||||
|
url = self.__aguaiot.api_url + API_PATH_DEVICE_JOB_STATUS + id_request
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
retry_count = 0
|
||||||
|
res = await self.__aguaiot.handle_webcall("GET", url, payload)
|
||||||
|
while (
|
||||||
|
res is False or res["jobAnswerStatus"] != "completed"
|
||||||
|
) and retry_count < 10:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
res = await self.__aguaiot.handle_webcall("GET", url, payload)
|
||||||
|
retry_count = retry_count + 1
|
||||||
|
|
||||||
|
if (
|
||||||
|
res is False
|
||||||
|
or res["jobAnswerStatus"] != "completed"
|
||||||
|
or "Cmd" not in res["jobAnswerData"]
|
||||||
|
):
|
||||||
|
raise AguaIOTError("Error while request device writing")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def registers(self):
|
||||||
|
return list(self.__register_map_dict.keys())
|
||||||
|
|
||||||
|
def get_register(self, key):
|
||||||
|
register = self.__register_map_dict.get(key, {})
|
||||||
|
|
||||||
|
try:
|
||||||
|
register["value_raw"] = str(
|
||||||
|
self.__information_dict[register["offset"]] & register["mask"]
|
||||||
|
)
|
||||||
|
|
||||||
|
formula = register["formula"].replace("#", register["value_raw"])
|
||||||
|
formula = formula.replace("Mod", "%")
|
||||||
|
register["value"] = simple_eval(
|
||||||
|
formula,
|
||||||
|
functions={
|
||||||
|
"IF": lambda a, b, c: b if a else c,
|
||||||
|
"int": lambda a: int(a),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return register
|
||||||
|
|
||||||
|
def get_register_value(self, key):
|
||||||
|
value = self.get_register(key).get("value")
|
||||||
|
|
||||||
|
# Fix for reading errors from wifi module
|
||||||
|
if (
|
||||||
|
self.__aguaiot.reading_error_fix
|
||||||
|
and int(self.get_register(key).get("value_raw", 0)) == 32768
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
f"Applied reading_error_fix. Dropped value {value} for register {key}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fix for stoves abusing air temp register
|
||||||
|
if (
|
||||||
|
self.__aguaiot.air_temp_fix
|
||||||
|
and key.endswith("air_get")
|
||||||
|
and value
|
||||||
|
and int(value) > 100
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
f"Applied air_temp_fix. Dropped value {value} for register {key}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_register_value_min(self, key):
|
||||||
|
return self.get_register(key).get("set_min")
|
||||||
|
|
||||||
|
def get_register_value_max(self, key):
|
||||||
|
return self.get_register(key).get("set_max")
|
||||||
|
|
||||||
|
def get_register_value_formatted(self, key):
|
||||||
|
return str.format(
|
||||||
|
self.get_register(key).get("format_string"),
|
||||||
|
self.get_register(key).get("value"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_register_value_description(self, key, language=None):
|
||||||
|
options = self.get_register_value_options(key, language)
|
||||||
|
if options:
|
||||||
|
return options.get(
|
||||||
|
self.get_register_value(key), self.get_register_value(key)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self.get_register_value(key)
|
||||||
|
|
||||||
|
def get_register_value_options(self, key, language=None):
|
||||||
|
if "enc_val" in self.get_register(key):
|
||||||
|
lang = language if language else self.__aguaiot.language
|
||||||
|
if lang not in self.get_register_value_options_languages(key):
|
||||||
|
lang = "ENG"
|
||||||
|
|
||||||
|
return {
|
||||||
|
item["value"]: item["description"]
|
||||||
|
for item in self.get_register(key).get("enc_val")
|
||||||
|
if item["lang"] == lang
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_register_value_options_languages(self, key):
|
||||||
|
if "enc_val" in self.get_register(key):
|
||||||
|
return {item["lang"] for item in self.get_register(key).get("enc_val")}
|
||||||
|
return set()
|
||||||
|
|
||||||
|
def get_register_enabled(self, key):
|
||||||
|
enable_key = key.rsplit("_", 1)[0] + "_enable"
|
||||||
|
if enable_key not in self.registers or not self.get_register(enable_key):
|
||||||
|
# Always enabled if no enable register present
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.get_register(enable_key).get("reg_type") != "ENABLE":
|
||||||
|
raise AguaIOTError(f"Not a register of type ENABLE: {key}")
|
||||||
|
|
||||||
|
if "enable_val" in self.get_register(enable_key):
|
||||||
|
enabled_values = [
|
||||||
|
d["value"] for d in self.get_register(enable_key).get("enable_val")
|
||||||
|
]
|
||||||
|
return self.get_register_value(enable_key) in enabled_values
|
||||||
|
else:
|
||||||
|
return self.get_register_value(enable_key) == 1
|
||||||
|
|
||||||
|
async def set_register_value(self, key, value, limit_value_raw=False):
|
||||||
|
if value is None:
|
||||||
|
raise AguaIOTError(f"Error while trying to set '{key}' to None")
|
||||||
|
|
||||||
|
value = self.__prepare_value_for_writing(
|
||||||
|
key, value, limit_value_raw=limit_value_raw
|
||||||
|
)
|
||||||
|
items = {key: value}
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.__request_writing(items)
|
||||||
|
except AguaIOTError:
|
||||||
|
raise AguaIOTError(f"Error while trying to set: key={key} value={value}")
|
||||||
|
|
||||||
|
async def set_register_values(self, items, limit_value_raw=False):
|
||||||
|
for key in items:
|
||||||
|
items[key] = self.__prepare_value_for_writing(
|
||||||
|
key, items[key], limit_value_raw=limit_value_raw
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.__request_writing(items)
|
||||||
|
except AguaIOTError:
|
||||||
|
raise AguaIOTError(f"Error while trying to set: items={items}")
|
||||||
|
|
||||||
|
async def set_register_value_description(
|
||||||
|
self, key, value_description, value_fallback=None, language=None
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
options = self.get_register_value_options(key, language)
|
||||||
|
value = list(options.keys())[
|
||||||
|
list(options.values()).index(value_description)
|
||||||
|
]
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
value = value_description
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except ValueError:
|
||||||
|
value = value_fallback
|
||||||
|
|
||||||
|
await self.set_register_value(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTError(Exception):
|
||||||
|
"""Exception type for Agua IOT"""
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
Exception.__init__(self, message)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTUnauthorized(AguaIOTError):
|
||||||
|
"""Unauthorized"""
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTConnectionError(AguaIOTError):
|
||||||
|
"""Connection error"""
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTUpdateError(AguaIOTError):
|
||||||
|
"""Update error"""
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(message)
|
||||||
69
custom_components/aguaiot/binary_sensor.py
Normal file
69
custom_components/aguaiot/binary_sensor.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from .const import BINARY_SENSORS, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
coordinator = config_entry.runtime_data
|
||||||
|
agua = coordinator.agua
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
for device in agua.devices:
|
||||||
|
hybrid = "power_wood_set" in device.registers
|
||||||
|
|
||||||
|
for sensor in BINARY_SENSORS:
|
||||||
|
if (
|
||||||
|
sensor.key in device.registers
|
||||||
|
and (sensor.force_enabled or device.get_register_enabled(sensor.key))
|
||||||
|
and (not sensor.hybrid_only or hybrid)
|
||||||
|
):
|
||||||
|
sensors.append(AguaIOTHeatingBinarySensor(coordinator, device, sensor))
|
||||||
|
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTHeatingBinarySensor(CoordinatorEntity, BinarySensorEntity):
|
||||||
|
"""Binary sensor entity"""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(self, coordinator, device, description):
|
||||||
|
"""Initialize the thermostat."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._device = device
|
||||||
|
self.entity_description = description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return f"{self._device.id_device}_{self.entity_description.key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the device, if any."""
|
||||||
|
return self.entity_description.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
if self.is_on:
|
||||||
|
return self.entity_description.icon_on or self.entity_description.icon
|
||||||
|
else:
|
||||||
|
return self.entity_description.icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._device.id_device)},
|
||||||
|
name=self._device.name,
|
||||||
|
manufacturer="Micronova",
|
||||||
|
model=self._device.name_product,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return bool(self._device.get_register_value(self.entity_description.key))
|
||||||
701
custom_components/aguaiot/climate.py
Normal file
701
custom_components/aguaiot/climate.py
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
"""Support for Agua IOT heating devices."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import copy
|
||||||
|
import numbers
|
||||||
|
from homeassistant.helpers import entity_platform
|
||||||
|
from homeassistant.util import dt
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.components.climate import ClimateEntity
|
||||||
|
from homeassistant.components.climate.const import (
|
||||||
|
HVACAction,
|
||||||
|
HVACMode,
|
||||||
|
ClimateEntityFeature,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_TEMPERATURE,
|
||||||
|
UnitOfTemperature,
|
||||||
|
PRECISION_HALVES,
|
||||||
|
)
|
||||||
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
AIR_VARIANTS,
|
||||||
|
WATER_VARIANTS,
|
||||||
|
CLIMATE_CANALIZATIONS,
|
||||||
|
MODE_PELLETS,
|
||||||
|
MODE_WOOD,
|
||||||
|
STATUS_OFF,
|
||||||
|
STATUS_IDLE,
|
||||||
|
)
|
||||||
|
from .aguaiot import AguaIOTError
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
coordinator = config_entry.runtime_data
|
||||||
|
agua = coordinator.agua
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for device in agua.devices:
|
||||||
|
stove = AguaIOTAirDevice(coordinator, device)
|
||||||
|
entities.append(stove)
|
||||||
|
|
||||||
|
if any(f"temp_{variant}_set" in device.registers for variant in WATER_VARIANTS):
|
||||||
|
entities.append(AguaIOTWaterDevice(coordinator, device, stove))
|
||||||
|
|
||||||
|
for canalization in CLIMATE_CANALIZATIONS:
|
||||||
|
for c_found in [
|
||||||
|
m
|
||||||
|
for i in device.registers
|
||||||
|
for m in [re.match(canalization.key, i.lower())]
|
||||||
|
if m
|
||||||
|
]:
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
canalization.key_enable
|
||||||
|
and device.get_register_enabled(
|
||||||
|
canalization.key_enable.format(id=c_found.group(1))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
canalization.key2_enable
|
||||||
|
and device.get_register_enabled(
|
||||||
|
canalization.key2_enable.format(id=c_found.group(1))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
not canalization.key_enable
|
||||||
|
and device.get_register_enabled(c_found.group(0))
|
||||||
|
)
|
||||||
|
):
|
||||||
|
c_copy = copy.deepcopy(canalization)
|
||||||
|
c_copy.key = c_found.group(0)
|
||||||
|
for key in [
|
||||||
|
"name",
|
||||||
|
"key_temp_set",
|
||||||
|
"key_temp_get",
|
||||||
|
"key_temp2_get",
|
||||||
|
"key_vent_set",
|
||||||
|
]:
|
||||||
|
if getattr(c_copy, key):
|
||||||
|
setattr(
|
||||||
|
c_copy,
|
||||||
|
key,
|
||||||
|
getattr(c_copy, key).format_map(c_found.groupdict()),
|
||||||
|
)
|
||||||
|
|
||||||
|
entities.append(
|
||||||
|
AguaIOTCanalizationDevice(coordinator, device, c_copy, stove)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
platform = entity_platform.async_get_current_platform()
|
||||||
|
platform.async_register_entity_service(
|
||||||
|
"sync_clock",
|
||||||
|
{},
|
||||||
|
"sync_clock",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTClimateDevice(CoordinatorEntity, ClimateEntity):
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._device.id_device)},
|
||||||
|
name=self._device.name,
|
||||||
|
manufacturer="Micronova",
|
||||||
|
model=self._device.name_product,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def precision(self):
|
||||||
|
"""Return the precision of the system."""
|
||||||
|
return PRECISION_HALVES
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTAirDevice(AguaIOTClimateDevice):
|
||||||
|
"""Representation of an Agua IOT heating device."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
def __init__(self, coordinator, device):
|
||||||
|
"""Initialize the thermostat."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._enable_turn_on_off_backwards_compatibility = False
|
||||||
|
self._device = device
|
||||||
|
self._hybrid = "power_wood_set" in device.registers
|
||||||
|
|
||||||
|
self._temperature_get_key = None
|
||||||
|
for variant in AIR_VARIANTS:
|
||||||
|
if (
|
||||||
|
f"temp_{variant}_get" in self._device.registers
|
||||||
|
and self._device.get_register_enabled(f"temp_{variant}_get")
|
||||||
|
and self._device.get_register_value(f"temp_{variant}_get")
|
||||||
|
):
|
||||||
|
self._temperature_get_key = f"temp_{variant}_get"
|
||||||
|
break
|
||||||
|
|
||||||
|
self._temperature_set_key = None
|
||||||
|
for variant in AIR_VARIANTS:
|
||||||
|
if (
|
||||||
|
f"temp_{variant}_set" in self._device.registers
|
||||||
|
and self._device.get_register_enabled(f"temp_{variant}_set")
|
||||||
|
and self._device.get_register_value(f"temp_{variant}_set")
|
||||||
|
):
|
||||||
|
self._temperature_set_key = f"temp_{variant}_set"
|
||||||
|
break
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return self._device.id_device
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return the list of supported features."""
|
||||||
|
features = (
|
||||||
|
ClimateEntityFeature.TURN_ON
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.FAN_MODE
|
||||||
|
)
|
||||||
|
if self._hybrid:
|
||||||
|
features = features | ClimateEntityFeature.PRESET_MODE
|
||||||
|
return features
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self):
|
||||||
|
"""Return the current running hvac operation."""
|
||||||
|
if self._device.get_register_value("status_get") is not None:
|
||||||
|
if (
|
||||||
|
str(
|
||||||
|
self._device.get_register_value_description(
|
||||||
|
key="status_get", language="ENG"
|
||||||
|
)
|
||||||
|
).upper()
|
||||||
|
in STATUS_IDLE
|
||||||
|
):
|
||||||
|
return HVACAction.IDLE
|
||||||
|
elif (
|
||||||
|
self._device.get_register_value("status_get") == 0
|
||||||
|
or str(
|
||||||
|
self._device.get_register_value_description(
|
||||||
|
key="status_get", language="ENG"
|
||||||
|
)
|
||||||
|
).upper()
|
||||||
|
in STATUS_OFF
|
||||||
|
):
|
||||||
|
return HVACAction.OFF
|
||||||
|
return HVACAction.HEATING
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
"""Return the list of available hvac operation modes."""
|
||||||
|
return [HVACMode.HEAT, HVACMode.OFF]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self):
|
||||||
|
"""Return hvac operation ie. heat, cool mode."""
|
||||||
|
if self._device.get_register_value("status_get") is not None:
|
||||||
|
if (
|
||||||
|
self._device.get_register_value("status_get") == 0
|
||||||
|
or str(
|
||||||
|
self._device.get_register_value_description(
|
||||||
|
key="status_get", language="ENG"
|
||||||
|
)
|
||||||
|
).upper()
|
||||||
|
in STATUS_OFF
|
||||||
|
):
|
||||||
|
return HVACMode.OFF
|
||||||
|
return HVACMode.HEAT
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
|
"""Set new target hvac mode."""
|
||||||
|
if hvac_mode == HVACMode.OFF:
|
||||||
|
await self.async_turn_off()
|
||||||
|
elif hvac_mode == HVACMode.HEAT:
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hybrid_mode(self):
|
||||||
|
return (
|
||||||
|
MODE_WOOD
|
||||||
|
if self._hybrid and self._device.get_register_enabled("real_power_wood_get")
|
||||||
|
else MODE_PELLETS
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self):
|
||||||
|
"""Return fan mode."""
|
||||||
|
power_register = (
|
||||||
|
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
|
||||||
|
)
|
||||||
|
return str(self._device.get_register_value_description(power_register))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self):
|
||||||
|
"""Return the list of available fan modes."""
|
||||||
|
fan_modes = []
|
||||||
|
power_register = (
|
||||||
|
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
|
||||||
|
)
|
||||||
|
for x in range(
|
||||||
|
self._device.get_register_value_min(power_register),
|
||||||
|
(self._device.get_register_value_max(power_register) + 1),
|
||||||
|
):
|
||||||
|
fan_modes.append(
|
||||||
|
str(self._device.get_register_value_options(power_register).get(x, x))
|
||||||
|
)
|
||||||
|
return fan_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self):
|
||||||
|
return [self.hybrid_mode]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self):
|
||||||
|
return self.hybrid_mode
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
|
# The stove will pick the correct mode.
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def async_turn_off(self):
|
||||||
|
"""Turn device off."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
key="status_managed_get",
|
||||||
|
value_description="OFF",
|
||||||
|
value_fallback=170,
|
||||||
|
language="ENG",
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to turn off device, error: %s", err)
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
|
"""Turn device on."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
key="status_managed_get",
|
||||||
|
value_description="ON",
|
||||||
|
value_fallback=85,
|
||||||
|
language="ENG",
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to turn on device, error: %s", err)
|
||||||
|
|
||||||
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
|
"""Set new target fan mode."""
|
||||||
|
power_register = (
|
||||||
|
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(power_register, fan_mode)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to set fan mode, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature to set."""
|
||||||
|
return self._device.get_register_value_min(self._temperature_set_key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature to set."""
|
||||||
|
return self._device.get_register_value_max(self._temperature_set_key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
if self._temperature_get_key:
|
||||||
|
value = self._device.get_register_value_description(
|
||||||
|
self._temperature_get_key
|
||||||
|
)
|
||||||
|
if isinstance(value, numbers.Number):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
if self.current_temperature:
|
||||||
|
return self._device.get_register_value(self._temperature_set_key)
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperature."""
|
||||||
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if temperature is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value(
|
||||||
|
self._temperature_set_key, temperature
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except (ValueError, AguaIOTError) as err:
|
||||||
|
_LOGGER.error("Failed to set temperature, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self):
|
||||||
|
"""Return the supported step of target temperature."""
|
||||||
|
return self._device.get_register(self._temperature_set_key).get("step", 1)
|
||||||
|
|
||||||
|
async def sync_clock(self):
|
||||||
|
dt_now = dt.now()
|
||||||
|
try:
|
||||||
|
await self._device.set_register_values(
|
||||||
|
{
|
||||||
|
"clock_hour_set": dt_now.hour,
|
||||||
|
"clock_minute_set": dt_now.minute,
|
||||||
|
"calendar_day_set": dt_now.day,
|
||||||
|
"calendar_month_set": dt_now.month,
|
||||||
|
"calendar_year_set": dt_now.year,
|
||||||
|
},
|
||||||
|
limit_value_raw=True,
|
||||||
|
)
|
||||||
|
except (ValueError, AguaIOTError) as err:
|
||||||
|
_LOGGER.error("Failed to set value, error: %s", err)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTWaterDevice(AguaIOTClimateDevice):
|
||||||
|
"""Representation of an Agua IOT heating device."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = "Water"
|
||||||
|
_attr_icon = "mdi:water"
|
||||||
|
|
||||||
|
def __init__(self, coordinator, device, parent):
|
||||||
|
"""Initialize the thermostat."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._enable_turn_on_off_backwards_compatibility = False
|
||||||
|
self._device = device
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._temperature_get_key = None
|
||||||
|
for variant in WATER_VARIANTS:
|
||||||
|
if (
|
||||||
|
f"temp_{variant}_get" in self._device.registers
|
||||||
|
and self._device.get_register_enabled(f"temp_{variant}_get")
|
||||||
|
and self._device.get_register_value(f"temp_{variant}_get")
|
||||||
|
):
|
||||||
|
self._temperature_get_key = f"temp_{variant}_get"
|
||||||
|
break
|
||||||
|
|
||||||
|
self._temperature_set_key = None
|
||||||
|
for variant in WATER_VARIANTS:
|
||||||
|
if (
|
||||||
|
f"temp_{variant}_set" in self._device.registers
|
||||||
|
and self._device.get_register_enabled(f"temp_{variant}_set")
|
||||||
|
and self._device.get_register_value(f"temp_{variant}_set")
|
||||||
|
):
|
||||||
|
self._temperature_set_key = f"temp_{variant}_set"
|
||||||
|
break
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
return f"{self._device.id_device}_water"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return the list of supported features."""
|
||||||
|
features = (
|
||||||
|
ClimateEntityFeature.TURN_ON
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
)
|
||||||
|
return features
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self):
|
||||||
|
return self._parent.hvac_action
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
return self._parent.hvac_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self):
|
||||||
|
return self._parent.hvac_mode
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
|
"""Set new target hvac mode."""
|
||||||
|
if hvac_mode == HVACMode.OFF:
|
||||||
|
await self.async_turn_off()
|
||||||
|
elif hvac_mode == HVACMode.HEAT:
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
async def async_turn_off(self):
|
||||||
|
"""Turn device off."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
key="status_managed_get",
|
||||||
|
value_description="OFF",
|
||||||
|
value_fallback=170,
|
||||||
|
language="ENG",
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to turn off device, error: %s", err)
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
|
"""Turn device on."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
key="status_managed_get",
|
||||||
|
value_description="ON",
|
||||||
|
value_fallback=85,
|
||||||
|
language="ENG",
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to turn on device, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature to set."""
|
||||||
|
return self._device.get_register_value_min(self._temperature_set_key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature to set."""
|
||||||
|
return self._device.get_register_value_max(self._temperature_set_key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
if self._temperature_get_key:
|
||||||
|
value = self._device.get_register_value_description(
|
||||||
|
self._temperature_get_key
|
||||||
|
)
|
||||||
|
if isinstance(value, numbers.Number):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
if self.current_temperature:
|
||||||
|
return self._device.get_register_value(self._temperature_set_key)
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperature."""
|
||||||
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if temperature is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value(
|
||||||
|
self._temperature_set_key, temperature
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except (ValueError, AguaIOTError) as err:
|
||||||
|
_LOGGER.error("Failed to set temperature, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self):
|
||||||
|
"""Return the supported step of target temperature."""
|
||||||
|
return self._device.get_register(self._temperature_set_key).get("step", 1)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTCanalizationDevice(AguaIOTClimateDevice):
|
||||||
|
"""Canalization device"""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(self, coordinator, device, description, parent):
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._enable_turn_on_off_backwards_compatibility = False
|
||||||
|
self._device = device
|
||||||
|
self._parent = parent
|
||||||
|
self.entity_description = description
|
||||||
|
self._fan_register = self.entity_description.key
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.entity_description.key_vent_set
|
||||||
|
and self.entity_description.key_vent_set in self._device.registers
|
||||||
|
):
|
||||||
|
self._fan_register = self.entity_description.key_vent_set
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
return f"{self._device.id_device}_{self.entity_description.key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.entity_description.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
features = ClimateEntityFeature.FAN_MODE
|
||||||
|
if (
|
||||||
|
self.entity_description.key_temp_set
|
||||||
|
and self.entity_description.key_temp_set in self._device.registers
|
||||||
|
and self._device.get_register_enabled(self.entity_description.key_temp_set)
|
||||||
|
):
|
||||||
|
features |= ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
if (
|
||||||
|
self.entity_description.key_vent_set
|
||||||
|
and self.entity_description.key_vent_set in self._device.registers
|
||||||
|
):
|
||||||
|
features |= ClimateEntityFeature.PRESET_MODE
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self):
|
||||||
|
"""Return fan mode."""
|
||||||
|
return str(self._device.get_register_value_description(self._fan_register))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self):
|
||||||
|
"""Return the list of available fan modes."""
|
||||||
|
fan_modes = []
|
||||||
|
for x in range(
|
||||||
|
self._device.get_register_value_min(self._fan_register),
|
||||||
|
(self._device.get_register_value_max(self._fan_register) + 1),
|
||||||
|
):
|
||||||
|
fan_modes.append(
|
||||||
|
str(
|
||||||
|
self._device.get_register_value_options(self._fan_register).get(
|
||||||
|
x, x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return fan_modes
|
||||||
|
|
||||||
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
|
"""Set new target fan mode."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
self._fan_register, fan_mode
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to set fan mode, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self):
|
||||||
|
return list(
|
||||||
|
self._device.get_register_value_options(
|
||||||
|
self.entity_description.key
|
||||||
|
).values()
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self):
|
||||||
|
return self._device.get_register_value_description(self.entity_description.key)
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
|
"""Set new target preset mode."""
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value_description(
|
||||||
|
self.entity_description.key, preset_mode
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except AguaIOTError as err:
|
||||||
|
_LOGGER.error("Failed to set preset mode, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self):
|
||||||
|
if self._device.get_register_value(self.entity_description.key):
|
||||||
|
return self._parent.hvac_action
|
||||||
|
return HVACAction.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
if self._device.get_register_value(self.entity_description.key):
|
||||||
|
return [self._parent.hvac_mode]
|
||||||
|
return [HVACMode.OFF]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self):
|
||||||
|
if self._device.get_register_value(self.entity_description.key):
|
||||||
|
return self._parent.hvac_mode
|
||||||
|
return HVACMode.OFF
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature to set."""
|
||||||
|
if self.entity_description.key_temp_set in self._device.registers:
|
||||||
|
return self._device.get_register_value_min(
|
||||||
|
self.entity_description.key_temp_set
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature to set."""
|
||||||
|
if self.entity_description.key_temp_set in self._device.registers:
|
||||||
|
return self._device.get_register_value_max(
|
||||||
|
self.entity_description.key_temp_set
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
if self.entity_description.key_temp_set in self._device.registers:
|
||||||
|
if self.current_temperature:
|
||||||
|
return self._device.get_register_value(
|
||||||
|
self.entity_description.key_temp_set
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
if (
|
||||||
|
self.entity_description.key_temp_get in self._device.registers
|
||||||
|
and self._device.get_register_enabled(self.entity_description.key_temp_get)
|
||||||
|
):
|
||||||
|
value = self._device.get_register_value_description(
|
||||||
|
self.entity_description.key_temp_get
|
||||||
|
)
|
||||||
|
if isinstance(value, numbers.Number):
|
||||||
|
return value
|
||||||
|
elif (
|
||||||
|
self.entity_description.key_temp2_get
|
||||||
|
and self.entity_description.key_temp2_get in self._device.registers
|
||||||
|
and self._device.get_register_enabled(self.entity_description.key_temp2_get)
|
||||||
|
):
|
||||||
|
value = self._device.get_register_value_description(
|
||||||
|
self.entity_description.key_temp2_get
|
||||||
|
)
|
||||||
|
if isinstance(value, numbers.Number):
|
||||||
|
return value
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperature."""
|
||||||
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if temperature is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._device.set_register_value(
|
||||||
|
self.entity_description.key_temp_set, temperature
|
||||||
|
)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
except (ValueError, AguaIOTError) as err:
|
||||||
|
_LOGGER.error("Failed to set temperature, error: %s", err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self):
|
||||||
|
"""Return the supported step of target temperature."""
|
||||||
|
if self.entity_description.key_temp_set in self._device.registers:
|
||||||
|
return self._device.get_register(self.entity_description.key_temp_set).get(
|
||||||
|
"step", 1
|
||||||
|
)
|
||||||
228
custom_components/aguaiot/config_flow.py
Normal file
228
custom_components/aguaiot/config_flow.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"""Config flow for Agua IOT."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from .aguaiot import (
|
||||||
|
AguaIOTConnectionError,
|
||||||
|
AguaIOTError,
|
||||||
|
AguaIOTUnauthorized,
|
||||||
|
aguaiot,
|
||||||
|
)
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
ConfigEntry,
|
||||||
|
ConfigFlow,
|
||||||
|
OptionsFlowWithReload,
|
||||||
|
CONN_CLASS_CLOUD_POLL,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.httpx_client import get_async_client
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_API_URL,
|
||||||
|
CONF_CUSTOMER_CODE,
|
||||||
|
CONF_LOGIN_API_URL,
|
||||||
|
CONF_UUID,
|
||||||
|
CONF_ENDPOINT,
|
||||||
|
CONF_BRAND_ID,
|
||||||
|
CONF_BRAND,
|
||||||
|
CONF_LANGUAGE,
|
||||||
|
CONF_AIR_TEMP_FIX,
|
||||||
|
CONF_READING_ERROR_FIX,
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
CONF_HTTP_TIMEOUT,
|
||||||
|
CONF_BUFFER_READ_TIMEOUT,
|
||||||
|
DOMAIN,
|
||||||
|
ENDPOINTS,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def conf_entries(hass):
|
||||||
|
"""Return the email tuples for the domain."""
|
||||||
|
return set(
|
||||||
|
(entry.data[CONF_EMAIL], entry.data[CONF_API_URL])
|
||||||
|
for entry in hass.config_entries.async_entries(DOMAIN)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Agua IOT Config Flow handler."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
def _entry_in_configuration_exists(self, user_input) -> bool:
|
||||||
|
"""Return True if config already exists in configuration."""
|
||||||
|
email = user_input[CONF_EMAIL]
|
||||||
|
host_server = ENDPOINTS[user_input[CONF_ENDPOINT]][CONF_API_URL]
|
||||||
|
if (email, host_server) in conf_entries(self.hass):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""User initiated integration."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
# Validate user input
|
||||||
|
email = user_input[CONF_EMAIL]
|
||||||
|
password = user_input[CONF_PASSWORD]
|
||||||
|
|
||||||
|
endpoint = user_input[CONF_ENDPOINT]
|
||||||
|
api_url = ENDPOINTS[endpoint][CONF_API_URL]
|
||||||
|
customer_code = ENDPOINTS[endpoint][CONF_CUSTOMER_CODE]
|
||||||
|
login_api_url = ENDPOINTS[endpoint].get(CONF_LOGIN_API_URL)
|
||||||
|
brand_id = ENDPOINTS[endpoint].get(CONF_BRAND_ID)
|
||||||
|
brand = ENDPOINTS[endpoint].get(CONF_BRAND)
|
||||||
|
|
||||||
|
if self._entry_in_configuration_exists(user_input):
|
||||||
|
return self.async_abort(reason="device_already_configured")
|
||||||
|
|
||||||
|
try:
|
||||||
|
gen_uuid = str(uuid.uuid1())
|
||||||
|
agua = aguaiot(
|
||||||
|
api_url=api_url,
|
||||||
|
customer_code=customer_code,
|
||||||
|
email=email,
|
||||||
|
password=password,
|
||||||
|
unique_id=gen_uuid,
|
||||||
|
login_api_url=login_api_url,
|
||||||
|
brand_id=brand_id,
|
||||||
|
brand=brand,
|
||||||
|
async_client=get_async_client(self.hass),
|
||||||
|
)
|
||||||
|
await agua.connect()
|
||||||
|
except AguaIOTUnauthorized as e:
|
||||||
|
_LOGGER.error("Agua IOT Unauthorized: %s", e)
|
||||||
|
errors["base"] = "unauthorized"
|
||||||
|
except AguaIOTConnectionError as e:
|
||||||
|
_LOGGER.error("Agua IOT Connection error: %s", e)
|
||||||
|
errors["base"] = "connection_error"
|
||||||
|
except AguaIOTError as e:
|
||||||
|
_LOGGER.error("Agua IOT error: %s", e)
|
||||||
|
errors["base"] = "unknown_error"
|
||||||
|
|
||||||
|
if "base" not in errors:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=endpoint,
|
||||||
|
data={
|
||||||
|
CONF_EMAIL: email,
|
||||||
|
CONF_PASSWORD: password,
|
||||||
|
CONF_UUID: gen_uuid,
|
||||||
|
CONF_API_URL: api_url,
|
||||||
|
CONF_CUSTOMER_CODE: customer_code,
|
||||||
|
CONF_LOGIN_API_URL: login_api_url,
|
||||||
|
CONF_BRAND_ID: brand_id,
|
||||||
|
CONF_BRAND: brand,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
user_input = {}
|
||||||
|
|
||||||
|
data_schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_ENDPOINT, default=user_input.get(CONF_ENDPOINT)
|
||||||
|
): vol.In(ENDPOINTS.keys()),
|
||||||
|
vol.Required(CONF_EMAIL, default=user_input.get(CONF_EMAIL)): str,
|
||||||
|
vol.Required(CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=data_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry: ConfigEntry):
|
||||||
|
return AguaIOTOptionsFlowHandler()
|
||||||
|
|
||||||
|
|
||||||
|
class AguaIOTOptionsFlowHandler(OptionsFlowWithReload):
|
||||||
|
async def async_step_init(self, _user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
return await self.async_step_user()
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
"""Set up AguaIOT entry."""
|
||||||
|
entry = self.config_entry
|
||||||
|
|
||||||
|
api_url = entry.data.get(CONF_API_URL)
|
||||||
|
customer_code = entry.data.get(CONF_CUSTOMER_CODE)
|
||||||
|
email = entry.data.get(CONF_EMAIL)
|
||||||
|
password = entry.data.get(CONF_PASSWORD)
|
||||||
|
gen_uuid = entry.data.get(CONF_UUID)
|
||||||
|
login_api_url = entry.data.get(CONF_LOGIN_API_URL)
|
||||||
|
brand_id = entry.data.get(CONF_BRAND_ID)
|
||||||
|
brand = entry.data.get(CONF_BRAND)
|
||||||
|
|
||||||
|
agua = aguaiot(
|
||||||
|
api_url=api_url,
|
||||||
|
customer_code=customer_code,
|
||||||
|
email=email,
|
||||||
|
password=password,
|
||||||
|
unique_id=gen_uuid,
|
||||||
|
login_api_url=login_api_url,
|
||||||
|
brand_id=brand_id,
|
||||||
|
brand=brand,
|
||||||
|
async_client=get_async_client(self.hass),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await agua.connect()
|
||||||
|
except AguaIOTUnauthorized as e:
|
||||||
|
_LOGGER.error("Agua IOT Unauthorized: %s", e)
|
||||||
|
return False
|
||||||
|
except AguaIOTConnectionError as e:
|
||||||
|
_LOGGER.error("Agua IOT Connection error: %s", e)
|
||||||
|
return False
|
||||||
|
except AguaIOTError as e:
|
||||||
|
_LOGGER.error("Agua IOT error: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
languages = ["ENG"]
|
||||||
|
if agua.devices:
|
||||||
|
languages = sorted(
|
||||||
|
list(
|
||||||
|
agua.devices[0].get_register_value_options_languages(
|
||||||
|
"status_managed_get"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
vol.Optional(
|
||||||
|
CONF_AIR_TEMP_FIX,
|
||||||
|
default=self.config_entry.options.get(CONF_AIR_TEMP_FIX, False),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_READING_ERROR_FIX,
|
||||||
|
default=self.config_entry.options.get(CONF_READING_ERROR_FIX, False),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
default=self.config_entry.options.get(CONF_UPDATE_INTERVAL, 60),
|
||||||
|
): vol.All(vol.Coerce(int), vol.Range(min=10)),
|
||||||
|
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"),
|
||||||
|
): vol.In(languages),
|
||||||
|
}
|
||||||
|
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
|
||||||
625
custom_components/aguaiot/const.py
Normal file
625
custom_components/aguaiot/const.py
Normal file
@@ -0,0 +1,625 @@
|
|||||||
|
"""Agua IOT constants."""
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
Platform,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfPressure,
|
||||||
|
REVOLUTIONS_PER_MINUTE,
|
||||||
|
)
|
||||||
|
from homeassistant.components.binary_sensor import (
|
||||||
|
BinarySensorDeviceClass,
|
||||||
|
BinarySensorEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.components.switch import (
|
||||||
|
SwitchDeviceClass,
|
||||||
|
SwitchEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.components.number import (
|
||||||
|
NumberDeviceClass,
|
||||||
|
NumberEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.components.climate import (
|
||||||
|
ClimateEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.components.select import SelectEntityDescription
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AguaIOTBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||||
|
force_enabled: bool = False
|
||||||
|
hybrid_only: bool = False
|
||||||
|
icon_on: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AguaIOTSensorEntityDescription(SensorEntityDescription):
|
||||||
|
force_enabled: bool = False
|
||||||
|
hybrid_only: bool = False
|
||||||
|
hybrid_exclude: bool = False
|
||||||
|
raw_value: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AguaIOTNumberEntityDescription(NumberEntityDescription):
|
||||||
|
force_enabled: bool = False
|
||||||
|
hybrid_only: bool = False
|
||||||
|
hybrid_exclude: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AguaIOTCanalizationEntityDescription(ClimateEntityDescription):
|
||||||
|
key_temp_set: str | None = None
|
||||||
|
key_temp_get: str | None = None
|
||||||
|
key_temp2_get: str | None = None
|
||||||
|
key_vent_set: str | None = None
|
||||||
|
key_enable: str | None = None
|
||||||
|
key2_enable: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = "aguaiot"
|
||||||
|
CONF_API_URL = "api_url"
|
||||||
|
CONF_CUSTOMER_CODE = "customer_code"
|
||||||
|
CONF_LOGIN_API_URL = "login_api_url"
|
||||||
|
CONF_UUID = "uuid"
|
||||||
|
CONF_ENDPOINT = "endpoint"
|
||||||
|
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_UPDATE_INTERVAL = "update_interval"
|
||||||
|
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"]
|
||||||
|
|
||||||
|
MODE_WOOD = "Wood"
|
||||||
|
MODE_PELLETS = "Pellet"
|
||||||
|
|
||||||
|
STATUS_OFF = ["OFF", "FINAL CLEANING", "STOP", "SHUT OFF", "0", "6"]
|
||||||
|
STATUS_IDLE = [
|
||||||
|
"ECO STOP",
|
||||||
|
"STANDBY",
|
||||||
|
"STAND BY",
|
||||||
|
"STAND-BY",
|
||||||
|
"ALARM",
|
||||||
|
"MEMORY ALARM",
|
||||||
|
"ALARM MEMORY",
|
||||||
|
"MEM.ALM",
|
||||||
|
"MEM. ALARM",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
]
|
||||||
|
|
||||||
|
PLATFORMS = [
|
||||||
|
Platform.CLIMATE,
|
||||||
|
Platform.BINARY_SENSOR,
|
||||||
|
Platform.SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
|
Platform.NUMBER,
|
||||||
|
Platform.SELECT,
|
||||||
|
]
|
||||||
|
|
||||||
|
ENDPOINTS = {
|
||||||
|
"Alfapalm": {
|
||||||
|
CONF_CUSTOMER_CODE: "862148",
|
||||||
|
CONF_API_URL: "https://alfaplam.agua-iot.com",
|
||||||
|
},
|
||||||
|
"APP-O BIOEN": {
|
||||||
|
CONF_CUSTOMER_CODE: "289982",
|
||||||
|
CONF_API_URL: "https://unical.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Boreal Home": {
|
||||||
|
CONF_CUSTOMER_CODE: "173118",
|
||||||
|
CONF_API_URL: "https://boreal.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Bronpi Home": {
|
||||||
|
CONF_CUSTOMER_CODE: "164873",
|
||||||
|
CONF_API_URL: "https://bronpi.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Darwin Evolution": {
|
||||||
|
CONF_CUSTOMER_CODE: "475219",
|
||||||
|
CONF_API_URL: "https://cola.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Easy Connect": {
|
||||||
|
CONF_CUSTOMER_CODE: "354924",
|
||||||
|
CONF_API_URL: "https://remote.mcz.it",
|
||||||
|
},
|
||||||
|
"Easy Connect Plus": {
|
||||||
|
CONF_CUSTOMER_CODE: "746318",
|
||||||
|
CONF_API_URL: "https://remote.mcz.it",
|
||||||
|
},
|
||||||
|
"Easy Connect Poêle": {
|
||||||
|
CONF_CUSTOMER_CODE: "354925",
|
||||||
|
CONF_API_URL: "https://remote.mcz.it",
|
||||||
|
},
|
||||||
|
"Elcofire Pellet Home": {
|
||||||
|
CONF_CUSTOMER_CODE: "132679",
|
||||||
|
CONF_API_URL: "https://elcofire.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Elfire Wifi": {
|
||||||
|
CONF_CUSTOMER_CODE: "402762",
|
||||||
|
CONF_API_URL: "https://elfire.agua-iot.com",
|
||||||
|
},
|
||||||
|
"EvaCalòr - PuntoFuoco": {
|
||||||
|
CONF_CUSTOMER_CODE: "635987",
|
||||||
|
CONF_API_URL: "https://evastampaggi.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Fontana Forni": {
|
||||||
|
CONF_CUSTOMER_CODE: "505912",
|
||||||
|
CONF_API_URL: "https://fontanaforni.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Fonte Flamme contrôle 1": {
|
||||||
|
CONF_CUSTOMER_CODE: "848324",
|
||||||
|
CONF_API_URL: "https://fonteflame.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Globe-fire": {
|
||||||
|
CONF_CUSTOMER_CODE: "634876",
|
||||||
|
CONF_API_URL: "https://globefire.agua-iot.com",
|
||||||
|
},
|
||||||
|
"GO HEAT": {
|
||||||
|
CONF_CUSTOMER_CODE: "859435",
|
||||||
|
CONF_API_URL: "https://amg.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Jolly Mec Wi Fi": {
|
||||||
|
CONF_CUSTOMER_CODE: "732584",
|
||||||
|
CONF_API_URL: "https://jollymec.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Karmek Wifi": {
|
||||||
|
CONF_CUSTOMER_CODE: "403873",
|
||||||
|
CONF_API_URL: "https://karmekone.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Klover Home": {
|
||||||
|
CONF_CUSTOMER_CODE: "143789",
|
||||||
|
CONF_API_URL: "https://klover.agua-iot.com",
|
||||||
|
},
|
||||||
|
"L'artistico": {
|
||||||
|
CONF_CUSTOMER_CODE: "635912",
|
||||||
|
CONF_API_URL: "https://api.micronovasrl.com",
|
||||||
|
},
|
||||||
|
"LAMINOX Remote Control (2.0)": {
|
||||||
|
CONF_CUSTOMER_CODE: "352678",
|
||||||
|
CONF_API_URL: "https://laminox.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Lorflam Home": {
|
||||||
|
CONF_CUSTOMER_CODE: "121567",
|
||||||
|
CONF_API_URL: "https://lorflam.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Moretti design": {
|
||||||
|
CONF_CUSTOMER_CODE: "624813",
|
||||||
|
CONF_API_URL: "https://moretti.agua-iot.com",
|
||||||
|
},
|
||||||
|
"My Corisit": {
|
||||||
|
CONF_CUSTOMER_CODE: "101427",
|
||||||
|
CONF_API_URL: "https://mycorisit.agua-iot.com",
|
||||||
|
},
|
||||||
|
"MyPiazzetta": {
|
||||||
|
CONF_CUSTOMER_CODE: "458632",
|
||||||
|
CONF_API_URL: "https://piazzetta.agua-iot.com",
|
||||||
|
CONF_LOGIN_API_URL: "https://piazzetta-iot.app2cloud.it/api/bridge/endpoint/",
|
||||||
|
},
|
||||||
|
"MySuperior": {
|
||||||
|
CONF_CUSTOMER_CODE: "458632",
|
||||||
|
CONF_API_URL: "https://piazzetta.agua-iot.com",
|
||||||
|
CONF_LOGIN_API_URL: "https://piazzetta-iot.app2cloud.it/api/bridge/endpoint/",
|
||||||
|
CONF_BRAND_ID: "2",
|
||||||
|
CONF_BRAND: "superior",
|
||||||
|
},
|
||||||
|
"Nina": {
|
||||||
|
CONF_CUSTOMER_CODE: "999999",
|
||||||
|
CONF_API_URL: "https://micronova.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Nobis-Fi": {
|
||||||
|
CONF_CUSTOMER_CODE: "700700",
|
||||||
|
CONF_API_URL: "https://nobis.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Nordic Fire 2.0": {
|
||||||
|
CONF_CUSTOMER_CODE: "132678",
|
||||||
|
CONF_API_URL: "https://nordicfire.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Ravelli Wi-Fi": {
|
||||||
|
CONF_CUSTOMER_CODE: "953712",
|
||||||
|
CONF_API_URL: "https://ravelli.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Stufe a pellet Italia": {
|
||||||
|
CONF_CUSTOMER_CODE: "015142",
|
||||||
|
CONF_API_URL: "https://stufepelletitalia.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Thermoflux": {
|
||||||
|
CONF_CUSTOMER_CODE: "391278",
|
||||||
|
CONF_API_URL: "https://thermoflux.agua-iot.com",
|
||||||
|
},
|
||||||
|
"Total Control 3.0 (Extraflame)": {
|
||||||
|
CONF_CUSTOMER_CODE: "195764",
|
||||||
|
CONF_API_URL: "https://extraflame.agua-iot.com/",
|
||||||
|
},
|
||||||
|
"TS Smart": {
|
||||||
|
CONF_CUSTOMER_CODE: "046629",
|
||||||
|
CONF_API_URL: "https://timsistem.agua-iot.com",
|
||||||
|
},
|
||||||
|
"TurboFonte": {
|
||||||
|
CONF_CUSTOMER_CODE: "354924",
|
||||||
|
CONF_API_URL: "https://remote.mcz.it",
|
||||||
|
CONF_BRAND_ID: "2",
|
||||||
|
CONF_BRAND: "turbofonte",
|
||||||
|
},
|
||||||
|
"Wi-Phire": {
|
||||||
|
CONF_CUSTOMER_CODE: "521228",
|
||||||
|
CONF_API_URL: "https://lineavz.agua-iot.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
BINARY_SENSORS = (
|
||||||
|
AguaIOTBinarySensorEntityDescription(
|
||||||
|
key="ris_pellet_ris_get",
|
||||||
|
name="Pellets Depleted",
|
||||||
|
icon="mdi:fire",
|
||||||
|
icon_on="mdi:fire-alert",
|
||||||
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||||
|
),
|
||||||
|
AguaIOTBinarySensorEntityDescription(
|
||||||
|
key="popup_riserva_wood_get",
|
||||||
|
name="Wood Reserve",
|
||||||
|
icon="mdi:fire",
|
||||||
|
icon_on="mdi:fire-alert",
|
||||||
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||||
|
force_enabled=True,
|
||||||
|
),
|
||||||
|
AguaIOTBinarySensorEntityDescription(
|
||||||
|
key="thermostat_contact_get",
|
||||||
|
name="External Thermostat",
|
||||||
|
icon="mdi:electric-switch",
|
||||||
|
icon_on="mdi:electric-switch-closed",
|
||||||
|
),
|
||||||
|
AguaIOTBinarySensorEntityDescription(
|
||||||
|
key="thermostat_contact_rear_get",
|
||||||
|
name="External Thermostat Rear",
|
||||||
|
icon="mdi:electric-switch",
|
||||||
|
icon_on="mdi:electric-switch-closed",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSORS = (
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="status_get",
|
||||||
|
name="Status",
|
||||||
|
icon="mdi:fire",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="alarms_get",
|
||||||
|
name="Alarm",
|
||||||
|
icon="mdi:alert-outline",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
force_enabled=True,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="real_power_get",
|
||||||
|
name="Real Power",
|
||||||
|
icon="mdi:gauge",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
force_enabled=True,
|
||||||
|
raw_value=True,
|
||||||
|
hybrid_exclude=True,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="real_power_get",
|
||||||
|
name="Real Pellet Power",
|
||||||
|
icon="mdi:gauge",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
force_enabled=True,
|
||||||
|
raw_value=True,
|
||||||
|
hybrid_only=True,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="real_power_wood_get",
|
||||||
|
name="Real Wood Power",
|
||||||
|
icon="mdi:gauge",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
force_enabled=True,
|
||||||
|
hybrid_only=True,
|
||||||
|
raw_value=True,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="vent_front2_get",
|
||||||
|
name="Real Vent Front",
|
||||||
|
icon="mdi:fan",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="vent_rear2_get",
|
||||||
|
name="Real Vent Rear",
|
||||||
|
icon="mdi:fan",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="type_combustible_get",
|
||||||
|
name="Fuel",
|
||||||
|
icon="mdi:gas-burner",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="pres_h2o_get",
|
||||||
|
name="Water Pressure",
|
||||||
|
native_unit_of_measurement=UnitOfPressure.BAR,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="pascal_get",
|
||||||
|
name="Brazier Pressure",
|
||||||
|
native_unit_of_measurement=UnitOfPressure.PA,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="giri_estrattore_get",
|
||||||
|
name="Extractor Fan",
|
||||||
|
icon="mdi:fan",
|
||||||
|
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=None,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="pomp_h2o_get",
|
||||||
|
name="Water Pump",
|
||||||
|
icon="mdi:pump",
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=None,
|
||||||
|
device_class=None,
|
||||||
|
),
|
||||||
|
# Temperature sensors
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_gas_flue_get",
|
||||||
|
name="Smoke Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_probe_k_get",
|
||||||
|
name="Flame Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_air_get",
|
||||||
|
name="Air Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_air2_get",
|
||||||
|
name="Air Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_air3_get",
|
||||||
|
name="Air Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_air_palm_get",
|
||||||
|
name="Remote Air Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_rear2_get",
|
||||||
|
name="Vent Rear Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_front2_get",
|
||||||
|
name="Vent Front Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_water_get",
|
||||||
|
name="Water Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_h2o_mandata_get",
|
||||||
|
name="Water Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_h2o_get",
|
||||||
|
name="Water Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="temp_legna_get",
|
||||||
|
name="Wood Temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTSensorEntityDescription(
|
||||||
|
key="pellet_level_get",
|
||||||
|
name="Pellet Level",
|
||||||
|
native_unit_of_measurement="%",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:percent",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SWITCHES = (
|
||||||
|
SwitchEntityDescription(
|
||||||
|
key="natural_mode_manual_set",
|
||||||
|
name="Natural Mode",
|
||||||
|
icon="mdi:fan-off",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
),
|
||||||
|
SwitchEntityDescription(
|
||||||
|
key="standby_set",
|
||||||
|
name="Standby",
|
||||||
|
icon="mdi:power-standby",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
),
|
||||||
|
SwitchEntityDescription(
|
||||||
|
key="fun_auto_set",
|
||||||
|
name="Auto Mode",
|
||||||
|
icon="mdi:fan-auto",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
),
|
||||||
|
SwitchEntityDescription(
|
||||||
|
key="fun_pwf_set",
|
||||||
|
name="Powerful Mode",
|
||||||
|
icon="mdi:speedometer",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
),
|
||||||
|
SwitchEntityDescription(
|
||||||
|
key="eco_stop_set",
|
||||||
|
name="ECO Stop",
|
||||||
|
icon="mdi:leaf-off",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
NUMBERS = (
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="es_air_start_set",
|
||||||
|
name="Energy Saving Start",
|
||||||
|
native_step=1,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="es_air_stop_set",
|
||||||
|
name="Energy Saving Stop",
|
||||||
|
native_step=1,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
|
),
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="power_set",
|
||||||
|
name="Power",
|
||||||
|
icon="mdi:fire",
|
||||||
|
native_step=1,
|
||||||
|
force_enabled=True,
|
||||||
|
hybrid_exclude=True,
|
||||||
|
),
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="power_set",
|
||||||
|
name="Pellet Power",
|
||||||
|
icon="mdi:fire",
|
||||||
|
native_step=1,
|
||||||
|
force_enabled=True,
|
||||||
|
hybrid_only=True,
|
||||||
|
),
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="power_wood_set",
|
||||||
|
name="Wood Power",
|
||||||
|
icon="mdi:fire",
|
||||||
|
native_step=1,
|
||||||
|
hybrid_only=True,
|
||||||
|
),
|
||||||
|
AguaIOTNumberEntityDescription(
|
||||||
|
key="eco_temp_stop",
|
||||||
|
name="ECO Stop Time",
|
||||||
|
native_step=1,
|
||||||
|
native_unit_of_measurement="min",
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=30,
|
||||||
|
force_enabled=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CLIMATE_CANALIZATIONS = (
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Multifire {id}",
|
||||||
|
key=r"multifire_(?P<id>\d+)_set",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Canalization {id}",
|
||||||
|
key=r"canalization_(?P<id>[a-zA-Z0-9]+)_set",
|
||||||
|
key_enable="canalization_{id}_enable",
|
||||||
|
key2_enable="canalization_2{id}_enable",
|
||||||
|
key_temp_set="canalization_{id}_temp_air_set",
|
||||||
|
key_temp_get="canalization_{id}_temp_air_get",
|
||||||
|
key_temp2_get="canalization_2{id}_temp_air_get",
|
||||||
|
key_vent_set="canalization_{id}_vent_set",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Canalization Single",
|
||||||
|
key=r"canalization_single_vent_set",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Vent {id}",
|
||||||
|
key=r"vent_(?P<id>(front|rear))_set",
|
||||||
|
key_enable="vent_{id}2_enable",
|
||||||
|
key2_enable="vent_{id}2_enable",
|
||||||
|
key_temp_set="temp_{id}_set",
|
||||||
|
key_temp_get="temp_{id}_get",
|
||||||
|
key_temp2_get="temp_{id}2_get",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Vent {id}",
|
||||||
|
key=r"vent_(?!(front|rear))(?P<id>\w+)_set",
|
||||||
|
key_temp_set="temp_{id}_set",
|
||||||
|
key_temp_get="temp_{id}_get",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
AguaIOTCanalizationEntityDescription(
|
||||||
|
name="Multifire",
|
||||||
|
key="vent_front_sweetair_set",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECTS = (
|
||||||
|
SelectEntityDescription(
|
||||||
|
key="fan_mode_set",
|
||||||
|
name="Fan Mode",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
SelectEntityDescription(
|
||||||
|
key="fan2_mode_set",
|
||||||
|
name="Fan Mode",
|
||||||
|
icon="mdi:fan",
|
||||||
|
),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user