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