Add valve platform support to google_assistant (#106139)

* Add valve platform to google_assistant

* Use constant for domains set
This commit is contained in:
Jan Bouwhuis 2023-12-23 16:46:25 +01:00 committed by GitHub
parent c126022d4f
commit e311a6835e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 355 additions and 76 deletions

View File

@ -22,6 +22,7 @@ from homeassistant.components import (
sensor,
switch,
vacuum,
valve,
water_heater,
)
@ -65,6 +66,7 @@ DEFAULT_EXPOSED_DOMAINS = [
"sensor",
"switch",
"vacuum",
"valve",
"water_heater",
]
@ -95,6 +97,7 @@ TYPE_THERMOSTAT = f"{PREFIX_TYPES}THERMOSTAT"
TYPE_TV = f"{PREFIX_TYPES}TV"
TYPE_WINDOW = f"{PREFIX_TYPES}WINDOW"
TYPE_VACUUM = f"{PREFIX_TYPES}VACUUM"
TYPE_VALVE = f"{PREFIX_TYPES}VALVE"
TYPE_WATERHEATER = f"{PREFIX_TYPES}WATERHEATER"
SERVICE_REQUEST_SYNC = "request_sync"
@ -150,6 +153,7 @@ DOMAIN_TO_GOOGLE_TYPES = {
sensor.DOMAIN: TYPE_SENSOR,
switch.DOMAIN: TYPE_SWITCH,
vacuum.DOMAIN: TYPE_VACUUM,
valve.DOMAIN: TYPE_VALVE,
water_heater.DOMAIN: TYPE_WATERHEATER,
}

View File

@ -29,6 +29,7 @@ from homeassistant.components import (
sensor,
switch,
vacuum,
valve,
water_heater,
)
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.media_player import MediaPlayerEntityFeature, MediaType
from homeassistant.components.vacuum import VacuumEntityFeature
from homeassistant.components.valve import ValveEntityFeature
from homeassistant.components.water_heater import WaterHeaterEntityFeature
from homeassistant.const import (
ATTR_ASSUMED_STATE,
@ -180,6 +182,57 @@ TRAITS: list[type[_Trait]] = []
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")
@ -796,6 +849,9 @@ class StartStopTrait(_Trait):
if domain == cover.DOMAIN and features & CoverEntityFeature.STOP:
return True
if domain == valve.DOMAIN and features & ValveEntityFeature.STOP:
return True
return False
def sync_attributes(self):
@ -807,7 +863,7 @@ class StartStopTrait(_Trait):
& VacuumEntityFeature.PAUSE
!= 0
}
if domain == cover.DOMAIN:
if domain in COVER_VALVE_DOMAINS:
return {}
def query_attributes(self):
@ -823,14 +879,16 @@ class StartStopTrait(_Trait):
if domain == cover.DOMAIN:
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):
"""Execute a StartStop command."""
domain = self.state.domain
if domain == vacuum.DOMAIN:
return await self._execute_vacuum(command, data, params, challenge)
if domain == cover.DOMAIN:
return await self._execute_cover(command, data, params, challenge)
if domain in COVER_VALVE_DOMAINS:
return await self._execute_cover_or_valve(command, data, params, challenge)
async def _execute_vacuum(self, command, data, params, challenge):
"""Execute a StartStop command."""
@ -869,28 +927,35 @@ class StartStopTrait(_Trait):
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."""
domain = self.state.domain
if command == COMMAND_STARTSTOP:
if params["start"] is False:
if self.state.state in (
cover.STATE_CLOSING,
cover.STATE_OPENING,
) or self.state.attributes.get(ATTR_ASSUMED_STATE):
if (
self.state.state
in (
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(
self.state.domain,
cover.SERVICE_STOP_COVER,
domain,
SERVICE_STOP_COVER_VALVE[domain],
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=not self.config.should_report_state,
context=data.context,
)
else:
raise SmartHomeError(
ERR_ALREADY_STOPPED, "Cover is already stopped"
ERR_ALREADY_STOPPED,
f"{FRIENDLY_DOMAIN[domain]} is already stopped",
)
else:
raise SmartHomeError(
ERR_NOT_SUPPORTED, "Starting a cover is not supported"
ERR_NOT_SUPPORTED, f"Starting a {domain} is not supported"
)
else:
raise SmartHomeError(
@ -2081,7 +2146,7 @@ class OpenCloseTrait(_Trait):
@staticmethod
def supported(domain, features, device_class, _):
"""Test if state is supported."""
if domain == cover.DOMAIN:
if domain in COVER_VALVE_DOMAINS:
return True
return domain == binary_sensor.DOMAIN and device_class in (
@ -2116,6 +2181,17 @@ class OpenCloseTrait(_Trait):
and features & CoverEntityFeature.CLOSE == 0
):
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):
response["commandOnlyOpenClose"] = True
@ -2134,17 +2210,17 @@ class OpenCloseTrait(_Trait):
if self.state.attributes.get(ATTR_ASSUMED_STATE):
return response
if domain == cover.DOMAIN:
if domain in COVER_VALVE_DOMAINS:
if self.state.state == STATE_UNKNOWN:
raise SmartHomeError(
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:
response["openPercent"] = position
elif self.state.state != cover.STATE_CLOSED:
elif self.state.state != COVER_VALVE_STATES[domain]["closed"]:
response["openPercent"] = 100
else:
response["openPercent"] = 0
@ -2162,11 +2238,13 @@ class OpenCloseTrait(_Trait):
domain = self.state.domain
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}
should_verify = False
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:
raise SmartHomeError(
ERR_NOT_SUPPORTED,
@ -2177,16 +2255,16 @@ class OpenCloseTrait(_Trait):
position = params["openPercent"]
if position == 0:
service = cover.SERVICE_CLOSE_COVER
service = SERVICE_CLOSE_COVER_VALVE[domain]
should_verify = False
elif position == 100:
service = cover.SERVICE_OPEN_COVER
service = SERVICE_OPEN_COVER_VALVE[domain]
should_verify = True
elif features & CoverEntityFeature.SET_POSITION:
service = cover.SERVICE_SET_COVER_POSITION
elif features & COVER_VALVE_SET_POSITION_FEATURE[domain]:
service = SERVICE_SET_POSITION_COVER_VALVE[domain]
if position > 0:
should_verify = True
svc_params[cover.ATTR_POSITION] = position
svc_params[COVER_VALVE_POSITION[domain]] = position
else:
raise SmartHomeError(
ERR_NOT_SUPPORTED, "No support for partial open close"
@ -2200,7 +2278,7 @@ class OpenCloseTrait(_Trait):
_verify_pin_challenge(data, self.state, challenge)
await self.hass.services.async_call(
cover.DOMAIN,
domain,
service,
svc_params,
blocking=not self.config.should_report_state,

View File

@ -103,6 +103,7 @@
'sensor',
'switch',
'vacuum',
'valve',
'water_heater',
]),
'project_id': '1234',

View File

@ -28,6 +28,7 @@ from homeassistant.components import (
sensor,
switch,
vacuum,
valve,
water_heater,
)
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
@ -46,6 +47,7 @@ from homeassistant.components.media_player import (
MediaType,
)
from homeassistant.components.vacuum import VacuumEntityFeature
from homeassistant.components.valve import ValveEntityFeature
from homeassistant.components.water_heater import WaterHeaterEntityFeature
from homeassistant.config import async_process_ha_core_config
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"}
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"]])
async def test_color_setting_color_light(
hass: HomeAssistant, supported_color_modes
@ -2823,21 +2867,59 @@ async def test_traits_unknown_domains(
caplog.clear()
async def test_openclose_cover(hass: HomeAssistant) -> None:
"""Test OpenClose trait support for cover domain."""
assert helpers.get_google_type(cover.DOMAIN, None) is not None
assert trait.OpenCloseTrait.supported(
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
)
@pytest.mark.parametrize(
(
"domain",
"set_position_service",
"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(
hass,
State(
"cover.bla",
cover.STATE_OPEN,
f"{domain}.bla",
"open",
{
cover.ATTR_CURRENT_POSITION: 75,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
attr_current_position: 75,
ATTR_SUPPORTED_FEATURES: set_position_feature,
},
),
BASIC_CONFIG,
@ -2846,34 +2928,74 @@ async def test_openclose_cover(hass: HomeAssistant) -> None:
assert trt.sync_attributes() == {}
assert trt.query_attributes() == {"openPercent": 75}
calls_set = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
calls_open = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
calls_set = async_mock_service(hass, domain, set_position_service)
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_RELATIVE, BASIC_DATA, {"openRelativePercent": 50}, {}
)
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 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:
"""Test OpenClose trait support for cover domain with unknown state."""
assert helpers.get_google_type(cover.DOMAIN, None) is not None
@pytest.mark.parametrize(
("domain", "open_service", "set_position_feature", "open_feature"),
[
(
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(
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
cover.DOMAIN, set_position_feature, None, None
)
# No state
trt = trait.OpenCloseTrait(
hass,
State(
"cover.bla",
f"{domain}.bla",
STATE_UNKNOWN,
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.OPEN},
{ATTR_SUPPORTED_FEATURES: open_feature},
),
BASIC_CONFIG,
)
@ -2883,30 +3005,51 @@ async def test_openclose_cover_unknown_state(hass: HomeAssistant) -> None:
with pytest.raises(helpers.SmartHomeError):
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}, {})
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):
trt.query_attributes()
async def test_openclose_cover_assumed_state(hass: HomeAssistant) -> None:
"""Test OpenClose trait support for cover domain."""
assert helpers.get_google_type(cover.DOMAIN, None) is not None
assert trait.OpenCloseTrait.supported(
cover.DOMAIN, CoverEntityFeature.SET_POSITION, None, None
)
@pytest.mark.parametrize(
("domain", "set_position_service", "set_position_feature", "state_open"),
[
(
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(
hass,
State(
"cover.bla",
cover.STATE_OPEN,
f"{domain}.bla",
state_open,
{
ATTR_ASSUMED_STATE: True,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
ATTR_SUPPORTED_FEATURES: set_position_feature,
},
),
BASIC_CONFIG,
@ -2916,20 +3059,37 @@ async def test_openclose_cover_assumed_state(hass: HomeAssistant) -> None:
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}, {})
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:
"""Test OpenClose trait support for cover domain."""
assert helpers.get_google_type(cover.DOMAIN, None) is not None
assert trait.OpenCloseTrait.supported(cover.DOMAIN, 0, None, None)
@pytest.mark.parametrize(
("domain", "state_open"),
[
(
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(
"cover.bla",
cover.STATE_OPEN,
f"{domain}.bla",
state_open,
)
trt = trait.OpenCloseTrait(
@ -2945,21 +3105,57 @@ async def test_openclose_cover_query_only(hass: HomeAssistant) -> None:
assert trt.query_attributes() == {"openPercent": 100}
async def test_openclose_cover_no_position(hass: HomeAssistant) -> None:
"""Test OpenClose trait support for cover domain."""
assert helpers.get_google_type(cover.DOMAIN, None) is not None
@pytest.mark.parametrize(
(
"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(
cover.DOMAIN,
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE,
domain,
supported_features,
None,
None,
)
state = State(
"cover.bla",
cover.STATE_OPEN,
f"{domain}.bla",
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.query_attributes() == {"openPercent": 100}
state.state = cover.STATE_CLOSED
state.state = state_closed
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
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}, {})
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}, {})
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(
SmartHomeError, match=r"Current position not know for relative command"