Bump py-aosmith to 1.0.6 (#107409)
parent
4ea8c174f5
commit
3f2170bd06
|
@ -37,16 +37,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
await status_coordinator.async_config_entry_first_refresh()
|
await status_coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
for junction_id, status_data in status_coordinator.data.items():
|
for junction_id, aosmith_device in status_coordinator.data.items():
|
||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=entry.entry_id,
|
config_entry_id=entry.entry_id,
|
||||||
identifiers={(DOMAIN, junction_id)},
|
identifiers={(DOMAIN, junction_id)},
|
||||||
manufacturer="A. O. Smith",
|
manufacturer="A. O. Smith",
|
||||||
name=status_data.get("name"),
|
name=aosmith_device.name,
|
||||||
model=status_data.get("model"),
|
model=aosmith_device.model,
|
||||||
serial_number=status_data.get("serial"),
|
serial_number=aosmith_device.serial,
|
||||||
suggested_area=status_data.get("install", {}).get("location"),
|
suggested_area=aosmith_device.install_location,
|
||||||
sw_version=status_data.get("data", {}).get("firmwareVersion"),
|
sw_version=aosmith_device.status.firmware_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
energy_coordinator = AOSmithEnergyCoordinator(
|
energy_coordinator = AOSmithEnergyCoordinator(
|
||||||
|
|
|
@ -4,11 +4,6 @@ from datetime import timedelta
|
||||||
|
|
||||||
DOMAIN = "aosmith"
|
DOMAIN = "aosmith"
|
||||||
|
|
||||||
AOSMITH_MODE_ELECTRIC = "ELECTRIC"
|
|
||||||
AOSMITH_MODE_HEAT_PUMP = "HEAT_PUMP"
|
|
||||||
AOSMITH_MODE_HYBRID = "HYBRID"
|
|
||||||
AOSMITH_MODE_VACATION = "VACATION"
|
|
||||||
|
|
||||||
# Update interval to be used for normal background updates.
|
# Update interval to be used for normal background updates.
|
||||||
REGULAR_INTERVAL = timedelta(seconds=30)
|
REGULAR_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
|
@ -17,9 +12,3 @@ FAST_INTERVAL = timedelta(seconds=1)
|
||||||
|
|
||||||
# Update interval to be used for energy usage data.
|
# Update interval to be used for energy usage data.
|
||||||
ENERGY_USAGE_INTERVAL = timedelta(minutes=10)
|
ENERGY_USAGE_INTERVAL = timedelta(minutes=10)
|
||||||
|
|
||||||
HOT_WATER_STATUS_MAP = {
|
|
||||||
"LOW": "low",
|
|
||||||
"MEDIUM": "medium",
|
|
||||||
"HIGH": "high",
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
"""The data update coordinator for the A. O. Smith integration."""
|
"""The data update coordinator for the A. O. Smith integration."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from py_aosmith import (
|
from py_aosmith import (
|
||||||
AOSmithAPIClient,
|
AOSmithAPIClient,
|
||||||
AOSmithInvalidCredentialsException,
|
AOSmithInvalidCredentialsException,
|
||||||
AOSmithUnknownException,
|
AOSmithUnknownException,
|
||||||
)
|
)
|
||||||
|
from py_aosmith.models import Device as AOSmithDevice
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
@ -17,7 +17,7 @@ from .const import DOMAIN, ENERGY_USAGE_INTERVAL, FAST_INTERVAL, REGULAR_INTERVA
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AOSmithStatusCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
class AOSmithStatusCoordinator(DataUpdateCoordinator[dict[str, AOSmithDevice]]):
|
||||||
"""Coordinator for device status, updating with a frequent interval."""
|
"""Coordinator for device status, updating with a frequent interval."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: AOSmithAPIClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: AOSmithAPIClient) -> None:
|
||||||
|
@ -25,7 +25,7 @@ class AOSmithStatusCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]])
|
||||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=REGULAR_INTERVAL)
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=REGULAR_INTERVAL)
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
|
async def _async_update_data(self) -> dict[str, AOSmithDevice]:
|
||||||
"""Fetch latest data from the device status endpoint."""
|
"""Fetch latest data from the device status endpoint."""
|
||||||
try:
|
try:
|
||||||
devices = await self.client.get_devices()
|
devices = await self.client.get_devices()
|
||||||
|
@ -34,12 +34,9 @@ class AOSmithStatusCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]])
|
||||||
except AOSmithUnknownException as err:
|
except AOSmithUnknownException as err:
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
|
||||||
mode_pending = any(
|
mode_pending = any(device.status.mode_change_pending for device in devices)
|
||||||
device.get("data", {}).get("modePending") for device in devices
|
|
||||||
)
|
|
||||||
setpoint_pending = any(
|
setpoint_pending = any(
|
||||||
device.get("data", {}).get("temperatureSetpointPending")
|
device.status.temperature_setpoint_pending for device in devices
|
||||||
for device in devices
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if mode_pending or setpoint_pending:
|
if mode_pending or setpoint_pending:
|
||||||
|
@ -47,7 +44,7 @@ class AOSmithStatusCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]])
|
||||||
else:
|
else:
|
||||||
self.update_interval = REGULAR_INTERVAL
|
self.update_interval = REGULAR_INTERVAL
|
||||||
|
|
||||||
return {device.get("junctionId"): device for device in devices}
|
return {device.junction_id: device for device in devices}
|
||||||
|
|
||||||
|
|
||||||
class AOSmithEnergyCoordinator(DataUpdateCoordinator[dict[str, float]]):
|
class AOSmithEnergyCoordinator(DataUpdateCoordinator[dict[str, float]]):
|
||||||
|
@ -78,6 +75,6 @@ class AOSmithEnergyCoordinator(DataUpdateCoordinator[dict[str, float]]):
|
||||||
except AOSmithUnknownException as err:
|
except AOSmithUnknownException as err:
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
|
||||||
energy_usage_by_junction_id[junction_id] = energy_usage.get("lifetimeKwh")
|
energy_usage_by_junction_id[junction_id] = energy_usage.lifetime_kwh
|
||||||
|
|
||||||
return energy_usage_by_junction_id
|
return energy_usage_by_junction_id
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
from py_aosmith import AOSmithAPIClient
|
from py_aosmith import AOSmithAPIClient
|
||||||
|
from py_aosmith.models import Device as AOSmithDevice
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
@ -37,26 +38,20 @@ class AOSmithStatusEntity(AOSmithEntity[AOSmithStatusCoordinator]):
|
||||||
"""Base entity for entities that use data from the status coordinator."""
|
"""Base entity for entities that use data from the status coordinator."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device(self):
|
def device(self) -> AOSmithDevice:
|
||||||
"""Shortcut to get the device status from the coordinator data."""
|
"""Shortcut to get the device from the coordinator data."""
|
||||||
return self.coordinator.data.get(self.junction_id)
|
return self.coordinator.data[self.junction_id]
|
||||||
|
|
||||||
@property
|
|
||||||
def device_data(self):
|
|
||||||
"""Shortcut to get the device data within the device status."""
|
|
||||||
device = self.device
|
|
||||||
return None if device is None else device.get("data", {})
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return super().available and self.device_data.get("isOnline") is True
|
return super().available and self.device.status.is_online
|
||||||
|
|
||||||
|
|
||||||
class AOSmithEnergyEntity(AOSmithEntity[AOSmithEnergyCoordinator]):
|
class AOSmithEnergyEntity(AOSmithEntity[AOSmithEnergyCoordinator]):
|
||||||
"""Base entity for entities that use data from the energy coordinator."""
|
"""Base entity for entities that use data from the energy coordinator."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def energy_usage(self) -> float | None:
|
def energy_usage(self) -> float:
|
||||||
"""Shortcut to get the energy usage from the coordinator data."""
|
"""Shortcut to get the energy usage from the coordinator data."""
|
||||||
return self.coordinator.data.get(self.junction_id)
|
return self.coordinator.data[self.junction_id]
|
||||||
|
|
|
@ -5,5 +5,5 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aosmith",
|
"documentation": "https://www.home-assistant.io/integrations/aosmith",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["py-aosmith==1.0.4"]
|
"requirements": ["py-aosmith==1.0.6"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
|
||||||
|
from py_aosmith.models import Device as AOSmithDevice, HotWaterStatus
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
|
@ -16,7 +17,7 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AOSmithData
|
from . import AOSmithData
|
||||||
from .const import DOMAIN, HOT_WATER_STATUS_MAP
|
from .const import DOMAIN
|
||||||
from .coordinator import AOSmithEnergyCoordinator, AOSmithStatusCoordinator
|
from .coordinator import AOSmithEnergyCoordinator, AOSmithStatusCoordinator
|
||||||
from .entity import AOSmithEnergyEntity, AOSmithStatusEntity
|
from .entity import AOSmithEnergyEntity, AOSmithStatusEntity
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ from .entity import AOSmithEnergyEntity, AOSmithStatusEntity
|
||||||
class AOSmithStatusSensorEntityDescription(SensorEntityDescription):
|
class AOSmithStatusSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Entity description class for sensors using data from the status coordinator."""
|
"""Entity description class for sensors using data from the status coordinator."""
|
||||||
|
|
||||||
value_fn: Callable[[dict[str, Any]], str | int | None]
|
value_fn: Callable[[AOSmithDevice], str | int | None]
|
||||||
|
|
||||||
|
|
||||||
STATUS_ENTITY_DESCRIPTIONS: tuple[AOSmithStatusSensorEntityDescription, ...] = (
|
STATUS_ENTITY_DESCRIPTIONS: tuple[AOSmithStatusSensorEntityDescription, ...] = (
|
||||||
|
@ -36,11 +37,17 @@ STATUS_ENTITY_DESCRIPTIONS: tuple[AOSmithStatusSensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
options=["low", "medium", "high"],
|
options=["low", "medium", "high"],
|
||||||
value_fn=lambda device: HOT_WATER_STATUS_MAP.get(
|
value_fn=lambda device: HOT_WATER_STATUS_MAP.get(
|
||||||
device.get("data", {}).get("hotWaterStatus")
|
device.status.hot_water_status
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HOT_WATER_STATUS_MAP: dict[HotWaterStatus, str] = {
|
||||||
|
HotWaterStatus.LOW: "low",
|
||||||
|
HotWaterStatus.MEDIUM: "medium",
|
||||||
|
HotWaterStatus.HIGH: "high",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from py_aosmith.models import OperationMode as AOSmithOperationMode
|
||||||
|
|
||||||
from homeassistant.components.water_heater import (
|
from homeassistant.components.water_heater import (
|
||||||
STATE_ECO,
|
STATE_ECO,
|
||||||
STATE_ELECTRIC,
|
STATE_ELECTRIC,
|
||||||
|
@ -16,31 +18,25 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AOSmithData
|
from . import AOSmithData
|
||||||
from .const import (
|
from .const import DOMAIN
|
||||||
AOSMITH_MODE_ELECTRIC,
|
|
||||||
AOSMITH_MODE_HEAT_PUMP,
|
|
||||||
AOSMITH_MODE_HYBRID,
|
|
||||||
AOSMITH_MODE_VACATION,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
from .coordinator import AOSmithStatusCoordinator
|
from .coordinator import AOSmithStatusCoordinator
|
||||||
from .entity import AOSmithStatusEntity
|
from .entity import AOSmithStatusEntity
|
||||||
|
|
||||||
MODE_HA_TO_AOSMITH = {
|
MODE_HA_TO_AOSMITH = {
|
||||||
STATE_OFF: AOSMITH_MODE_VACATION,
|
STATE_ECO: AOSmithOperationMode.HYBRID,
|
||||||
STATE_ECO: AOSMITH_MODE_HYBRID,
|
STATE_ELECTRIC: AOSmithOperationMode.ELECTRIC,
|
||||||
STATE_ELECTRIC: AOSMITH_MODE_ELECTRIC,
|
STATE_HEAT_PUMP: AOSmithOperationMode.HEAT_PUMP,
|
||||||
STATE_HEAT_PUMP: AOSMITH_MODE_HEAT_PUMP,
|
STATE_OFF: AOSmithOperationMode.VACATION,
|
||||||
}
|
}
|
||||||
MODE_AOSMITH_TO_HA = {
|
MODE_AOSMITH_TO_HA = {
|
||||||
AOSMITH_MODE_ELECTRIC: STATE_ELECTRIC,
|
AOSmithOperationMode.ELECTRIC: STATE_ELECTRIC,
|
||||||
AOSMITH_MODE_HEAT_PUMP: STATE_HEAT_PUMP,
|
AOSmithOperationMode.HEAT_PUMP: STATE_HEAT_PUMP,
|
||||||
AOSMITH_MODE_HYBRID: STATE_ECO,
|
AOSmithOperationMode.HYBRID: STATE_ECO,
|
||||||
AOSMITH_MODE_VACATION: STATE_OFF,
|
AOSmithOperationMode.VACATION: STATE_OFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Operation mode to use when exiting away mode
|
# Operation mode to use when exiting away mode
|
||||||
DEFAULT_OPERATION_MODE = AOSMITH_MODE_HYBRID
|
DEFAULT_OPERATION_MODE = AOSmithOperationMode.HYBRID
|
||||||
|
|
||||||
DEFAULT_SUPPORT_FLAGS = (
|
DEFAULT_SUPPORT_FLAGS = (
|
||||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||||
|
@ -79,23 +75,22 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
|
||||||
@property
|
@property
|
||||||
def operation_list(self) -> list[str]:
|
def operation_list(self) -> list[str]:
|
||||||
"""Return the list of supported operation modes."""
|
"""Return the list of supported operation modes."""
|
||||||
op_modes = []
|
ha_modes = []
|
||||||
for mode_dict in self.device_data.get("modes", []):
|
for supported_mode in self.device.supported_modes:
|
||||||
mode_name = mode_dict.get("mode")
|
ha_mode = MODE_AOSMITH_TO_HA.get(supported_mode.mode)
|
||||||
ha_mode = MODE_AOSMITH_TO_HA.get(mode_name)
|
|
||||||
|
|
||||||
# Filtering out STATE_OFF since it is handled by away mode
|
# Filtering out STATE_OFF since it is handled by away mode
|
||||||
if ha_mode is not None and ha_mode != STATE_OFF:
|
if ha_mode is not None and ha_mode != STATE_OFF:
|
||||||
op_modes.append(ha_mode)
|
ha_modes.append(ha_mode)
|
||||||
|
|
||||||
return op_modes
|
return ha_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> WaterHeaterEntityFeature:
|
def supported_features(self) -> WaterHeaterEntityFeature:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
supports_vacation_mode = any(
|
supports_vacation_mode = any(
|
||||||
mode_dict.get("mode") == AOSMITH_MODE_VACATION
|
supported_mode.mode == AOSmithOperationMode.VACATION
|
||||||
for mode_dict in self.device_data.get("modes", [])
|
for supported_mode in self.device.supported_modes
|
||||||
)
|
)
|
||||||
|
|
||||||
if supports_vacation_mode:
|
if supports_vacation_mode:
|
||||||
|
@ -106,22 +101,22 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> float | None:
|
def target_temperature(self) -> float | None:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self.device_data.get("temperatureSetpoint")
|
return self.device.status.temperature_setpoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self) -> float:
|
def max_temp(self) -> float:
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
return self.device_data.get("temperatureSetpointMaximum")
|
return self.device.status.temperature_setpoint_maximum
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> str:
|
def current_operation(self) -> str:
|
||||||
"""Return the current operation mode."""
|
"""Return the current operation mode."""
|
||||||
return MODE_AOSMITH_TO_HA.get(self.device_data.get("mode"), STATE_OFF)
|
return MODE_AOSMITH_TO_HA.get(self.device.status.current_mode, STATE_OFF)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def is_away_mode_on(self):
|
||||||
"""Return True if away mode is on."""
|
"""Return True if away mode is on."""
|
||||||
return self.device_data.get("mode") == AOSMITH_MODE_VACATION
|
return self.device.status.current_mode == AOSmithOperationMode.VACATION
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||||
"""Set new target operation mode."""
|
"""Set new target operation mode."""
|
||||||
|
@ -129,18 +124,19 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
|
||||||
if aosmith_mode is not None:
|
if aosmith_mode is not None:
|
||||||
await self.client.update_mode(self.junction_id, aosmith_mode)
|
await self.client.update_mode(self.junction_id, aosmith_mode)
|
||||||
|
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get("temperature")
|
temperature = kwargs.get("temperature")
|
||||||
await self.client.update_setpoint(self.junction_id, temperature)
|
if temperature is not None:
|
||||||
|
await self.client.update_setpoint(self.junction_id, temperature)
|
||||||
|
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self) -> None:
|
async def async_turn_away_mode_on(self) -> None:
|
||||||
"""Turn away mode on."""
|
"""Turn away mode on."""
|
||||||
await self.client.update_mode(self.junction_id, AOSMITH_MODE_VACATION)
|
await self.client.update_mode(self.junction_id, AOSmithOperationMode.VACATION)
|
||||||
|
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
|
|
@ -1554,7 +1554,7 @@ pushover_complete==1.1.1
|
||||||
pvo==2.1.1
|
pvo==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.aosmith
|
# homeassistant.components.aosmith
|
||||||
py-aosmith==1.0.4
|
py-aosmith==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.canary
|
# homeassistant.components.canary
|
||||||
py-canary==0.5.3
|
py-canary==0.5.3
|
||||||
|
|
|
@ -1201,7 +1201,7 @@ pushover_complete==1.1.1
|
||||||
pvo==2.1.1
|
pvo==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.aosmith
|
# homeassistant.components.aosmith
|
||||||
py-aosmith==1.0.4
|
py-aosmith==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.canary
|
# homeassistant.components.canary
|
||||||
py-canary==0.5.3
|
py-canary==0.5.3
|
||||||
|
|
|
@ -3,6 +3,16 @@ from collections.abc import Generator
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from py_aosmith import AOSmithAPIClient
|
from py_aosmith import AOSmithAPIClient
|
||||||
|
from py_aosmith.models import (
|
||||||
|
Device,
|
||||||
|
DeviceStatus,
|
||||||
|
DeviceType,
|
||||||
|
EnergyUseData,
|
||||||
|
EnergyUseHistoryEntry,
|
||||||
|
HotWaterStatus,
|
||||||
|
OperationMode,
|
||||||
|
SupportedOperationModeInfo,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.aosmith.const import DOMAIN
|
from homeassistant.components.aosmith.const import DOMAIN
|
||||||
|
@ -10,11 +20,7 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||||
MockConfigEntry,
|
|
||||||
load_json_array_fixture,
|
|
||||||
load_json_object_fixture,
|
|
||||||
)
|
|
||||||
|
|
||||||
FIXTURE_USER_INPUT = {
|
FIXTURE_USER_INPUT = {
|
||||||
CONF_EMAIL: "testemail@example.com",
|
CONF_EMAIL: "testemail@example.com",
|
||||||
|
@ -22,6 +28,80 @@ FIXTURE_USER_INPUT = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_device_fixture(
|
||||||
|
mode_pending: bool, setpoint_pending: bool, has_vacation_mode: bool
|
||||||
|
):
|
||||||
|
"""Build a fixture for a device."""
|
||||||
|
supported_modes: list[SupportedOperationModeInfo] = [
|
||||||
|
SupportedOperationModeInfo(
|
||||||
|
mode=OperationMode.HYBRID,
|
||||||
|
original_name="HYBRID",
|
||||||
|
has_day_selection=False,
|
||||||
|
),
|
||||||
|
SupportedOperationModeInfo(
|
||||||
|
mode=OperationMode.HEAT_PUMP,
|
||||||
|
original_name="HEAT_PUMP",
|
||||||
|
has_day_selection=False,
|
||||||
|
),
|
||||||
|
SupportedOperationModeInfo(
|
||||||
|
mode=OperationMode.ELECTRIC,
|
||||||
|
original_name="ELECTRIC",
|
||||||
|
has_day_selection=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if has_vacation_mode:
|
||||||
|
supported_modes.append(
|
||||||
|
SupportedOperationModeInfo(
|
||||||
|
mode=OperationMode.VACATION,
|
||||||
|
original_name="VACATION",
|
||||||
|
has_day_selection=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Device(
|
||||||
|
brand="aosmith",
|
||||||
|
model="HPTS-50 200 202172000",
|
||||||
|
device_type=DeviceType.NEXT_GEN_HEAT_PUMP,
|
||||||
|
dsn="dsn",
|
||||||
|
junction_id="junctionId",
|
||||||
|
name="My water heater",
|
||||||
|
serial="serial",
|
||||||
|
install_location="Basement",
|
||||||
|
supported_modes=supported_modes,
|
||||||
|
status=DeviceStatus(
|
||||||
|
firmware_version="2.14",
|
||||||
|
is_online=True,
|
||||||
|
current_mode=OperationMode.HEAT_PUMP,
|
||||||
|
mode_change_pending=mode_pending,
|
||||||
|
temperature_setpoint=130,
|
||||||
|
temperature_setpoint_pending=setpoint_pending,
|
||||||
|
temperature_setpoint_previous=130,
|
||||||
|
temperature_setpoint_maximum=130,
|
||||||
|
hot_water_status=HotWaterStatus.LOW,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ENERGY_USE_FIXTURE = EnergyUseData(
|
||||||
|
lifetime_kwh=132.825,
|
||||||
|
history=[
|
||||||
|
EnergyUseHistoryEntry(
|
||||||
|
date="2023-10-30T04:00:00.000Z",
|
||||||
|
energy_use_kwh=2.01,
|
||||||
|
),
|
||||||
|
EnergyUseHistoryEntry(
|
||||||
|
date="2023-10-31T04:00:00.000Z",
|
||||||
|
energy_use_kwh=1.542,
|
||||||
|
),
|
||||||
|
EnergyUseHistoryEntry(
|
||||||
|
date="2023-11-01T04:00:00.000Z",
|
||||||
|
energy_use_kwh=1.908,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_config_entry() -> MockConfigEntry:
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
"""Return the default mocked config entry."""
|
"""Return the default mocked config entry."""
|
||||||
|
@ -42,25 +122,44 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def get_devices_fixture() -> str:
|
def get_devices_fixture_mode_pending() -> bool:
|
||||||
"""Return the name of the fixture to use for get_devices."""
|
"""Return whether to set mode_pending in the get_devices fixture."""
|
||||||
return "get_devices"
|
return False
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def mock_client(get_devices_fixture: str) -> Generator[MagicMock, None, None]:
|
def get_devices_fixture_setpoint_pending() -> bool:
|
||||||
|
"""Return whether to set setpoint_pending in the get_devices fixture."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def get_devices_fixture_has_vacation_mode() -> bool:
|
||||||
|
"""Return whether to include vacation mode in the get_devices fixture."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def mock_client(
|
||||||
|
get_devices_fixture_mode_pending: bool,
|
||||||
|
get_devices_fixture_setpoint_pending: bool,
|
||||||
|
get_devices_fixture_has_vacation_mode: bool,
|
||||||
|
) -> Generator[MagicMock, None, None]:
|
||||||
"""Return a mocked client."""
|
"""Return a mocked client."""
|
||||||
get_devices_fixture = load_json_array_fixture(f"{get_devices_fixture}.json", DOMAIN)
|
get_devices_fixture = [
|
||||||
get_energy_use_fixture = load_json_object_fixture(
|
build_device_fixture(
|
||||||
"get_energy_use_data.json", DOMAIN
|
get_devices_fixture_mode_pending,
|
||||||
)
|
get_devices_fixture_setpoint_pending,
|
||||||
|
get_devices_fixture_has_vacation_mode,
|
||||||
|
)
|
||||||
|
]
|
||||||
get_all_device_info_fixture = load_json_object_fixture(
|
get_all_device_info_fixture = load_json_object_fixture(
|
||||||
"get_all_device_info.json", DOMAIN
|
"get_all_device_info.json", DOMAIN
|
||||||
)
|
)
|
||||||
|
|
||||||
client_mock = MagicMock(AOSmithAPIClient)
|
client_mock = MagicMock(AOSmithAPIClient)
|
||||||
client_mock.get_devices = AsyncMock(return_value=get_devices_fixture)
|
client_mock.get_devices = AsyncMock(return_value=get_devices_fixture)
|
||||||
client_mock.get_energy_use_data = AsyncMock(return_value=get_energy_use_fixture)
|
client_mock.get_energy_use_data = AsyncMock(return_value=ENERGY_USE_FIXTURE)
|
||||||
client_mock.get_all_device_info = AsyncMock(
|
client_mock.get_all_device_info = AsyncMock(
|
||||||
return_value=get_all_device_info_fixture
|
return_value=get_all_device_info_fixture
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"brand": "aosmith",
|
|
||||||
"model": "HPTS-50 200 202172000",
|
|
||||||
"deviceType": "NEXT_GEN_HEAT_PUMP",
|
|
||||||
"dsn": "dsn",
|
|
||||||
"junctionId": "junctionId",
|
|
||||||
"name": "My water heater",
|
|
||||||
"serial": "serial",
|
|
||||||
"install": {
|
|
||||||
"location": "Basement"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"__typename": "NextGenHeatPump",
|
|
||||||
"temperatureSetpoint": 130,
|
|
||||||
"temperatureSetpointPending": false,
|
|
||||||
"temperatureSetpointPrevious": 130,
|
|
||||||
"temperatureSetpointMaximum": 130,
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"mode": "HYBRID",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "ELECTRIC",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "VACATION",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isOnline": true,
|
|
||||||
"firmwareVersion": "2.14",
|
|
||||||
"hotWaterStatus": "LOW",
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"modePending": false,
|
|
||||||
"vacationModeRemainingDays": 0,
|
|
||||||
"electricModeRemainingDays": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,46 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"brand": "aosmith",
|
|
||||||
"model": "HPTS-50 200 202172000",
|
|
||||||
"deviceType": "NEXT_GEN_HEAT_PUMP",
|
|
||||||
"dsn": "dsn",
|
|
||||||
"junctionId": "junctionId",
|
|
||||||
"name": "My water heater",
|
|
||||||
"serial": "serial",
|
|
||||||
"install": {
|
|
||||||
"location": "Basement"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"__typename": "NextGenHeatPump",
|
|
||||||
"temperatureSetpoint": 130,
|
|
||||||
"temperatureSetpointPending": false,
|
|
||||||
"temperatureSetpointPrevious": 130,
|
|
||||||
"temperatureSetpointMaximum": 130,
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"mode": "HYBRID",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "ELECTRIC",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "VACATION",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isOnline": true,
|
|
||||||
"firmwareVersion": "2.14",
|
|
||||||
"hotWaterStatus": "LOW",
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"modePending": true,
|
|
||||||
"vacationModeRemainingDays": 0,
|
|
||||||
"electricModeRemainingDays": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,42 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"brand": "aosmith",
|
|
||||||
"model": "HPTS-50 200 202172000",
|
|
||||||
"deviceType": "NEXT_GEN_HEAT_PUMP",
|
|
||||||
"dsn": "dsn",
|
|
||||||
"junctionId": "junctionId",
|
|
||||||
"name": "My water heater",
|
|
||||||
"serial": "serial",
|
|
||||||
"install": {
|
|
||||||
"location": "Basement"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"__typename": "NextGenHeatPump",
|
|
||||||
"temperatureSetpoint": 130,
|
|
||||||
"temperatureSetpointPending": false,
|
|
||||||
"temperatureSetpointPrevious": 130,
|
|
||||||
"temperatureSetpointMaximum": 130,
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"mode": "HYBRID",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "ELECTRIC",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isOnline": true,
|
|
||||||
"firmwareVersion": "2.14",
|
|
||||||
"hotWaterStatus": "LOW",
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"modePending": false,
|
|
||||||
"vacationModeRemainingDays": 0,
|
|
||||||
"electricModeRemainingDays": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,46 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"brand": "aosmith",
|
|
||||||
"model": "HPTS-50 200 202172000",
|
|
||||||
"deviceType": "NEXT_GEN_HEAT_PUMP",
|
|
||||||
"dsn": "dsn",
|
|
||||||
"junctionId": "junctionId",
|
|
||||||
"name": "My water heater",
|
|
||||||
"serial": "serial",
|
|
||||||
"install": {
|
|
||||||
"location": "Basement"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"__typename": "NextGenHeatPump",
|
|
||||||
"temperatureSetpoint": 130,
|
|
||||||
"temperatureSetpointPending": true,
|
|
||||||
"temperatureSetpointPrevious": 130,
|
|
||||||
"temperatureSetpointMaximum": 130,
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"mode": "HYBRID",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"controls": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "ELECTRIC",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "VACATION",
|
|
||||||
"controls": "SELECT_DAYS"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isOnline": true,
|
|
||||||
"firmwareVersion": "2.14",
|
|
||||||
"hotWaterStatus": "LOW",
|
|
||||||
"mode": "HEAT_PUMP",
|
|
||||||
"modePending": false,
|
|
||||||
"vacationModeRemainingDays": 0,
|
|
||||||
"electricModeRemainingDays": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"average": 2.7552000000000003,
|
|
||||||
"graphData": [
|
|
||||||
{
|
|
||||||
"date": "2023-10-30T04:00:00.000Z",
|
|
||||||
"kwh": 2.01
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2023-10-31T04:00:00.000Z",
|
|
||||||
"kwh": 1.542
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2023-11-01T04:00:00.000Z",
|
|
||||||
"kwh": 1.908
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lifetimeKwh": 132.825,
|
|
||||||
"startDate": "Oct 30"
|
|
||||||
}
|
|
|
@ -15,11 +15,9 @@ from homeassistant.components.aosmith.const import (
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import (
|
from .conftest import build_device_fixture
|
||||||
MockConfigEntry,
|
|
||||||
async_fire_time_changed,
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
load_json_array_fixture,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_setup(init_integration: MockConfigEntry) -> None:
|
async def test_config_entry_setup(init_integration: MockConfigEntry) -> None:
|
||||||
|
@ -52,7 +50,7 @@ async def test_config_entry_not_ready_get_energy_use_data_error(
|
||||||
"""Test the config entry not ready when get_energy_use_data fails."""
|
"""Test the config entry not ready when get_energy_use_data fails."""
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
get_devices_fixture = load_json_array_fixture("get_devices.json", DOMAIN)
|
get_devices_fixture = [build_device_fixture(False, False, True)]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices",
|
"homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices",
|
||||||
|
@ -68,12 +66,17 @@ async def test_config_entry_not_ready_get_energy_use_data_error(
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("get_devices_fixture", "time_to_wait", "expected_call_count"),
|
(
|
||||||
|
"get_devices_fixture_mode_pending",
|
||||||
|
"get_devices_fixture_setpoint_pending",
|
||||||
|
"time_to_wait",
|
||||||
|
"expected_call_count",
|
||||||
|
),
|
||||||
[
|
[
|
||||||
("get_devices", REGULAR_INTERVAL, 1),
|
(False, False, REGULAR_INTERVAL, 1),
|
||||||
("get_devices", FAST_INTERVAL, 0),
|
(False, False, FAST_INTERVAL, 0),
|
||||||
("get_devices_mode_pending", FAST_INTERVAL, 1),
|
(True, False, FAST_INTERVAL, 1),
|
||||||
("get_devices_setpoint_pending", FAST_INTERVAL, 1),
|
(False, True, FAST_INTERVAL, 1),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_update(
|
async def test_update(
|
||||||
|
|
|
@ -2,15 +2,10 @@
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from py_aosmith.models import OperationMode
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.aosmith.const import (
|
|
||||||
AOSMITH_MODE_ELECTRIC,
|
|
||||||
AOSMITH_MODE_HEAT_PUMP,
|
|
||||||
AOSMITH_MODE_HYBRID,
|
|
||||||
AOSMITH_MODE_VACATION,
|
|
||||||
)
|
|
||||||
from homeassistant.components.water_heater import (
|
from homeassistant.components.water_heater import (
|
||||||
ATTR_AWAY_MODE,
|
ATTR_AWAY_MODE,
|
||||||
ATTR_OPERATION_MODE,
|
ATTR_OPERATION_MODE,
|
||||||
|
@ -59,8 +54,8 @@ async def test_state(
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("get_devices_fixture"),
|
("get_devices_fixture_has_vacation_mode"),
|
||||||
["get_devices_no_vacation_mode"],
|
[False],
|
||||||
)
|
)
|
||||||
async def test_state_away_mode_unsupported(
|
async def test_state_away_mode_unsupported(
|
||||||
hass: HomeAssistant, init_integration: MockConfigEntry
|
hass: HomeAssistant, init_integration: MockConfigEntry
|
||||||
|
@ -77,9 +72,9 @@ async def test_state_away_mode_unsupported(
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("hass_mode", "aosmith_mode"),
|
("hass_mode", "aosmith_mode"),
|
||||||
[
|
[
|
||||||
(STATE_HEAT_PUMP, AOSMITH_MODE_HEAT_PUMP),
|
(STATE_HEAT_PUMP, OperationMode.HEAT_PUMP),
|
||||||
(STATE_ECO, AOSMITH_MODE_HYBRID),
|
(STATE_ECO, OperationMode.HYBRID),
|
||||||
(STATE_ELECTRIC, AOSMITH_MODE_ELECTRIC),
|
(STATE_ELECTRIC, OperationMode.ELECTRIC),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_set_operation_mode(
|
async def test_set_operation_mode(
|
||||||
|
@ -122,8 +117,8 @@ async def test_set_temperature(
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("hass_away_mode", "aosmith_mode"),
|
("hass_away_mode", "aosmith_mode"),
|
||||||
[
|
[
|
||||||
(True, AOSMITH_MODE_VACATION),
|
(True, OperationMode.VACATION),
|
||||||
(False, AOSMITH_MODE_HYBRID),
|
(False, OperationMode.HYBRID),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_away_mode(
|
async def test_away_mode(
|
||||||
|
|
Loading…
Reference in New Issue