mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +00:00
Add OSO Energy services (#118770)
* Add OSO Energy services * Fixes after review * Add tests for OSO Energy water heater * Fixes after review * Revert changes for service schema in OSO Energy * Improve osoenergy unit tests
This commit is contained in:
parent
d40341f1ad
commit
cdf809926b
@ -11,5 +11,22 @@
|
||||
"default": "mdi:water-boiler"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_profile": {
|
||||
"service": "mdi:thermometer-lines"
|
||||
},
|
||||
"set_profile": {
|
||||
"service": "mdi:thermometer-lines"
|
||||
},
|
||||
"set_v40_min": {
|
||||
"service": "mdi:car-coolant-level"
|
||||
},
|
||||
"turn_off": {
|
||||
"service": "mdi:water-boiler-off"
|
||||
},
|
||||
"turn_on": {
|
||||
"service": "mdi:water-boiler"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
261
homeassistant/components/osoenergy/services.yaml
Normal file
261
homeassistant/components/osoenergy/services.yaml
Normal file
@ -0,0 +1,261 @@
|
||||
get_profile:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
set_profile:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
fields:
|
||||
hour_00:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_01:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_02:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_03:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_04:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_05:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_06:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_07:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_08:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_09:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_10:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_11:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_12:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_13:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_14:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_15:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_16:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_17:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_18:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_19:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_20:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_21:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_22:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
hour_23:
|
||||
required: false
|
||||
example: 75
|
||||
selector:
|
||||
number:
|
||||
min: 10
|
||||
max: 75
|
||||
step: 1
|
||||
unit_of_measurement: °C
|
||||
set_v40_min:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
fields:
|
||||
v40_min:
|
||||
required: true
|
||||
example: 240
|
||||
selector:
|
||||
number:
|
||||
min: 200
|
||||
max: 550
|
||||
step: 1
|
||||
unit_of_measurement: L
|
||||
turn_off:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
fields:
|
||||
until_temp_limit:
|
||||
required: true
|
||||
default: false
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
turn_on:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
fields:
|
||||
until_temp_limit:
|
||||
required: true
|
||||
default: false
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
@ -91,5 +91,143 @@
|
||||
"name": "Temperature one"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_profile": {
|
||||
"name": "Get heater profile",
|
||||
"description": "Get the temperature profile of water heater"
|
||||
},
|
||||
"set_profile": {
|
||||
"name": "Set heater profile",
|
||||
"description": "Set the temperature profile of water heater",
|
||||
"fields": {
|
||||
"hour_00": {
|
||||
"name": "00:00",
|
||||
"description": "00:00 hour"
|
||||
},
|
||||
"hour_01": {
|
||||
"name": "01:00",
|
||||
"description": "01:00 hour"
|
||||
},
|
||||
"hour_02": {
|
||||
"name": "02:00",
|
||||
"description": "02:00 hour"
|
||||
},
|
||||
"hour_03": {
|
||||
"name": "03:00",
|
||||
"description": "03:00 hour"
|
||||
},
|
||||
"hour_04": {
|
||||
"name": "04:00",
|
||||
"description": "04:00 hour"
|
||||
},
|
||||
"hour_05": {
|
||||
"name": "05:00",
|
||||
"description": "05:00 hour"
|
||||
},
|
||||
"hour_06": {
|
||||
"name": "06:00",
|
||||
"description": "06:00 hour"
|
||||
},
|
||||
"hour_07": {
|
||||
"name": "07:00",
|
||||
"description": "07:00 hour"
|
||||
},
|
||||
"hour_08": {
|
||||
"name": "08:00",
|
||||
"description": "08:00 hour"
|
||||
},
|
||||
"hour_09": {
|
||||
"name": "09:00",
|
||||
"description": "09:00 hour"
|
||||
},
|
||||
"hour_10": {
|
||||
"name": "10:00",
|
||||
"description": "10:00 hour"
|
||||
},
|
||||
"hour_11": {
|
||||
"name": "11:00",
|
||||
"description": "11:00 hour"
|
||||
},
|
||||
"hour_12": {
|
||||
"name": "12:00",
|
||||
"description": "12:00 hour"
|
||||
},
|
||||
"hour_13": {
|
||||
"name": "13:00",
|
||||
"description": "13:00 hour"
|
||||
},
|
||||
"hour_14": {
|
||||
"name": "14:00",
|
||||
"description": "14:00 hour"
|
||||
},
|
||||
"hour_15": {
|
||||
"name": "15:00",
|
||||
"description": "15:00 hour"
|
||||
},
|
||||
"hour_16": {
|
||||
"name": "16:00",
|
||||
"description": "16:00 hour"
|
||||
},
|
||||
"hour_17": {
|
||||
"name": "17:00",
|
||||
"description": "17:00 hour"
|
||||
},
|
||||
"hour_18": {
|
||||
"name": "18:00",
|
||||
"description": "18:00 hour"
|
||||
},
|
||||
"hour_19": {
|
||||
"name": "19:00",
|
||||
"description": "19:00 hour"
|
||||
},
|
||||
"hour_20": {
|
||||
"name": "20:00",
|
||||
"description": "20:00 hour"
|
||||
},
|
||||
"hour_21": {
|
||||
"name": "21:00",
|
||||
"description": "21:00 hour"
|
||||
},
|
||||
"hour_22": {
|
||||
"name": "22:00",
|
||||
"description": "22:00 hour"
|
||||
},
|
||||
"hour_23": {
|
||||
"name": "23:00",
|
||||
"description": "23:00 hour"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set_v40_min": {
|
||||
"name": "Set v40 min",
|
||||
"description": "Set the minimum quantity of water at 40°C for a heater",
|
||||
"fields": {
|
||||
"v40_min": {
|
||||
"name": "V40 Min",
|
||||
"description": "Minimum quantity of water at 40°C (200-350 for SAGA S200, 300-550 for SAGA S300)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"turn_off": {
|
||||
"name": "Turn off heating",
|
||||
"description": "Turn off heating for one hour or until min temperature is reached",
|
||||
"fields": {
|
||||
"until_temp_limit": {
|
||||
"name": "Until temperature limit",
|
||||
"description": "Choose if heating should be off until min temperature (True) is reached or for one hour (False)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"turn_on": {
|
||||
"name": "Turn on heating",
|
||||
"description": "Turn on heating for one hour or until max temperature is reached",
|
||||
"fields": {
|
||||
"until_temp_limit": {
|
||||
"name": "Until temperature limit",
|
||||
"description": "Choose if heating should be on until max temperature (True) is reached or for one hour (False)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
"""Support for OSO Energy water heaters."""
|
||||
|
||||
import datetime as dt
|
||||
from typing import Any
|
||||
|
||||
from apyosoenergyapi import OSOEnergy
|
||||
from apyosoenergyapi.helper.const import OSOEnergyWaterHeaterData
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.water_heater import (
|
||||
STATE_ECO,
|
||||
@ -15,12 +17,17 @@ from homeassistant.components.water_heater import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.json import JsonValueType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import OSOEnergyEntity
|
||||
|
||||
ATTR_UNTIL_TEMP_LIMIT = "until_temp_limit"
|
||||
ATTR_V40MIN = "v40_min"
|
||||
CURRENT_OPERATION_MAP: dict[str, Any] = {
|
||||
"default": {
|
||||
"off": STATE_OFF,
|
||||
@ -34,6 +41,11 @@ CURRENT_OPERATION_MAP: dict[str, Any] = {
|
||||
"extraenergy": STATE_HIGH_DEMAND,
|
||||
},
|
||||
}
|
||||
SERVICE_GET_PROFILE = "get_profile"
|
||||
SERVICE_SET_PROFILE = "set_profile"
|
||||
SERVICE_SET_V40MIN = "set_v40_min"
|
||||
SERVICE_TURN_OFF = "turn_off"
|
||||
SERVICE_TURN_ON = "turn_on"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -46,6 +58,102 @@ async def async_setup_entry(
|
||||
return
|
||||
async_add_entities((OSOEnergyWaterHeater(osoenergy, dev) for dev in devices), True)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_GET_PROFILE,
|
||||
{},
|
||||
OSOEnergyWaterHeater.async_get_profile.__name__,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
service_set_profile_schema = cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Optional(f"hour_{hour:02d}"): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=10, max=75)
|
||||
)
|
||||
for hour in range(24)
|
||||
}
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_PROFILE,
|
||||
service_set_profile_schema,
|
||||
OSOEnergyWaterHeater.async_set_profile.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_V40MIN,
|
||||
{
|
||||
vol.Required(ATTR_V40MIN): vol.All(
|
||||
vol.Coerce(float), vol.Range(min=200, max=550)
|
||||
),
|
||||
},
|
||||
OSOEnergyWaterHeater.async_set_v40_min.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_TURN_OFF,
|
||||
{vol.Required(ATTR_UNTIL_TEMP_LIMIT): vol.All(cv.boolean)},
|
||||
OSOEnergyWaterHeater.async_oso_turn_off.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_TURN_ON,
|
||||
{vol.Required(ATTR_UNTIL_TEMP_LIMIT): vol.All(cv.boolean)},
|
||||
OSOEnergyWaterHeater.async_oso_turn_on.__name__,
|
||||
)
|
||||
|
||||
|
||||
def _get_utc_hour(local_hour: int) -> dt.datetime:
|
||||
"""Convert the requested local hour to a utc hour for the day.
|
||||
|
||||
Args:
|
||||
local_hour: the local hour (0-23) for the current day to be converted.
|
||||
|
||||
Returns:
|
||||
Datetime representation for the requested hour in utc time for the day.
|
||||
|
||||
"""
|
||||
now = dt_util.now()
|
||||
local_time = now.replace(hour=local_hour, minute=0, second=0, microsecond=0)
|
||||
return dt_util.as_utc(local_time)
|
||||
|
||||
|
||||
def _get_local_hour(utc_hour: int) -> dt.datetime:
|
||||
"""Convert the requested utc hour to a local hour for the day.
|
||||
|
||||
Args:
|
||||
utc_hour: the utc hour (0-23) for the current day to be converted.
|
||||
|
||||
Returns:
|
||||
Datetime representation for the requested hour in local time for the day.
|
||||
|
||||
"""
|
||||
utc_now = dt_util.utcnow()
|
||||
utc_time = utc_now.replace(hour=utc_hour, minute=0, second=0, microsecond=0)
|
||||
return dt_util.as_local(utc_time)
|
||||
|
||||
|
||||
def _convert_profile_to_local(values: list[float]) -> list[JsonValueType]:
|
||||
"""Convert UTC profile to local.
|
||||
|
||||
Receives a device temperature schedule - 24 values for the day where the index represents the hour of the day in UTC.
|
||||
Converts the schedule to local time.
|
||||
|
||||
Args:
|
||||
values: list of floats representing the 24 hour temperature schedule for the device
|
||||
Returns:
|
||||
The device temperature schedule in local time.
|
||||
|
||||
"""
|
||||
profile: list[JsonValueType] = [0.0] * 24
|
||||
for hour in range(24):
|
||||
local_hour = _get_local_hour(hour)
|
||||
profile[local_hour.hour] = float(values[hour])
|
||||
|
||||
return profile
|
||||
|
||||
|
||||
class OSOEnergyWaterHeater(
|
||||
OSOEnergyEntity[OSOEnergyWaterHeaterData], WaterHeaterEntity
|
||||
@ -53,7 +161,9 @@ class OSOEnergyWaterHeater(
|
||||
"""OSO Energy Water Heater Device."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_supported_features = WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
_attr_supported_features = (
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE | WaterHeaterEntityFeature.ON_OFF
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
def __init__(
|
||||
@ -131,6 +241,36 @@ class OSOEnergyWaterHeater(
|
||||
|
||||
await self.osoenergy.hotwater.set_profile(self.entity_data, profile)
|
||||
|
||||
async def async_get_profile(self) -> ServiceResponse:
|
||||
"""Return the current temperature profile of the device."""
|
||||
|
||||
profile = self.entity_data.profile
|
||||
return {"profile": _convert_profile_to_local(profile)}
|
||||
|
||||
async def async_set_profile(self, **kwargs: Any) -> None:
|
||||
"""Handle the service call."""
|
||||
profile = self.entity_data.profile
|
||||
|
||||
for hour in range(24):
|
||||
hour_key = f"hour_{hour:02d}"
|
||||
|
||||
if hour_key in kwargs:
|
||||
profile[_get_utc_hour(hour).hour] = kwargs[hour_key]
|
||||
|
||||
await self.osoenergy.hotwater.set_profile(self.entity_data, profile)
|
||||
|
||||
async def async_set_v40_min(self, v40_min) -> None:
|
||||
"""Handle the service call."""
|
||||
await self.osoenergy.hotwater.set_v40_min(self.entity_data, v40_min)
|
||||
|
||||
async def async_oso_turn_off(self, until_temp_limit) -> None:
|
||||
"""Handle the service call."""
|
||||
await self.osoenergy.hotwater.turn_off(self.entity_data, until_temp_limit)
|
||||
|
||||
async def async_oso_turn_on(self, until_temp_limit) -> None:
|
||||
"""Handle the service call."""
|
||||
await self.osoenergy.hotwater.turn_on(self.entity_data, until_temp_limit)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update all Node data from Hive."""
|
||||
await self.osoenergy.session.update_data()
|
||||
|
90
tests/components/osoenergy/conftest.py
Normal file
90
tests/components/osoenergy/conftest.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""Common fixtures for the OSO Energy tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from apyosoenergyapi.waterheater import OSOEnergyWaterHeaterData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.osoenergy.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util.json import JsonObjectType
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||
|
||||
MOCK_CONFIG = {
|
||||
CONF_API_KEY: "secret_api_key",
|
||||
}
|
||||
TEST_USER_EMAIL = "test_user_email@domain.com"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def water_heater_fixture() -> JsonObjectType:
|
||||
"""Load the water heater fixture."""
|
||||
return load_json_object_fixture("water_heater.json", DOMAIN)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_water_heater(water_heater_fixture) -> MagicMock:
|
||||
"""Water heater mock object."""
|
||||
mock_heater = MagicMock(OSOEnergyWaterHeaterData)
|
||||
for key, value in water_heater_fixture.items():
|
||||
setattr(mock_heater, key, value)
|
||||
return mock_heater
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_entry_data() -> dict[str, Any]:
|
||||
"""Mock config entry data for fixture."""
|
||||
return MOCK_CONFIG
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry(
|
||||
hass: HomeAssistant, mock_entry_data: dict[str, Any]
|
||||
) -> ConfigEntry:
|
||||
"""Mock a config entry setup for incomfort integration."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=mock_entry_data)
|
||||
entry.add_to_hass(hass)
|
||||
return entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_osoenergy_client(mock_water_heater) -> Generator[AsyncMock]:
|
||||
"""Mock a OSO Energy client."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.osoenergy.OSOEnergy", MagicMock()
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.osoenergy.config_flow.OSOEnergy", new=mock_client
|
||||
),
|
||||
):
|
||||
mock_session = MagicMock()
|
||||
mock_session.device_list = {"water_heater": [mock_water_heater]}
|
||||
mock_session.start_session = AsyncMock(
|
||||
return_value={"water_heater": [mock_water_heater]}
|
||||
)
|
||||
mock_session.update_data = AsyncMock(return_value=True)
|
||||
|
||||
mock_client().session = mock_session
|
||||
|
||||
mock_hotwater = MagicMock()
|
||||
mock_hotwater.get_water_heater = AsyncMock(return_value=mock_water_heater)
|
||||
mock_hotwater.set_profile = AsyncMock(return_value=True)
|
||||
mock_hotwater.set_v40_min = AsyncMock(return_value=True)
|
||||
mock_hotwater.turn_on = AsyncMock(return_value=True)
|
||||
mock_hotwater.turn_off = AsyncMock(return_value=True)
|
||||
|
||||
mock_client().hotwater = mock_hotwater
|
||||
|
||||
mock_client().get_user_email = AsyncMock(return_value=TEST_USER_EMAIL)
|
||||
mock_client().start_session = AsyncMock(
|
||||
return_value={"water_heater": [mock_water_heater]}
|
||||
)
|
||||
|
||||
yield mock_client
|
20
tests/components/osoenergy/fixtures/water_heater.json
Normal file
20
tests/components/osoenergy/fixtures/water_heater.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"device_id": "osoenergy_water_heater",
|
||||
"device_type": "SAGA S200",
|
||||
"device_name": "TEST DEVICE",
|
||||
"current_temperature": 60,
|
||||
"min_temperature": 10,
|
||||
"max_temperature": 75,
|
||||
"target_temperature": 60,
|
||||
"target_temperature_low": 57,
|
||||
"target_temperature_high": 63,
|
||||
"available": true,
|
||||
"online": true,
|
||||
"current_operation": "on",
|
||||
"optimization_mode": "oso",
|
||||
"heater_mode": "auto",
|
||||
"profile": [
|
||||
10, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
|
||||
60, 60, 60, 60, 60
|
||||
]
|
||||
}
|
57
tests/components/osoenergy/snapshots/test_water_heater.ambr
Normal file
57
tests/components/osoenergy/snapshots/test_water_heater.ambr
Normal file
@ -0,0 +1,57 @@
|
||||
# serializer version: 1
|
||||
# name: test_water_heater[water_heater.test_device-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max_temp': 75,
|
||||
'min_temp': 10,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'water_heater',
|
||||
'entity_category': None,
|
||||
'entity_id': 'water_heater.test_device',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'osoenergy',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <WaterHeaterEntityFeature: 9>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'osoenergy_water_heater',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_water_heater[water_heater.test_device-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 60,
|
||||
'friendly_name': 'TEST DEVICE',
|
||||
'max_temp': 75,
|
||||
'min_temp': 10,
|
||||
'supported_features': <WaterHeaterEntityFeature: 9>,
|
||||
'target_temp_high': 63,
|
||||
'target_temp_low': 57,
|
||||
'temperature': 60,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'water_heater.test_device',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'eco',
|
||||
})
|
||||
# ---
|
276
tests/components/osoenergy/test_water_heater.py
Normal file
276
tests/components/osoenergy/test_water_heater.py
Normal file
@ -0,0 +1,276 @@
|
||||
"""The water heater tests for the OSO Energy platform."""
|
||||
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.osoenergy.const import DOMAIN
|
||||
from homeassistant.components.osoenergy.water_heater import (
|
||||
ATTR_UNTIL_TEMP_LIMIT,
|
||||
ATTR_V40MIN,
|
||||
SERVICE_GET_PROFILE,
|
||||
SERVICE_SET_PROFILE,
|
||||
SERVICE_SET_V40MIN,
|
||||
)
|
||||
from homeassistant.components.water_heater import (
|
||||
DOMAIN as WATER_HEATER_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_TEMPERATURE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import snapshot_platform
|
||||
|
||||
|
||||
@patch("homeassistant.components.osoenergy.PLATFORMS", [Platform.WATER_HEATER])
|
||||
async def test_water_heater(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test states of the water heater."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2024-10-10 00:00:00")
|
||||
async def test_get_profile(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
profile = await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_GET_PROFILE,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
# The profile is returned in UTC format from the server
|
||||
# Each index represents an hour from the current day (0-23). For example index 2 - 02:00 UTC
|
||||
# Depending on the time zone and the DST the UTC hour is converted to local time and the value is placed in the correct index
|
||||
# Example: time zone 'US/Pacific' and DST (-7 hours difference) - index 9 (09:00 UTC) will be converted to index 2 (02:00 Local)
|
||||
assert profile == {
|
||||
"water_heater.test_device": {
|
||||
"profile": [
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
10,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2024-10-10 00:00:00")
|
||||
async def test_set_profile(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_PROFILE,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device", "hour_01": 45},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# The server expects to receive the profile in UTC format
|
||||
# Each field represents an hour from the current day (0-23). For example field hour_01 - 01:00 Local time
|
||||
# Depending on the time zone and the DST the Local hour is converted to UTC time and the value is placed in the correct index
|
||||
# Example: time zone 'US/Pacific' and DST (-7 hours difference) - index 1 (01:00 Local) will be converted to index 8 (08:00 Utc)
|
||||
mock_osoenergy_client().hotwater.set_profile.assert_called_once_with(
|
||||
ANY,
|
||||
[
|
||||
10,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
45,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
60,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def test_set_v40_min(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_V40MIN,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device", ATTR_V40MIN: 300},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.set_v40_min.assert_called_once_with(ANY, 300)
|
||||
|
||||
|
||||
async def test_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
WATER_HEATER_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device", ATTR_TEMPERATURE: 45},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.set_profile.assert_called_once_with(
|
||||
ANY,
|
||||
[
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def test_turn_on(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test turning the heater on."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
WATER_HEATER_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.turn_on.assert_called_once_with(ANY, True)
|
||||
|
||||
|
||||
async def test_turn_off(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
WATER_HEATER_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.turn_off.assert_called_once_with(ANY, True)
|
||||
|
||||
|
||||
async def test_oso_turn_on(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test turning the heater on."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device", ATTR_UNTIL_TEMP_LIMIT: False},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.turn_on.assert_called_once_with(ANY, False)
|
||||
|
||||
|
||||
async def test_oso_turn_off(
|
||||
hass: HomeAssistant,
|
||||
mock_osoenergy_client: MagicMock,
|
||||
mock_config_entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test getting the heater profile."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "water_heater.test_device", ATTR_UNTIL_TEMP_LIMIT: False},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_osoenergy_client().hotwater.turn_off.assert_called_once_with(ANY, False)
|
Loading…
x
Reference in New Issue
Block a user