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 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.helpers.entity import DeviceInfo
@ -22,6 +28,36 @@ class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator], ABC):
"""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):
"""Define an Airzone Cloud Zone entity."""
@ -49,6 +85,5 @@ class AirzoneZoneEntity(AirzoneEntity):
"""Return zone value by key."""
value = None
if zone := self.coordinator.data[AZD_ZONES].get(self.zone_id):
if key in zone:
value = zone[key]
value = zone.get(key)
return value

View file

@ -3,7 +3,13 @@ from __future__ import annotations
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 (
SensorDeviceClass,
@ -18,7 +24,17 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
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, ...]] = (
SensorEntityDescription(
@ -42,9 +58,25 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""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 description in ZONE_SENSOR_TYPES:
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)
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):
"""Define an Airzone Cloud Zone sensor."""

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ from typing import Any
from unittest.mock import patch
from aioairzone_cloud.const import (
API_AZ_AIDOO,
API_AZ_SYSTEM,
API_AZ_ZONE,
API_CELSIUS,
@ -38,6 +39,7 @@ from aioairzone_cloud.const import (
API_ZONE_NUMBER,
)
from aioairzone_cloud.device import Device
from aioairzone_cloud.webserver import WebServer
from homeassistant.components.airzone_cloud import DOMAIN
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
@ -46,6 +48,7 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
WS_ID = "11:22:33:44:55:66"
WS_ID_AIDOO = "11:22:33:44:55:67"
CONFIG = {
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_WS_IDS: [
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]:
"""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":
return {
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(
hass: HomeAssistant,
) -> None:
@ -174,7 +225,7 @@ async def async_init_integration(
return_value=GET_INSTALLATIONS_MOCK,
), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver",
return_value=GET_WEBSERVER_MOCK,
side_effect=mock_get_webserver,
), patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.login",
return_value=None,