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:
osohotwateriot 2024-10-22 13:22:46 +03:00 committed by GitHub
parent d40341f1ad
commit cdf809926b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1001 additions and 2 deletions

View File

@ -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"
}
}
}

View 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:

View File

@ -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)"
}
}
}
}
}

View File

@ -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()

View 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

View 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
]
}

View 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',
})
# ---

View 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)