mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add valve platform support to google_assistant (#106139)
* Add valve platform to google_assistant * Use constant for domains set
This commit is contained in:
parent
c126022d4f
commit
e311a6835e
@ -22,6 +22,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
valve,
|
||||||
water_heater,
|
water_heater,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ DEFAULT_EXPOSED_DOMAINS = [
|
|||||||
"sensor",
|
"sensor",
|
||||||
"switch",
|
"switch",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -95,6 +97,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_VALVE = f"{PREFIX_TYPES}VALVE"
|
||||||
TYPE_WATERHEATER = f"{PREFIX_TYPES}WATERHEATER"
|
TYPE_WATERHEATER = f"{PREFIX_TYPES}WATERHEATER"
|
||||||
|
|
||||||
SERVICE_REQUEST_SYNC = "request_sync"
|
SERVICE_REQUEST_SYNC = "request_sync"
|
||||||
@ -150,6 +153,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,
|
||||||
|
valve.DOMAIN: TYPE_VALVE,
|
||||||
water_heater.DOMAIN: TYPE_WATERHEATER,
|
water_heater.DOMAIN: TYPE_WATERHEATER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
valve,
|
||||||
water_heater,
|
water_heater,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||||
@ -41,6 +42,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.valve import ValveEntityFeature
|
||||||
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
@ -180,6 +182,57 @@ TRAITS: list[type[_Trait]] = []
|
|||||||
|
|
||||||
FAN_SPEED_MAX_SPEED_COUNT = 5
|
FAN_SPEED_MAX_SPEED_COUNT = 5
|
||||||
|
|
||||||
|
COVER_VALVE_STATES = {
|
||||||
|
cover.DOMAIN: {
|
||||||
|
"closed": cover.STATE_CLOSED,
|
||||||
|
"closing": cover.STATE_CLOSING,
|
||||||
|
"open": cover.STATE_OPEN,
|
||||||
|
"opening": cover.STATE_OPENING,
|
||||||
|
},
|
||||||
|
valve.DOMAIN: {
|
||||||
|
"closed": valve.STATE_CLOSED,
|
||||||
|
"closing": valve.STATE_CLOSING,
|
||||||
|
"open": valve.STATE_OPEN,
|
||||||
|
"opening": valve.STATE_OPENING,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVICE_STOP_COVER_VALVE = {
|
||||||
|
cover.DOMAIN: cover.SERVICE_STOP_COVER,
|
||||||
|
valve.DOMAIN: valve.SERVICE_STOP_VALVE,
|
||||||
|
}
|
||||||
|
SERVICE_OPEN_COVER_VALVE = {
|
||||||
|
cover.DOMAIN: cover.SERVICE_OPEN_COVER,
|
||||||
|
valve.DOMAIN: valve.SERVICE_OPEN_VALVE,
|
||||||
|
}
|
||||||
|
SERVICE_CLOSE_COVER_VALVE = {
|
||||||
|
cover.DOMAIN: cover.SERVICE_CLOSE_COVER,
|
||||||
|
valve.DOMAIN: valve.SERVICE_CLOSE_VALVE,
|
||||||
|
}
|
||||||
|
SERVICE_SET_POSITION_COVER_VALVE = {
|
||||||
|
cover.DOMAIN: cover.SERVICE_SET_COVER_POSITION,
|
||||||
|
valve.DOMAIN: valve.SERVICE_SET_VALVE_POSITION,
|
||||||
|
}
|
||||||
|
|
||||||
|
COVER_VALVE_CURRENT_POSITION = {
|
||||||
|
cover.DOMAIN: cover.ATTR_CURRENT_POSITION,
|
||||||
|
valve.DOMAIN: valve.ATTR_CURRENT_POSITION,
|
||||||
|
}
|
||||||
|
|
||||||
|
COVER_VALVE_POSITION = {
|
||||||
|
cover.DOMAIN: cover.ATTR_POSITION,
|
||||||
|
valve.DOMAIN: valve.ATTR_POSITION,
|
||||||
|
}
|
||||||
|
|
||||||
|
COVER_VALVE_SET_POSITION_FEATURE = {
|
||||||
|
cover.DOMAIN: CoverEntityFeature.SET_POSITION,
|
||||||
|
valve.DOMAIN: ValveEntityFeature.SET_POSITION,
|
||||||
|
}
|
||||||
|
|
||||||
|
COVER_VALVE_DOMAINS = {cover.DOMAIN, valve.DOMAIN}
|
||||||
|
|
||||||
|
FRIENDLY_DOMAIN = {cover.DOMAIN: "Cover", valve.DOMAIN: "Valve"}
|
||||||
|
|
||||||
_TraitT = TypeVar("_TraitT", bound="_Trait")
|
_TraitT = TypeVar("_TraitT", bound="_Trait")
|
||||||
|
|
||||||
|
|
||||||
@ -796,6 +849,9 @@ class StartStopTrait(_Trait):
|
|||||||
if domain == cover.DOMAIN and features & CoverEntityFeature.STOP:
|
if domain == cover.DOMAIN and features & CoverEntityFeature.STOP:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if domain == valve.DOMAIN and features & ValveEntityFeature.STOP:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
@ -807,7 +863,7 @@ class StartStopTrait(_Trait):
|
|||||||
& VacuumEntityFeature.PAUSE
|
& VacuumEntityFeature.PAUSE
|
||||||
!= 0
|
!= 0
|
||||||
}
|
}
|
||||||
if domain == cover.DOMAIN:
|
if domain in COVER_VALVE_DOMAINS:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
@ -823,14 +879,16 @@ class StartStopTrait(_Trait):
|
|||||||
|
|
||||||
if domain == cover.DOMAIN:
|
if domain == cover.DOMAIN:
|
||||||
return {"isRunning": state in (cover.STATE_CLOSING, cover.STATE_OPENING)}
|
return {"isRunning": state in (cover.STATE_CLOSING, cover.STATE_OPENING)}
|
||||||
|
if domain == valve.DOMAIN:
|
||||||
|
return {"isRunning": True}
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute a StartStop command."""
|
"""Execute a StartStop command."""
|
||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
if domain == vacuum.DOMAIN:
|
if domain == vacuum.DOMAIN:
|
||||||
return await self._execute_vacuum(command, data, params, challenge)
|
return await self._execute_vacuum(command, data, params, challenge)
|
||||||
if domain == cover.DOMAIN:
|
if domain in COVER_VALVE_DOMAINS:
|
||||||
return await self._execute_cover(command, data, params, challenge)
|
return await self._execute_cover_or_valve(command, data, params, challenge)
|
||||||
|
|
||||||
async def _execute_vacuum(self, command, data, params, challenge):
|
async def _execute_vacuum(self, command, data, params, challenge):
|
||||||
"""Execute a StartStop command."""
|
"""Execute a StartStop command."""
|
||||||
@ -869,28 +927,35 @@ class StartStopTrait(_Trait):
|
|||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _execute_cover(self, command, data, params, challenge):
|
async def _execute_cover_or_valve(self, command, data, params, challenge):
|
||||||
"""Execute a StartStop command."""
|
"""Execute a StartStop command."""
|
||||||
|
domain = self.state.domain
|
||||||
if command == COMMAND_STARTSTOP:
|
if command == COMMAND_STARTSTOP:
|
||||||
if params["start"] is False:
|
if params["start"] is False:
|
||||||
if self.state.state in (
|
if (
|
||||||
cover.STATE_CLOSING,
|
self.state.state
|
||||||
cover.STATE_OPENING,
|
in (
|
||||||
) or self.state.attributes.get(ATTR_ASSUMED_STATE):
|
COVER_VALVE_STATES[domain]["closing"],
|
||||||
|
COVER_VALVE_STATES[domain]["opening"],
|
||||||
|
)
|
||||||
|
or domain == valve.DOMAIN
|
||||||
|
or self.state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
):
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
self.state.domain,
|
domain,
|
||||||
cover.SERVICE_STOP_COVER,
|
SERVICE_STOP_COVER_VALVE[domain],
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=not self.config.should_report_state,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_ALREADY_STOPPED, "Cover is already stopped"
|
ERR_ALREADY_STOPPED,
|
||||||
|
f"{FRIENDLY_DOMAIN[domain]} is already stopped",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_NOT_SUPPORTED, "Starting a cover is not supported"
|
ERR_NOT_SUPPORTED, f"Starting a {domain} is not supported"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
@ -2081,7 +2146,7 @@ class OpenCloseTrait(_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 == cover.DOMAIN:
|
if domain in COVER_VALVE_DOMAINS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return domain == binary_sensor.DOMAIN and device_class in (
|
return domain == binary_sensor.DOMAIN and device_class in (
|
||||||
@ -2116,6 +2181,17 @@ class OpenCloseTrait(_Trait):
|
|||||||
and features & CoverEntityFeature.CLOSE == 0
|
and features & CoverEntityFeature.CLOSE == 0
|
||||||
):
|
):
|
||||||
response["queryOnlyOpenClose"] = True
|
response["queryOnlyOpenClose"] = True
|
||||||
|
elif (
|
||||||
|
self.state.domain == valve.DOMAIN
|
||||||
|
and features & ValveEntityFeature.SET_POSITION == 0
|
||||||
|
):
|
||||||
|
response["discreteOnlyOpenClose"] = True
|
||||||
|
|
||||||
|
if (
|
||||||
|
features & ValveEntityFeature.OPEN == 0
|
||||||
|
and features & ValveEntityFeature.CLOSE == 0
|
||||||
|
):
|
||||||
|
response["queryOnlyOpenClose"] = True
|
||||||
|
|
||||||
if self.state.attributes.get(ATTR_ASSUMED_STATE):
|
if self.state.attributes.get(ATTR_ASSUMED_STATE):
|
||||||
response["commandOnlyOpenClose"] = True
|
response["commandOnlyOpenClose"] = True
|
||||||
@ -2134,17 +2210,17 @@ class OpenCloseTrait(_Trait):
|
|||||||
if self.state.attributes.get(ATTR_ASSUMED_STATE):
|
if self.state.attributes.get(ATTR_ASSUMED_STATE):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
if domain == cover.DOMAIN:
|
if domain in COVER_VALVE_DOMAINS:
|
||||||
if self.state.state == STATE_UNKNOWN:
|
if self.state.state == STATE_UNKNOWN:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_NOT_SUPPORTED, "Querying state is not supported"
|
ERR_NOT_SUPPORTED, "Querying state is not supported"
|
||||||
)
|
)
|
||||||
|
|
||||||
position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION)
|
position = self.state.attributes.get(COVER_VALVE_CURRENT_POSITION[domain])
|
||||||
|
|
||||||
if position is not None:
|
if position is not None:
|
||||||
response["openPercent"] = position
|
response["openPercent"] = position
|
||||||
elif self.state.state != cover.STATE_CLOSED:
|
elif self.state.state != COVER_VALVE_STATES[domain]["closed"]:
|
||||||
response["openPercent"] = 100
|
response["openPercent"] = 100
|
||||||
else:
|
else:
|
||||||
response["openPercent"] = 0
|
response["openPercent"] = 0
|
||||||
@ -2162,11 +2238,13 @@ class OpenCloseTrait(_Trait):
|
|||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if domain == cover.DOMAIN:
|
if domain in COVER_VALVE_DOMAINS:
|
||||||
svc_params = {ATTR_ENTITY_ID: self.state.entity_id}
|
svc_params = {ATTR_ENTITY_ID: self.state.entity_id}
|
||||||
should_verify = False
|
should_verify = False
|
||||||
if command == COMMAND_OPENCLOSE_RELATIVE:
|
if command == COMMAND_OPENCLOSE_RELATIVE:
|
||||||
position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION)
|
position = self.state.attributes.get(
|
||||||
|
COVER_VALVE_CURRENT_POSITION[domain]
|
||||||
|
)
|
||||||
if position is None:
|
if position is None:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_NOT_SUPPORTED,
|
ERR_NOT_SUPPORTED,
|
||||||
@ -2177,16 +2255,16 @@ class OpenCloseTrait(_Trait):
|
|||||||
position = params["openPercent"]
|
position = params["openPercent"]
|
||||||
|
|
||||||
if position == 0:
|
if position == 0:
|
||||||
service = cover.SERVICE_CLOSE_COVER
|
service = SERVICE_CLOSE_COVER_VALVE[domain]
|
||||||
should_verify = False
|
should_verify = False
|
||||||
elif position == 100:
|
elif position == 100:
|
||||||
service = cover.SERVICE_OPEN_COVER
|
service = SERVICE_OPEN_COVER_VALVE[domain]
|
||||||
should_verify = True
|
should_verify = True
|
||||||
elif features & CoverEntityFeature.SET_POSITION:
|
elif features & COVER_VALVE_SET_POSITION_FEATURE[domain]:
|
||||||
service = cover.SERVICE_SET_COVER_POSITION
|
service = SERVICE_SET_POSITION_COVER_VALVE[domain]
|
||||||
if position > 0:
|
if position > 0:
|
||||||
should_verify = True
|
should_verify = True
|
||||||
svc_params[cover.ATTR_POSITION] = position
|
svc_params[COVER_VALVE_POSITION[domain]] = position
|
||||||
else:
|
else:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_NOT_SUPPORTED, "No support for partial open close"
|
ERR_NOT_SUPPORTED, "No support for partial open close"
|
||||||
@ -2200,7 +2278,7 @@ class OpenCloseTrait(_Trait):
|
|||||||
_verify_pin_challenge(data, self.state, challenge)
|
_verify_pin_challenge(data, self.state, challenge)
|
||||||
|
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
cover.DOMAIN,
|
domain,
|
||||||
service,
|
service,
|
||||||
svc_params,
|
svc_params,
|
||||||
blocking=not self.config.should_report_state,
|
blocking=not self.config.should_report_state,
|
||||||
|
@ -103,6 +103,7 @@
|
|||||||
'sensor',
|
'sensor',
|
||||||
'switch',
|
'switch',
|
||||||
'vacuum',
|
'vacuum',
|
||||||
|
'valve',
|
||||||
'water_heater',
|
'water_heater',
|
||||||
]),
|
]),
|
||||||
'project_id': '1234',
|
'project_id': '1234',
|
||||||
|
@ -28,6 +28,7 @@ from homeassistant.components import (
|
|||||||
sensor,
|
sensor,
|
||||||
switch,
|
switch,
|
||||||
vacuum,
|
vacuum,
|
||||||
|
valve,
|
||||||
water_heater,
|
water_heater,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||||
@ -46,6 +47,7 @@ from homeassistant.components.media_player import (
|
|||||||
MediaType,
|
MediaType,
|
||||||
)
|
)
|
||||||
from homeassistant.components.vacuum import VacuumEntityFeature
|
from homeassistant.components.vacuum import VacuumEntityFeature
|
||||||
|
from homeassistant.components.valve import ValveEntityFeature
|
||||||
from homeassistant.components.water_heater import WaterHeaterEntityFeature
|
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 (
|
||||||
@ -650,6 +652,48 @@ async def test_startstop_cover_assumed(hass: HomeAssistant) -> None:
|
|||||||
assert stop_calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert stop_calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_startstop_valve(hass: HomeAssistant) -> None:
|
||||||
|
"""Test startStop trait support for valve domain."""
|
||||||
|
assert helpers.get_google_type(valve.DOMAIN, None) is not None
|
||||||
|
assert trait.StartStopTrait.supported(
|
||||||
|
valve.DOMAIN, ValveEntityFeature.STOP, None, None
|
||||||
|
)
|
||||||
|
assert not trait.StartStopTrait.supported(
|
||||||
|
valve.DOMAIN, ValveEntityFeature.SET_POSITION, None, None
|
||||||
|
)
|
||||||
|
|
||||||
|
state = State(
|
||||||
|
"valve.water",
|
||||||
|
valve.STATE_CLOSED,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: ValveEntityFeature.STOP},
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.StartStopTrait(
|
||||||
|
hass,
|
||||||
|
state,
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trt.sync_attributes() == {}
|
||||||
|
|
||||||
|
for state_value in (
|
||||||
|
valve.STATE_CLOSED,
|
||||||
|
valve.STATE_CLOSING,
|
||||||
|
valve.STATE_OPENING,
|
||||||
|
valve.STATE_OPEN,
|
||||||
|
):
|
||||||
|
state.state = state_value
|
||||||
|
assert trt.query_attributes() == {"isRunning": True}
|
||||||
|
|
||||||
|
stop_calls = async_mock_service(hass, valve.DOMAIN, valve.SERVICE_STOP_VALVE)
|
||||||
|
await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {"start": False}, {})
|
||||||
|
assert len(stop_calls) == 1
|
||||||
|
assert stop_calls[0].data == {ATTR_ENTITY_ID: "valve.water"}
|
||||||
|
|
||||||
|
with pytest.raises(SmartHomeError, match="Starting a valve is not supported"):
|
||||||
|
await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {"start": True}, {})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("supported_color_modes", [["hs"], ["rgb"], ["xy"]])
|
@pytest.mark.parametrize("supported_color_modes", [["hs"], ["rgb"], ["xy"]])
|
||||||
async def test_color_setting_color_light(
|
async def test_color_setting_color_light(
|
||||||
hass: HomeAssistant, supported_color_modes
|
hass: HomeAssistant, supported_color_modes
|
||||||
@ -2823,21 +2867,59 @@ async def test_traits_unknown_domains(
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test OpenClose trait support for cover domain."""
|
(
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
"domain",
|
||||||
assert trait.OpenCloseTrait.supported(
|
"set_position_service",
|
||||||
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
|
"close_service",
|
||||||
)
|
"open_service",
|
||||||
|
"set_position_feature",
|
||||||
|
"attr_position",
|
||||||
|
"attr_current_position",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
cover.DOMAIN,
|
||||||
|
cover.SERVICE_SET_COVER_POSITION,
|
||||||
|
cover.SERVICE_CLOSE_COVER,
|
||||||
|
cover.SERVICE_OPEN_COVER,
|
||||||
|
CoverEntityFeature.SET_POSITION,
|
||||||
|
cover.ATTR_POSITION,
|
||||||
|
cover.ATTR_CURRENT_POSITION,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
valve.DOMAIN,
|
||||||
|
valve.SERVICE_SET_VALVE_POSITION,
|
||||||
|
valve.SERVICE_CLOSE_VALVE,
|
||||||
|
valve.SERVICE_OPEN_VALVE,
|
||||||
|
ValveEntityFeature.SET_POSITION,
|
||||||
|
valve.ATTR_POSITION,
|
||||||
|
valve.ATTR_CURRENT_POSITION,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_openclose_cover_valve(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: str,
|
||||||
|
set_position_service: str,
|
||||||
|
close_service: str,
|
||||||
|
open_service: str,
|
||||||
|
set_position_feature: int,
|
||||||
|
attr_position: str,
|
||||||
|
attr_current_position: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test OpenClose trait support."""
|
||||||
|
assert helpers.get_google_type(domain, None) is not None
|
||||||
|
assert trait.OpenCloseTrait.supported(domain, set_position_service, None, None)
|
||||||
|
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
hass,
|
hass,
|
||||||
State(
|
State(
|
||||||
"cover.bla",
|
f"{domain}.bla",
|
||||||
cover.STATE_OPEN,
|
"open",
|
||||||
{
|
{
|
||||||
cover.ATTR_CURRENT_POSITION: 75,
|
attr_current_position: 75,
|
||||||
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
|
ATTR_SUPPORTED_FEATURES: set_position_feature,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BASIC_CONFIG,
|
BASIC_CONFIG,
|
||||||
@ -2846,34 +2928,74 @@ async def test_openclose_cover(hass: HomeAssistant) -> None:
|
|||||||
assert trt.sync_attributes() == {}
|
assert trt.sync_attributes() == {}
|
||||||
assert trt.query_attributes() == {"openPercent": 75}
|
assert trt.query_attributes() == {"openPercent": 75}
|
||||||
|
|
||||||
calls_set = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
|
calls_set = async_mock_service(hass, domain, set_position_service)
|
||||||
calls_open = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
|
calls_open = async_mock_service(hass, domain, open_service)
|
||||||
|
calls_close = async_mock_service(hass, domain, close_service)
|
||||||
|
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {})
|
||||||
await trt.execute(
|
await trt.execute(
|
||||||
trait.COMMAND_OPENCLOSE_RELATIVE, BASIC_DATA, {"openRelativePercent": 50}, {}
|
trait.COMMAND_OPENCLOSE_RELATIVE, BASIC_DATA, {"openRelativePercent": 50}, {}
|
||||||
)
|
)
|
||||||
assert len(calls_set) == 1
|
assert len(calls_set) == 1
|
||||||
assert calls_set[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50}
|
assert calls_set[0].data == {
|
||||||
|
ATTR_ENTITY_ID: f"{domain}.bla",
|
||||||
|
attr_position: 50,
|
||||||
|
}
|
||||||
|
calls_set.pop(0)
|
||||||
|
|
||||||
assert len(calls_open) == 1
|
assert len(calls_open) == 1
|
||||||
assert calls_open[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert calls_open[0].data == {ATTR_ENTITY_ID: f"{domain}.bla"}
|
||||||
|
calls_open.pop(0)
|
||||||
|
|
||||||
|
assert len(calls_close) == 0
|
||||||
|
|
||||||
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 0}, {})
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_OPENCLOSE_RELATIVE, BASIC_DATA, {"openRelativePercent": 0}, {}
|
||||||
|
)
|
||||||
|
assert len(calls_set) == 1
|
||||||
|
assert len(calls_close) == 1
|
||||||
|
assert calls_close[0].data == {ATTR_ENTITY_ID: f"{domain}.bla"}
|
||||||
|
assert len(calls_open) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_unknown_state(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test OpenClose trait support for cover domain with unknown state."""
|
("domain", "open_service", "set_position_feature", "open_feature"),
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
[
|
||||||
|
(
|
||||||
|
cover.DOMAIN,
|
||||||
|
cover.SERVICE_OPEN_COVER,
|
||||||
|
CoverEntityFeature.SET_POSITION,
|
||||||
|
CoverEntityFeature.OPEN,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
valve.DOMAIN,
|
||||||
|
valve.SERVICE_OPEN_VALVE,
|
||||||
|
ValveEntityFeature.SET_POSITION,
|
||||||
|
ValveEntityFeature.OPEN,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_openclose_cover_valve_unknown_state(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
open_service: str,
|
||||||
|
domain: str,
|
||||||
|
set_position_feature: int,
|
||||||
|
open_feature: int,
|
||||||
|
) -> None:
|
||||||
|
"""Test OpenClose trait support with unknown state."""
|
||||||
|
assert helpers.get_google_type(domain, None) is not None
|
||||||
assert trait.OpenCloseTrait.supported(
|
assert trait.OpenCloseTrait.supported(
|
||||||
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
|
cover.DOMAIN, set_position_feature, None, None
|
||||||
)
|
)
|
||||||
|
|
||||||
# No state
|
# No state
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
hass,
|
hass,
|
||||||
State(
|
State(
|
||||||
"cover.bla",
|
f"{domain}.bla",
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.OPEN},
|
{ATTR_SUPPORTED_FEATURES: open_feature},
|
||||||
),
|
),
|
||||||
BASIC_CONFIG,
|
BASIC_CONFIG,
|
||||||
)
|
)
|
||||||
@ -2883,30 +3005,51 @@ async def test_openclose_cover_unknown_state(hass: HomeAssistant) -> None:
|
|||||||
with pytest.raises(helpers.SmartHomeError):
|
with pytest.raises(helpers.SmartHomeError):
|
||||||
trt.query_attributes()
|
trt.query_attributes()
|
||||||
|
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
|
calls = async_mock_service(hass, domain, open_service)
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 100}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 100}, {})
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert calls[0].data == {ATTR_ENTITY_ID: f"{domain}.bla"}
|
||||||
|
|
||||||
with pytest.raises(helpers.SmartHomeError):
|
with pytest.raises(helpers.SmartHomeError):
|
||||||
trt.query_attributes()
|
trt.query_attributes()
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_assumed_state(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test OpenClose trait support for cover domain."""
|
("domain", "set_position_service", "set_position_feature", "state_open"),
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
[
|
||||||
assert trait.OpenCloseTrait.supported(
|
(
|
||||||
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
|
cover.DOMAIN,
|
||||||
)
|
cover.SERVICE_SET_COVER_POSITION,
|
||||||
|
CoverEntityFeature.SET_POSITION,
|
||||||
|
cover.STATE_OPEN,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
valve.DOMAIN,
|
||||||
|
valve.SERVICE_SET_VALVE_POSITION,
|
||||||
|
ValveEntityFeature.SET_POSITION,
|
||||||
|
valve.STATE_OPEN,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_openclose_cover_valve_assumed_state(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: str,
|
||||||
|
set_position_service: str,
|
||||||
|
set_position_feature: int,
|
||||||
|
state_open: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test OpenClose trait support."""
|
||||||
|
assert helpers.get_google_type(domain, None) is not None
|
||||||
|
assert trait.OpenCloseTrait.supported(domain, set_position_feature, None, None)
|
||||||
|
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
hass,
|
hass,
|
||||||
State(
|
State(
|
||||||
"cover.bla",
|
f"{domain}.bla",
|
||||||
cover.STATE_OPEN,
|
state_open,
|
||||||
{
|
{
|
||||||
ATTR_ASSUMED_STATE: True,
|
ATTR_ASSUMED_STATE: True,
|
||||||
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
|
ATTR_SUPPORTED_FEATURES: set_position_feature,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BASIC_CONFIG,
|
BASIC_CONFIG,
|
||||||
@ -2916,20 +3059,37 @@ async def test_openclose_cover_assumed_state(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert trt.query_attributes() == {}
|
assert trt.query_attributes() == {}
|
||||||
|
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
|
calls = async_mock_service(hass, domain, set_position_service)
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 40}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 40}, {})
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 40}
|
assert calls[0].data == {ATTR_ENTITY_ID: f"{domain}.bla", cover.ATTR_POSITION: 40}
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_query_only(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test OpenClose trait support for cover domain."""
|
("domain", "state_open"),
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
[
|
||||||
assert trait.OpenCloseTrait.supported(cover.DOMAIN, 0, None, None)
|
(
|
||||||
|
cover.DOMAIN,
|
||||||
|
cover.STATE_OPEN,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
valve.DOMAIN,
|
||||||
|
valve.STATE_OPEN,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_openclose_cover_valve_query_only(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: str,
|
||||||
|
state_open: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test OpenClose trait support."""
|
||||||
|
assert helpers.get_google_type(domain, None) is not None
|
||||||
|
assert trait.OpenCloseTrait.supported(domain, 0, None, None)
|
||||||
|
|
||||||
state = State(
|
state = State(
|
||||||
"cover.bla",
|
f"{domain}.bla",
|
||||||
cover.STATE_OPEN,
|
state_open,
|
||||||
)
|
)
|
||||||
|
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
@ -2945,21 +3105,57 @@ async def test_openclose_cover_query_only(hass: HomeAssistant) -> None:
|
|||||||
assert trt.query_attributes() == {"openPercent": 100}
|
assert trt.query_attributes() == {"openPercent": 100}
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_no_position(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test OpenClose trait support for cover domain."""
|
(
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
"domain",
|
||||||
|
"state_open",
|
||||||
|
"state_closed",
|
||||||
|
"supported_features",
|
||||||
|
"open_service",
|
||||||
|
"close_service",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
cover.DOMAIN,
|
||||||
|
cover.STATE_OPEN,
|
||||||
|
cover.STATE_CLOSED,
|
||||||
|
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE,
|
||||||
|
cover.SERVICE_OPEN_COVER,
|
||||||
|
cover.SERVICE_CLOSE_COVER,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
valve.DOMAIN,
|
||||||
|
valve.STATE_OPEN,
|
||||||
|
valve.STATE_CLOSED,
|
||||||
|
ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE,
|
||||||
|
valve.SERVICE_OPEN_VALVE,
|
||||||
|
valve.SERVICE_CLOSE_VALVE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_openclose_cover_valve_no_position(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: str,
|
||||||
|
state_open: str,
|
||||||
|
state_closed: str,
|
||||||
|
supported_features: int,
|
||||||
|
open_service: str,
|
||||||
|
close_service: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test OpenClose trait support."""
|
||||||
|
assert helpers.get_google_type(domain, None) is not None
|
||||||
assert trait.OpenCloseTrait.supported(
|
assert trait.OpenCloseTrait.supported(
|
||||||
cover.DOMAIN,
|
domain,
|
||||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE,
|
supported_features,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
state = State(
|
state = State(
|
||||||
"cover.bla",
|
f"{domain}.bla",
|
||||||
cover.STATE_OPEN,
|
state_open,
|
||||||
{
|
{
|
||||||
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE,
|
ATTR_SUPPORTED_FEATURES: supported_features,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2972,20 +3168,20 @@ async def test_openclose_cover_no_position(hass: HomeAssistant) -> None:
|
|||||||
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
||||||
assert trt.query_attributes() == {"openPercent": 100}
|
assert trt.query_attributes() == {"openPercent": 100}
|
||||||
|
|
||||||
state.state = cover.STATE_CLOSED
|
state.state = state_closed
|
||||||
|
|
||||||
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
||||||
assert trt.query_attributes() == {"openPercent": 0}
|
assert trt.query_attributes() == {"openPercent": 0}
|
||||||
|
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER)
|
calls = async_mock_service(hass, domain, close_service)
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 0}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 0}, {})
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert calls[0].data == {ATTR_ENTITY_ID: f"{domain}.bla"}
|
||||||
|
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
|
calls = async_mock_service(hass, domain, open_service)
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 100}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 100}, {})
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert calls[0].data == {ATTR_ENTITY_ID: f"{domain}.bla"}
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
SmartHomeError, match=r"Current position not know for relative command"
|
SmartHomeError, match=r"Current position not know for relative command"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user