Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Aidoo sensors to Airzone Cloud #93541

Merged
merged 6 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 38 additions & 3 deletions homeassistant/components/airzone_cloud/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
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
Expand All @@ -22,6 +28,36 @@ def get_airzone_value(self, key: str) -> Any:
"""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."""

Expand Down Expand Up @@ -49,6 +85,5 @@ def get_airzone_value(self, key: str) -> Any:
"""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
61 changes: 57 additions & 4 deletions homeassistant/components/airzone_cloud/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

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,
Expand All @@ -18,7 +24,17 @@

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(
Expand All @@ -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: 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,
)
)

sensors = []
# 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:
Expand Down Expand Up @@ -76,6 +108,27 @@ def _async_update_attrs(self) -> None:
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."""

Expand Down
6 changes: 3 additions & 3 deletions tests/components/airzone_cloud/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
CONFIG,
GET_INSTALLATION_MOCK,
GET_INSTALLATIONS_MOCK,
GET_WEBSERVER_MOCK,
WS_ID,
mock_get_device_status,
mock_get_webserver,
)


Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions tests/components/airzone_cloud/test_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
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
Expand All @@ -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,
Expand All @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions tests/components/airzone_cloud/test_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
API_DEVICES,
API_GROUPS,
API_WS_ID,
AZD_AIDOOS,
AZD_INSTALLATIONS,
AZD_SYSTEMS,
AZD_WEBSERVERS,
Expand Down Expand Up @@ -109,6 +110,7 @@ async def test_config_entry_diagnostics(
)

assert list(diag["coord_data"]) >= [
AZD_AIDOOS,
AZD_INSTALLATIONS,
AZD_SYSTEMS,
AZD_WEBSERVERS,
Expand Down
4 changes: 4 additions & 0 deletions tests/components/airzone_cloud/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
53 changes: 52 additions & 1 deletion tests/components/airzone_cloud/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from unittest.mock import patch

from aioairzone_cloud.const import (
API_AZ_AIDOO,
API_AZ_SYSTEM,
API_AZ_ZONE,
API_CELSIUS,
Expand Down Expand Up @@ -38,6 +39,7 @@
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
Expand All @@ -46,6 +48,7 @@
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",
Expand Down Expand Up @@ -88,6 +91,17 @@
},
],
},
{
API_NAME: "Aidoo Group",
API_DEVICES: [
{
API_DEVICE_ID: "aidoo1",
API_NAME: "Bron",
API_TYPE: API_AZ_AIDOO,
API_WS_ID: WS_ID_AIDOO,
},
],
},
],
}

Expand All @@ -98,6 +112,7 @@
API_NAME: "House",
API_WS_IDS: [
WS_ID,
WS_ID_AIDOO,
],
},
],
Expand All @@ -120,10 +135,37 @@
},
}

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: [],
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down