Upload files to "custom_components/aguaiot"

This commit is contained in:
2026-01-08 13:26:40 +00:00
parent a52cf90122
commit 64dbb8d4ad
5 changed files with 2334 additions and 0 deletions

View 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)

View 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))

View 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
)

View 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))

View 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",
),
)