Add Aidoo sensors to Airzone Cloud (#93541)

This commit is contained in:
Álvaro Fernández Rojas 2023-05-29 21:58:53 +02:00 committed by GitHub
parent 8b662dc94f
commit 6aa01e1441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 14 deletions

View File

@ -4,7 +4,13 @@ from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any from typing import Any
from aioairzone_cloud.const import AZD_NAME, AZD_SYSTEM_ID, AZD_ZONES from aioairzone_cloud.const import (
AZD_AIDOOS,
AZD_NAME,
AZD_SYSTEM_ID,
AZD_WEBSERVER,
AZD_ZONES,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
@ -22,6 +28,36 @@ class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator], ABC):
"""Return Airzone Cloud entity value by key.""" """Return Airzone Cloud entity value by key."""
class AirzoneAidooEntity(AirzoneEntity):
"""Define an Airzone Cloud Aidoo entity."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
entry: ConfigEntry,
aidoo_id: str,
aidoo_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.aidoo_id = aidoo_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{entry.unique_id}_{aidoo_id}")},
manufacturer=MANUFACTURER,
name=aidoo_data[AZD_NAME],
via_device=(DOMAIN, f"{entry.unique_id}_{aidoo_data[AZD_WEBSERVER]}"),
)
def get_airzone_value(self, key: str) -> Any:
"""Return Aidoo value by key."""
value = None
if aidoo := self.coordinator.data[AZD_AIDOOS].get(self.aidoo_id):
value = aidoo.get(key)
return value
class AirzoneZoneEntity(AirzoneEntity): class AirzoneZoneEntity(AirzoneEntity):
"""Define an Airzone Cloud Zone entity.""" """Define an Airzone Cloud Zone entity."""
@ -49,6 +85,5 @@ class AirzoneZoneEntity(AirzoneEntity):
"""Return zone value by key.""" """Return zone value by key."""
value = None value = None
if zone := self.coordinator.data[AZD_ZONES].get(self.zone_id): if zone := self.coordinator.data[AZD_ZONES].get(self.zone_id):
if key in zone: value = zone.get(key)
value = zone[key]
return value return value

View File

@ -3,7 +3,13 @@ from __future__ import annotations
from typing import Any, Final from typing import Any, Final
from aioairzone_cloud.const import AZD_HUMIDITY, AZD_NAME, AZD_TEMP, AZD_ZONES from aioairzone_cloud.const import (
AZD_AIDOOS,
AZD_HUMIDITY,
AZD_NAME,
AZD_TEMP,
AZD_ZONES,
)
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -18,7 +24,17 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity from .entity import AirzoneAidooEntity, AirzoneEntity, AirzoneZoneEntity
AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key=AZD_TEMP,
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
)
ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = ( ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription( SensorEntityDescription(
@ -42,9 +58,25 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add Airzone Cloud sensors from a config_entry.""" """Add Airzone Cloud sensors from a config_entry."""
coordinator = hass.data[DOMAIN][entry.entry_id] coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
sensors = [] sensors: list[AirzoneSensor] = []
# Aidoos
for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items():
for description in AIDOO_SENSOR_TYPES:
if description.key in aidoo_data:
sensors.append(
AirzoneAidooSensor(
coordinator,
description,
entry,
aidoo_id,
aidoo_data,
)
)
# Zones
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
for description in ZONE_SENSOR_TYPES: for description in ZONE_SENSOR_TYPES:
if description.key in zone_data: if description.key in zone_data:
@ -76,6 +108,27 @@ class AirzoneSensor(AirzoneEntity, SensorEntity):
self._attr_native_value = self.get_airzone_value(self.entity_description.key) self._attr_native_value = self.get_airzone_value(self.entity_description.key)
class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
"""Define an Airzone Cloud Aidoo sensor."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: SensorEntityDescription,
entry: ConfigEntry,
aidoo_id: str,
aidoo_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, entry, aidoo_id, aidoo_data)
self._attr_name = f"{aidoo_data[AZD_NAME]} {description.name}"
self._attr_unique_id = f"{entry.unique_id}_{aidoo_id}_{description.key}"
self.entity_description = description
self._async_update_attrs()
class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor): class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
"""Define an Airzone Cloud Zone sensor.""" """Define an Airzone Cloud Zone sensor."""

View File

@ -14,9 +14,9 @@ from .util import (
CONFIG, CONFIG,
GET_INSTALLATION_MOCK, GET_INSTALLATION_MOCK,
GET_INSTALLATIONS_MOCK, GET_INSTALLATIONS_MOCK,
GET_WEBSERVER_MOCK,
WS_ID, WS_ID,
mock_get_device_status, mock_get_device_status,
mock_get_webserver,
) )
@ -37,7 +37,7 @@ async def test_form(hass: HomeAssistant) -> None:
return_value=GET_INSTALLATIONS_MOCK, return_value=GET_INSTALLATIONS_MOCK,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver",
return_value=GET_WEBSERVER_MOCK, side_effect=mock_get_webserver,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.login", "homeassistant.components.airzone_cloud.AirzoneCloudApi.login",
return_value=None, return_value=None,
@ -98,7 +98,7 @@ async def test_installations_list_error(hass: HomeAssistant) -> None:
side_effect=AirzoneCloudError, side_effect=AirzoneCloudError,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver",
return_value=GET_WEBSERVER_MOCK, side_effect=mock_get_webserver,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.login", "homeassistant.components.airzone_cloud.AirzoneCloudApi.login",
return_value=None, return_value=None,

View File

@ -14,8 +14,8 @@ from .util import (
CONFIG, CONFIG,
GET_INSTALLATION_MOCK, GET_INSTALLATION_MOCK,
GET_INSTALLATIONS_MOCK, GET_INSTALLATIONS_MOCK,
GET_WEBSERVER_MOCK,
mock_get_device_status, mock_get_device_status,
mock_get_webserver,
) )
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed
@ -42,7 +42,7 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
return_value=GET_INSTALLATIONS_MOCK, return_value=GET_INSTALLATIONS_MOCK,
) as mock_installations, patch( ) as mock_installations, patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver",
return_value=GET_WEBSERVER_MOCK, side_effect=mock_get_webserver,
) as mock_webserver, patch( ) as mock_webserver, patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.login", "homeassistant.components.airzone_cloud.AirzoneCloudApi.login",
return_value=None, return_value=None,
@ -53,7 +53,7 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
mock_device_status.assert_called() mock_device_status.assert_called()
mock_installation.assert_awaited_once() mock_installation.assert_awaited_once()
mock_installations.assert_called_once() mock_installations.assert_called_once()
mock_webserver.assert_called_once() mock_webserver.assert_called()
mock_device_status.reset_mock() mock_device_status.reset_mock()
mock_installation.reset_mock() mock_installation.reset_mock()

View File

@ -7,6 +7,7 @@ from aioairzone_cloud.const import (
API_DEVICES, API_DEVICES,
API_GROUPS, API_GROUPS,
API_WS_ID, API_WS_ID,
AZD_AIDOOS,
AZD_INSTALLATIONS, AZD_INSTALLATIONS,
AZD_SYSTEMS, AZD_SYSTEMS,
AZD_WEBSERVERS, AZD_WEBSERVERS,
@ -109,6 +110,7 @@ async def test_config_entry_diagnostics(
) )
assert list(diag["coord_data"]) >= [ assert list(diag["coord_data"]) >= [
AZD_AIDOOS,
AZD_INSTALLATIONS, AZD_INSTALLATIONS,
AZD_SYSTEMS, AZD_SYSTEMS,
AZD_WEBSERVERS, AZD_WEBSERVERS,

View File

@ -12,6 +12,10 @@ async def test_airzone_create_sensors(
await async_init_integration(hass) await async_init_integration(hass)
# Aidoos
state = hass.states.get("sensor.bron_temperature")
assert state.state == "21.0"
# Zones # Zones
state = hass.states.get("sensor.dormitorio_temperature") state = hass.states.get("sensor.dormitorio_temperature")
assert state.state == "25.0" assert state.state == "25.0"

View File

@ -4,6 +4,7 @@ from typing import Any
from unittest.mock import patch from unittest.mock import patch
from aioairzone_cloud.const import ( from aioairzone_cloud.const import (
API_AZ_AIDOO,
API_AZ_SYSTEM, API_AZ_SYSTEM,
API_AZ_ZONE, API_AZ_ZONE,
API_CELSIUS, API_CELSIUS,
@ -38,6 +39,7 @@ from aioairzone_cloud.const import (
API_ZONE_NUMBER, API_ZONE_NUMBER,
) )
from aioairzone_cloud.device import Device from aioairzone_cloud.device import Device
from aioairzone_cloud.webserver import WebServer
from homeassistant.components.airzone_cloud import DOMAIN from homeassistant.components.airzone_cloud import DOMAIN
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
@ -46,6 +48,7 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
WS_ID = "11:22:33:44:55:66" WS_ID = "11:22:33:44:55:66"
WS_ID_AIDOO = "11:22:33:44:55:67"
CONFIG = { CONFIG = {
CONF_ID: "inst1", CONF_ID: "inst1",
@ -88,6 +91,17 @@ GET_INSTALLATION_MOCK = {
}, },
], ],
}, },
{
API_NAME: "Aidoo Group",
API_DEVICES: [
{
API_DEVICE_ID: "aidoo1",
API_NAME: "Bron",
API_TYPE: API_AZ_AIDOO,
API_WS_ID: WS_ID_AIDOO,
},
],
},
], ],
} }
@ -98,6 +112,7 @@ GET_INSTALLATIONS_MOCK = {
API_NAME: "House", API_NAME: "House",
API_WS_IDS: [ API_WS_IDS: [
WS_ID, WS_ID,
WS_ID_AIDOO,
], ],
}, },
], ],
@ -120,10 +135,37 @@ GET_WEBSERVER_MOCK = {
}, },
} }
GET_WEBSERVER_MOCK_AIDOO = {
API_WS_TYPE: "ws_aidoo",
API_CONFIG: {
API_WS_FW: "3.13",
API_STAT_SSID: "Wifi",
API_STAT_CHANNEL: 1,
API_STAT_AP_MAC: "00:00:00:00:00:01",
},
API_STATUS: {
API_IS_CONNECTED: True,
API_STAT_QUALITY: 4,
API_STAT_RSSI: -77,
API_CONNECTION_DATE: "2023-05-24 17:00:52 +0200",
API_DISCONNECTION_DATE: "2023-05-24 17:00:25 +0200",
},
}
def mock_get_device_status(device: Device) -> dict[str, Any]: def mock_get_device_status(device: Device) -> dict[str, Any]:
"""Mock API device status.""" """Mock API device status."""
if device.get_id() == "aidoo1":
return {
API_ERRORS: [],
API_IS_CONNECTED: True,
API_LOCAL_TEMP: {
API_CELSIUS: 21,
API_FAH: 70,
},
API_WARNINGS: [],
}
if device.get_id() == "system1": if device.get_id() == "system1":
return { return {
API_ERRORS: [], API_ERRORS: [],
@ -151,6 +193,15 @@ def mock_get_device_status(device: Device) -> dict[str, Any]:
} }
def mock_get_webserver(webserver: WebServer, devices: bool) -> dict[str, Any]:
"""Mock API get webserver."""
if webserver.get_id() == WS_ID_AIDOO:
return GET_WEBSERVER_MOCK_AIDOO
return GET_WEBSERVER_MOCK
async def async_init_integration( async def async_init_integration(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
@ -174,7 +225,7 @@ async def async_init_integration(
return_value=GET_INSTALLATIONS_MOCK, return_value=GET_INSTALLATIONS_MOCK,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver",
return_value=GET_WEBSERVER_MOCK, side_effect=mock_get_webserver,
), patch( ), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.login", "homeassistant.components.airzone_cloud.AirzoneCloudApi.login",
return_value=None, return_value=None,