mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add water_heater to google_assistant (#105915)
* Add water_heater to google_assistant * Follow up comments * Add water_heater to default exposed domains
This commit is contained in:
parent
c57cc85174
commit
f5f9b89848
@ -22,6 +22,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
water_heater,
|
||||||
)
|
)
|
||||||
|
|
||||||
DOMAIN = "google_assistant"
|
DOMAIN = "google_assistant"
|
||||||
@ -64,6 +65,7 @@ DEFAULT_EXPOSED_DOMAINS = [
|
|||||||
"sensor",
|
"sensor",
|
||||||
"switch",
|
"switch",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
|
"water_heater",
|
||||||
]
|
]
|
||||||
|
|
||||||
# https://developers.google.com/assistant/smarthome/guides
|
# https://developers.google.com/assistant/smarthome/guides
|
||||||
@ -93,6 +95,7 @@ TYPE_THERMOSTAT = f"{PREFIX_TYPES}THERMOSTAT"
|
|||||||
TYPE_TV = f"{PREFIX_TYPES}TV"
|
TYPE_TV = f"{PREFIX_TYPES}TV"
|
||||||
TYPE_WINDOW = f"{PREFIX_TYPES}WINDOW"
|
TYPE_WINDOW = f"{PREFIX_TYPES}WINDOW"
|
||||||
TYPE_VACUUM = f"{PREFIX_TYPES}VACUUM"
|
TYPE_VACUUM = f"{PREFIX_TYPES}VACUUM"
|
||||||
|
TYPE_WATERHEATER = f"{PREFIX_TYPES}WATERHEATER"
|
||||||
|
|
||||||
SERVICE_REQUEST_SYNC = "request_sync"
|
SERVICE_REQUEST_SYNC = "request_sync"
|
||||||
HOMEGRAPH_URL = "https://homegraph.googleapis.com/"
|
HOMEGRAPH_URL = "https://homegraph.googleapis.com/"
|
||||||
@ -147,6 +150,7 @@ DOMAIN_TO_GOOGLE_TYPES = {
|
|||||||
sensor.DOMAIN: TYPE_SENSOR,
|
sensor.DOMAIN: TYPE_SENSOR,
|
||||||
switch.DOMAIN: TYPE_SWITCH,
|
switch.DOMAIN: TYPE_SWITCH,
|
||||||
vacuum.DOMAIN: TYPE_VACUUM,
|
vacuum.DOMAIN: TYPE_VACUUM,
|
||||||
|
water_heater.DOMAIN: TYPE_WATERHEATER,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEVICE_CLASS_TO_GOOGLE_TYPES = {
|
DEVICE_CLASS_TO_GOOGLE_TYPES = {
|
||||||
|
@ -29,6 +29,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
water_heater,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||||
from homeassistant.components.camera import CameraEntityFeature
|
from homeassistant.components.camera import CameraEntityFeature
|
||||||
@ -40,6 +41,7 @@ from homeassistant.components.light import LightEntityFeature
|
|||||||
from homeassistant.components.lock import STATE_JAMMED, STATE_UNLOCKING
|
from homeassistant.components.lock import STATE_JAMMED, STATE_UNLOCKING
|
||||||
from homeassistant.components.media_player import MediaPlayerEntityFeature, MediaType
|
from homeassistant.components.media_player import MediaPlayerEntityFeature, MediaType
|
||||||
from homeassistant.components.vacuum import VacuumEntityFeature
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
@ -139,6 +141,7 @@ COMMAND_PAUSEUNPAUSE = f"{PREFIX_COMMANDS}PauseUnpause"
|
|||||||
COMMAND_BRIGHTNESS_ABSOLUTE = f"{PREFIX_COMMANDS}BrightnessAbsolute"
|
COMMAND_BRIGHTNESS_ABSOLUTE = f"{PREFIX_COMMANDS}BrightnessAbsolute"
|
||||||
COMMAND_COLOR_ABSOLUTE = f"{PREFIX_COMMANDS}ColorAbsolute"
|
COMMAND_COLOR_ABSOLUTE = f"{PREFIX_COMMANDS}ColorAbsolute"
|
||||||
COMMAND_ACTIVATE_SCENE = f"{PREFIX_COMMANDS}ActivateScene"
|
COMMAND_ACTIVATE_SCENE = f"{PREFIX_COMMANDS}ActivateScene"
|
||||||
|
COMMAND_SET_TEMPERATURE = f"{PREFIX_COMMANDS}SetTemperature"
|
||||||
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT = (
|
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT = (
|
||||||
f"{PREFIX_COMMANDS}ThermostatTemperatureSetpoint"
|
f"{PREFIX_COMMANDS}ThermostatTemperatureSetpoint"
|
||||||
)
|
)
|
||||||
@ -417,6 +420,9 @@ class OnOffTrait(_Trait):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def supported(domain, features, device_class, _):
|
def supported(domain, features, device_class, _):
|
||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
|
if domain == water_heater.DOMAIN and features & WaterHeaterEntityFeature.ON_OFF:
|
||||||
|
return True
|
||||||
|
|
||||||
return domain in (
|
return domain in (
|
||||||
group.DOMAIN,
|
group.DOMAIN,
|
||||||
input_boolean.DOMAIN,
|
input_boolean.DOMAIN,
|
||||||
@ -894,38 +900,97 @@ class StartStopTrait(_Trait):
|
|||||||
|
|
||||||
@register_trait
|
@register_trait
|
||||||
class TemperatureControlTrait(_Trait):
|
class TemperatureControlTrait(_Trait):
|
||||||
"""Trait for devices (other than thermostats) that support controlling temperature. Workaround for Temperature sensors.
|
"""Trait for devices (other than thermostats) that support controlling temperature.
|
||||||
|
|
||||||
|
Control the target temperature of water heaters.
|
||||||
|
Offers a workaround for Temperature sensors by setting queryOnlyTemperatureControl
|
||||||
|
in the response.
|
||||||
|
|
||||||
https://developers.google.com/assistant/smarthome/traits/temperaturecontrol
|
https://developers.google.com/assistant/smarthome/traits/temperaturecontrol
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = TRAIT_TEMPERATURE_CONTROL
|
name = TRAIT_TEMPERATURE_CONTROL
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
COMMAND_SET_TEMPERATURE,
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported(domain, features, device_class, _):
|
def supported(domain, features, device_class, _):
|
||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
return (
|
return (
|
||||||
|
domain == water_heater.DOMAIN
|
||||||
|
and features & WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||||
|
) or (
|
||||||
domain == sensor.DOMAIN
|
domain == sensor.DOMAIN
|
||||||
and device_class == sensor.SensorDeviceClass.TEMPERATURE
|
and device_class == sensor.SensorDeviceClass.TEMPERATURE
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
"""Return temperature attributes for a sync request."""
|
"""Return temperature attributes for a sync request."""
|
||||||
return {
|
response = {}
|
||||||
"temperatureUnitForUX": _google_temp_unit(
|
domain = self.state.domain
|
||||||
self.hass.config.units.temperature_unit
|
attrs = self.state.attributes
|
||||||
),
|
unit = self.hass.config.units.temperature_unit
|
||||||
"queryOnlyTemperatureControl": True,
|
response["temperatureUnitForUX"] = _google_temp_unit(unit)
|
||||||
"temperatureRange": {
|
|
||||||
|
if domain == water_heater.DOMAIN:
|
||||||
|
min_temp = round(
|
||||||
|
TemperatureConverter.convert(
|
||||||
|
float(attrs[water_heater.ATTR_MIN_TEMP]),
|
||||||
|
unit,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
max_temp = round(
|
||||||
|
TemperatureConverter.convert(
|
||||||
|
float(attrs[water_heater.ATTR_MAX_TEMP]),
|
||||||
|
unit,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
response["temperatureRange"] = {
|
||||||
|
"minThresholdCelsius": min_temp,
|
||||||
|
"maxThresholdCelsius": max_temp,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
response["queryOnlyTemperatureControl"] = True
|
||||||
|
response["temperatureRange"] = {
|
||||||
"minThresholdCelsius": -100,
|
"minThresholdCelsius": -100,
|
||||||
"maxThresholdCelsius": 100,
|
"maxThresholdCelsius": 100,
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
return response
|
||||||
|
|
||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
"""Return temperature states."""
|
"""Return temperature states."""
|
||||||
response = {}
|
response = {}
|
||||||
|
domain = self.state.domain
|
||||||
unit = self.hass.config.units.temperature_unit
|
unit = self.hass.config.units.temperature_unit
|
||||||
|
if domain == water_heater.DOMAIN:
|
||||||
|
target_temp = self.state.attributes[water_heater.ATTR_TEMPERATURE]
|
||||||
|
current_temp = self.state.attributes[water_heater.ATTR_CURRENT_TEMPERATURE]
|
||||||
|
if target_temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||||
|
response["temperatureSetpointCelsius"] = round(
|
||||||
|
TemperatureConverter.convert(
|
||||||
|
float(target_temp),
|
||||||
|
unit,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
if current_temp is not None:
|
||||||
|
response["temperatureAmbientCelsius"] = round(
|
||||||
|
TemperatureConverter.convert(
|
||||||
|
float(current_temp),
|
||||||
|
unit,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
# domain == sensor.DOMAIN
|
||||||
current_temp = self.state.state
|
current_temp = self.state.state
|
||||||
if current_temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
if current_temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||||
temp = round(
|
temp = round(
|
||||||
@ -940,8 +1005,35 @@ class TemperatureControlTrait(_Trait):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Unsupported."""
|
"""Execute a temperature point or mode command."""
|
||||||
raise SmartHomeError(ERR_NOT_SUPPORTED, "Execute is not supported by sensor")
|
# All sent in temperatures are always in Celsius
|
||||||
|
domain = self.state.domain
|
||||||
|
unit = self.hass.config.units.temperature_unit
|
||||||
|
|
||||||
|
if domain == water_heater.DOMAIN and command == COMMAND_SET_TEMPERATURE:
|
||||||
|
min_temp = self.state.attributes[water_heater.ATTR_MIN_TEMP]
|
||||||
|
max_temp = self.state.attributes[water_heater.ATTR_MAX_TEMP]
|
||||||
|
temp = TemperatureConverter.convert(
|
||||||
|
params["temperature"], UnitOfTemperature.CELSIUS, unit
|
||||||
|
)
|
||||||
|
if unit == UnitOfTemperature.FAHRENHEIT:
|
||||||
|
temp = round(temp)
|
||||||
|
if temp < min_temp or temp > max_temp:
|
||||||
|
raise SmartHomeError(
|
||||||
|
ERR_VALUE_OUT_OF_RANGE,
|
||||||
|
f"Temperature should be between {min_temp} and {max_temp}",
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
water_heater.DOMAIN,
|
||||||
|
water_heater.SERVICE_SET_TEMPERATURE,
|
||||||
|
{ATTR_ENTITY_ID: self.state.entity_id, ATTR_TEMPERATURE: temp},
|
||||||
|
blocking=not self.config.should_report_state,
|
||||||
|
context=data.context,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise SmartHomeError(ERR_NOT_SUPPORTED, f"Execute is not supported by {domain}")
|
||||||
|
|
||||||
|
|
||||||
@register_trait
|
@register_trait
|
||||||
@ -1696,6 +1788,12 @@ class ModesTrait(_Trait):
|
|||||||
if domain == light.DOMAIN and features & LightEntityFeature.EFFECT:
|
if domain == light.DOMAIN and features & LightEntityFeature.EFFECT:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if (
|
||||||
|
domain == water_heater.DOMAIN
|
||||||
|
and features & WaterHeaterEntityFeature.OPERATION_MODE
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
if domain != media_player.DOMAIN:
|
if domain != media_player.DOMAIN:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1736,6 +1834,7 @@ class ModesTrait(_Trait):
|
|||||||
(select.DOMAIN, select.ATTR_OPTIONS, "option"),
|
(select.DOMAIN, select.ATTR_OPTIONS, "option"),
|
||||||
(humidifier.DOMAIN, humidifier.ATTR_AVAILABLE_MODES, "mode"),
|
(humidifier.DOMAIN, humidifier.ATTR_AVAILABLE_MODES, "mode"),
|
||||||
(light.DOMAIN, light.ATTR_EFFECT_LIST, "effect"),
|
(light.DOMAIN, light.ATTR_EFFECT_LIST, "effect"),
|
||||||
|
(water_heater.DOMAIN, water_heater.ATTR_OPERATION_LIST, "operation mode"),
|
||||||
):
|
):
|
||||||
if self.state.domain != domain:
|
if self.state.domain != domain:
|
||||||
continue
|
continue
|
||||||
@ -1769,6 +1868,11 @@ class ModesTrait(_Trait):
|
|||||||
elif self.state.domain == humidifier.DOMAIN:
|
elif self.state.domain == humidifier.DOMAIN:
|
||||||
if ATTR_MODE in attrs:
|
if ATTR_MODE in attrs:
|
||||||
mode_settings["mode"] = attrs.get(ATTR_MODE)
|
mode_settings["mode"] = attrs.get(ATTR_MODE)
|
||||||
|
elif self.state.domain == water_heater.DOMAIN:
|
||||||
|
if water_heater.ATTR_OPERATION_MODE in attrs:
|
||||||
|
mode_settings["operation mode"] = attrs.get(
|
||||||
|
water_heater.ATTR_OPERATION_MODE
|
||||||
|
)
|
||||||
elif self.state.domain == light.DOMAIN and (
|
elif self.state.domain == light.DOMAIN and (
|
||||||
effect := attrs.get(light.ATTR_EFFECT)
|
effect := attrs.get(light.ATTR_EFFECT)
|
||||||
):
|
):
|
||||||
@ -1840,6 +1944,20 @@ class ModesTrait(_Trait):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.state.domain == water_heater.DOMAIN:
|
||||||
|
requested_mode = settings["operation mode"]
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
water_heater.DOMAIN,
|
||||||
|
water_heater.SERVICE_SET_OPERATION_MODE,
|
||||||
|
{
|
||||||
|
water_heater.ATTR_OPERATION_MODE: requested_mode,
|
||||||
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=not self.config.should_report_state,
|
||||||
|
context=data.context,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if self.state.domain == light.DOMAIN:
|
if self.state.domain == light.DOMAIN:
|
||||||
requested_effect = settings["effect"]
|
requested_effect = settings["effect"]
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
|
@ -103,6 +103,7 @@
|
|||||||
'sensor',
|
'sensor',
|
||||||
'switch',
|
'switch',
|
||||||
'vacuum',
|
'vacuum',
|
||||||
|
'water_heater',
|
||||||
]),
|
]),
|
||||||
'project_id': '1234',
|
'project_id': '1234',
|
||||||
'report_state': False,
|
'report_state': False,
|
||||||
|
@ -27,6 +27,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
water_heater,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||||
from homeassistant.components.camera import CameraEntityFeature
|
from homeassistant.components.camera import CameraEntityFeature
|
||||||
@ -44,6 +45,7 @@ from homeassistant.components.media_player import (
|
|||||||
MediaType,
|
MediaType,
|
||||||
)
|
)
|
||||||
from homeassistant.components.vacuum import VacuumEntityFeature
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.config import async_process_ha_core_config
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
@ -75,6 +77,7 @@ from homeassistant.core import (
|
|||||||
State,
|
State,
|
||||||
)
|
)
|
||||||
from homeassistant.util import color
|
from homeassistant.util import color
|
||||||
|
from homeassistant.util.unit_conversion import TemperatureConverter
|
||||||
|
|
||||||
from . import BASIC_CONFIG, MockConfig
|
from . import BASIC_CONFIG, MockConfig
|
||||||
|
|
||||||
@ -393,6 +396,35 @@ async def test_onoff_humidifier(hass: HomeAssistant) -> None:
|
|||||||
assert off_calls[0].data == {ATTR_ENTITY_ID: "humidifier.bla"}
|
assert off_calls[0].data == {ATTR_ENTITY_ID: "humidifier.bla"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_onoff_water_heater(hass: HomeAssistant) -> None:
|
||||||
|
"""Test OnOff trait support for water_heater domain."""
|
||||||
|
assert helpers.get_google_type(water_heater.DOMAIN, None) is not None
|
||||||
|
assert trait.OnOffTrait.supported(
|
||||||
|
water_heater.DOMAIN, WaterHeaterEntityFeature.ON_OFF, None, None
|
||||||
|
)
|
||||||
|
|
||||||
|
trt_on = trait.OnOffTrait(hass, State("water_heater.bla", STATE_ON), BASIC_CONFIG)
|
||||||
|
|
||||||
|
assert trt_on.sync_attributes() == {}
|
||||||
|
|
||||||
|
assert trt_on.query_attributes() == {"on": True}
|
||||||
|
|
||||||
|
trt_off = trait.OnOffTrait(hass, State("water_heater.bla", STATE_OFF), BASIC_CONFIG)
|
||||||
|
|
||||||
|
assert trt_off.query_attributes() == {"on": False}
|
||||||
|
|
||||||
|
on_calls = async_mock_service(hass, water_heater.DOMAIN, SERVICE_TURN_ON)
|
||||||
|
await trt_on.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": True}, {})
|
||||||
|
assert len(on_calls) == 1
|
||||||
|
assert on_calls[0].data == {ATTR_ENTITY_ID: "water_heater.bla"}
|
||||||
|
|
||||||
|
off_calls = async_mock_service(hass, water_heater.DOMAIN, SERVICE_TURN_OFF)
|
||||||
|
|
||||||
|
await trt_on.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": False}, {})
|
||||||
|
assert len(off_calls) == 1
|
||||||
|
assert off_calls[0].data == {ATTR_ENTITY_ID: "water_heater.bla"}
|
||||||
|
|
||||||
|
|
||||||
async def test_dock_vacuum(hass: HomeAssistant) -> None:
|
async def test_dock_vacuum(hass: HomeAssistant) -> None:
|
||||||
"""Test dock trait support for vacuum domain."""
|
"""Test dock trait support for vacuum domain."""
|
||||||
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
|
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
|
||||||
@ -1246,6 +1278,135 @@ async def test_temperature_control(hass: HomeAssistant) -> None:
|
|||||||
assert err.value.code == const.ERR_NOT_SUPPORTED
|
assert err.value.code == const.ERR_NOT_SUPPORTED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("unit_in", "unit_out", "temp_in", "temp_out", "current_in", "current_out"),
|
||||||
|
[
|
||||||
|
(UnitOfTemperature.CELSIUS, "C", "120", 120, "130", 130),
|
||||||
|
(UnitOfTemperature.FAHRENHEIT, "F", "248", 120, "266", 130),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_temperature_control_water_heater(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
unit_in: UnitOfTemperature,
|
||||||
|
unit_out: str,
|
||||||
|
temp_in: str,
|
||||||
|
temp_out: float,
|
||||||
|
current_in: str,
|
||||||
|
current_out: float,
|
||||||
|
) -> None:
|
||||||
|
"""Test TemperatureControl trait support for water heater domain."""
|
||||||
|
hass.config.units.temperature_unit = unit_in
|
||||||
|
|
||||||
|
min_temp = TemperatureConverter.convert(
|
||||||
|
water_heater.DEFAULT_MIN_TEMP,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
unit_in,
|
||||||
|
)
|
||||||
|
max_temp = TemperatureConverter.convert(
|
||||||
|
water_heater.DEFAULT_MAX_TEMP,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
unit_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.TemperatureControlTrait(
|
||||||
|
hass,
|
||||||
|
State(
|
||||||
|
"water_heater.bla",
|
||||||
|
"attributes",
|
||||||
|
{
|
||||||
|
"min_temp": min_temp,
|
||||||
|
"max_temp": max_temp,
|
||||||
|
"temperature": temp_in,
|
||||||
|
"current_temperature": current_in,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trt.sync_attributes() == {
|
||||||
|
"temperatureUnitForUX": unit_out,
|
||||||
|
"temperatureRange": {
|
||||||
|
"maxThresholdCelsius": water_heater.DEFAULT_MAX_TEMP,
|
||||||
|
"minThresholdCelsius": water_heater.DEFAULT_MIN_TEMP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert trt.query_attributes() == {
|
||||||
|
"temperatureSetpointCelsius": temp_out,
|
||||||
|
"temperatureAmbientCelsius": current_out,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("unit", "temp_init", "temp_in", "temp_out", "current_init"),
|
||||||
|
[
|
||||||
|
(UnitOfTemperature.CELSIUS, "180", 220, 220, "180"),
|
||||||
|
(UnitOfTemperature.FAHRENHEIT, "356", 220, 428, "356"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_temperature_control_water_heater_set_temperature(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
unit: UnitOfTemperature,
|
||||||
|
temp_init: str,
|
||||||
|
temp_in: float,
|
||||||
|
temp_out: float,
|
||||||
|
current_init: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test TemperatureControl trait support for water heater domain - SetTemperature."""
|
||||||
|
hass.config.units.temperature_unit = unit
|
||||||
|
|
||||||
|
min_temp = TemperatureConverter.convert(
|
||||||
|
40,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
unit,
|
||||||
|
)
|
||||||
|
max_temp = TemperatureConverter.convert(
|
||||||
|
230,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.TemperatureControlTrait(
|
||||||
|
hass,
|
||||||
|
State(
|
||||||
|
"water_heater.bla",
|
||||||
|
"attributes",
|
||||||
|
{
|
||||||
|
"min_temp": min_temp,
|
||||||
|
"max_temp": max_temp,
|
||||||
|
"temperature": temp_init,
|
||||||
|
"current_temperature": current_init,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trt.can_execute(trait.COMMAND_SET_TEMPERATURE, {})
|
||||||
|
|
||||||
|
calls = async_mock_service(
|
||||||
|
hass, water_heater.DOMAIN, water_heater.SERVICE_SET_TEMPERATURE
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(helpers.SmartHomeError):
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_SET_TEMPERATURE,
|
||||||
|
BASIC_DATA,
|
||||||
|
{"temperature": -100},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_SET_TEMPERATURE,
|
||||||
|
BASIC_DATA,
|
||||||
|
{"temperature": temp_in},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data == {
|
||||||
|
ATTR_ENTITY_ID: "water_heater.bla",
|
||||||
|
ATTR_TEMPERATURE: temp_out,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_humidity_setting_humidifier_setpoint(hass: HomeAssistant) -> None:
|
async def test_humidity_setting_humidifier_setpoint(hass: HomeAssistant) -> None:
|
||||||
"""Test HumiditySetting trait support for humidifier domain - setpoint."""
|
"""Test HumiditySetting trait support for humidifier domain - setpoint."""
|
||||||
assert helpers.get_google_type(humidifier.DOMAIN, None) is not None
|
assert helpers.get_google_type(humidifier.DOMAIN, None) is not None
|
||||||
@ -2411,6 +2572,84 @@ async def test_modes_humidifier(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_modes_water_heater(hass: HomeAssistant) -> None:
|
||||||
|
"""Test Humidifier Mode trait."""
|
||||||
|
assert helpers.get_google_type(water_heater.DOMAIN, None) is not None
|
||||||
|
assert trait.ModesTrait.supported(
|
||||||
|
water_heater.DOMAIN, WaterHeaterEntityFeature.OPERATION_MODE, None, None
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.ModesTrait(
|
||||||
|
hass,
|
||||||
|
State(
|
||||||
|
"water_heater.water_heater",
|
||||||
|
STATE_OFF,
|
||||||
|
attributes={
|
||||||
|
water_heater.ATTR_OPERATION_LIST: [
|
||||||
|
water_heater.STATE_ECO,
|
||||||
|
water_heater.STATE_HEAT_PUMP,
|
||||||
|
water_heater.STATE_GAS,
|
||||||
|
],
|
||||||
|
ATTR_SUPPORTED_FEATURES: WaterHeaterEntityFeature.OPERATION_MODE,
|
||||||
|
water_heater.ATTR_OPERATION_MODE: water_heater.STATE_HEAT_PUMP,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
attribs = trt.sync_attributes()
|
||||||
|
assert attribs == {
|
||||||
|
"availableModes": [
|
||||||
|
{
|
||||||
|
"name": "operation mode",
|
||||||
|
"name_values": [{"name_synonym": ["operation mode"], "lang": "en"}],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"setting_name": "eco",
|
||||||
|
"setting_values": [{"setting_synonym": ["eco"], "lang": "en"}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"setting_name": "heat_pump",
|
||||||
|
"setting_values": [
|
||||||
|
{"setting_synonym": ["heat_pump"], "lang": "en"}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"setting_name": "gas",
|
||||||
|
"setting_values": [{"setting_synonym": ["gas"], "lang": "en"}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"ordered": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert trt.query_attributes() == {
|
||||||
|
"currentModeSettings": {"operation mode": "heat_pump"},
|
||||||
|
"on": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert trt.can_execute(
|
||||||
|
trait.COMMAND_MODES, params={"updateModeSettings": {"operation mode": "gas"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
calls = async_mock_service(
|
||||||
|
hass, water_heater.DOMAIN, water_heater.SERVICE_SET_OPERATION_MODE
|
||||||
|
)
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_MODES,
|
||||||
|
BASIC_DATA,
|
||||||
|
{"updateModeSettings": {"operation mode": "gas"}},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data == {
|
||||||
|
"entity_id": "water_heater.water_heater",
|
||||||
|
"operation_mode": "gas",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_sound_modes(hass: HomeAssistant) -> None:
|
async def test_sound_modes(hass: HomeAssistant) -> None:
|
||||||
"""Test Mode trait."""
|
"""Test Mode trait."""
|
||||||
assert helpers.get_google_type(media_player.DOMAIN, None) is not None
|
assert helpers.get_google_type(media_player.DOMAIN, None) is not None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user