Support openRelativePercent for google assistant covers (#43336)
This commit is contained in:
parent
9b3c97345a
commit
e98f36e357
3 changed files with 106 additions and 22 deletions
|
@ -68,7 +68,6 @@ from .const import (
|
||||||
ERR_ALREADY_ARMED,
|
ERR_ALREADY_ARMED,
|
||||||
ERR_ALREADY_DISARMED,
|
ERR_ALREADY_DISARMED,
|
||||||
ERR_CHALLENGE_NOT_SETUP,
|
ERR_CHALLENGE_NOT_SETUP,
|
||||||
ERR_FUNCTION_NOT_SUPPORTED,
|
|
||||||
ERR_NOT_SUPPORTED,
|
ERR_NOT_SUPPORTED,
|
||||||
ERR_UNSUPPORTED_INPUT,
|
ERR_UNSUPPORTED_INPUT,
|
||||||
ERR_VALUE_OUT_OF_RANGE,
|
ERR_VALUE_OUT_OF_RANGE,
|
||||||
|
@ -120,6 +119,7 @@ COMMAND_INPUT = f"{PREFIX_COMMANDS}SetInput"
|
||||||
COMMAND_NEXT_INPUT = f"{PREFIX_COMMANDS}NextInput"
|
COMMAND_NEXT_INPUT = f"{PREFIX_COMMANDS}NextInput"
|
||||||
COMMAND_PREVIOUS_INPUT = f"{PREFIX_COMMANDS}PreviousInput"
|
COMMAND_PREVIOUS_INPUT = f"{PREFIX_COMMANDS}PreviousInput"
|
||||||
COMMAND_OPENCLOSE = f"{PREFIX_COMMANDS}OpenClose"
|
COMMAND_OPENCLOSE = f"{PREFIX_COMMANDS}OpenClose"
|
||||||
|
COMMAND_OPENCLOSE_RELATIVE = f"{PREFIX_COMMANDS}OpenCloseRelative"
|
||||||
COMMAND_SET_VOLUME = f"{PREFIX_COMMANDS}setVolume"
|
COMMAND_SET_VOLUME = f"{PREFIX_COMMANDS}setVolume"
|
||||||
COMMAND_VOLUME_RELATIVE = f"{PREFIX_COMMANDS}volumeRelative"
|
COMMAND_VOLUME_RELATIVE = f"{PREFIX_COMMANDS}volumeRelative"
|
||||||
COMMAND_MUTE = f"{PREFIX_COMMANDS}mute"
|
COMMAND_MUTE = f"{PREFIX_COMMANDS}mute"
|
||||||
|
@ -1519,7 +1519,7 @@ class OpenCloseTrait(_Trait):
|
||||||
)
|
)
|
||||||
|
|
||||||
name = TRAIT_OPENCLOSE
|
name = TRAIT_OPENCLOSE
|
||||||
commands = [COMMAND_OPENCLOSE]
|
commands = [COMMAND_OPENCLOSE, COMMAND_OPENCLOSE_RELATIVE]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported(domain, features, device_class):
|
def supported(domain, features, device_class):
|
||||||
|
@ -1543,9 +1543,20 @@ class OpenCloseTrait(_Trait):
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
"""Return opening direction."""
|
"""Return opening direction."""
|
||||||
response = {}
|
response = {}
|
||||||
|
features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if self.state.domain == binary_sensor.DOMAIN:
|
if self.state.domain == binary_sensor.DOMAIN:
|
||||||
response["queryOnlyOpenClose"] = True
|
response["queryOnlyOpenClose"] = True
|
||||||
response["discreteOnlyOpenClose"] = True
|
response["discreteOnlyOpenClose"] = True
|
||||||
|
elif self.state.domain == cover.DOMAIN:
|
||||||
|
if features & cover.SUPPORT_SET_POSITION == 0:
|
||||||
|
response["discreteOnlyOpenClose"] = True
|
||||||
|
|
||||||
|
if (
|
||||||
|
features & cover.SUPPORT_OPEN == 0
|
||||||
|
and features & cover.SUPPORT_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
|
||||||
|
@ -1590,26 +1601,36 @@ class OpenCloseTrait(_Trait):
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute an Open, close, Set position command."""
|
"""Execute an Open, close, Set position command."""
|
||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
|
features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if domain == cover.DOMAIN:
|
if domain == cover.DOMAIN:
|
||||||
svc_params = {ATTR_ENTITY_ID: self.state.entity_id}
|
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)
|
||||||
|
if position is None:
|
||||||
|
raise SmartHomeError(
|
||||||
|
ERR_NOT_SUPPORTED,
|
||||||
|
"Current position not know for relative command",
|
||||||
|
)
|
||||||
|
position = max(0, min(100, position + params["openRelativePercent"]))
|
||||||
|
else:
|
||||||
|
position = params["openPercent"]
|
||||||
|
|
||||||
if params["openPercent"] == 0:
|
if features & cover.SUPPORT_SET_POSITION:
|
||||||
|
service = cover.SERVICE_SET_COVER_POSITION
|
||||||
|
if position > 0:
|
||||||
|
should_verify = True
|
||||||
|
svc_params[cover.ATTR_POSITION] = position
|
||||||
|
elif position == 0:
|
||||||
service = cover.SERVICE_CLOSE_COVER
|
service = cover.SERVICE_CLOSE_COVER
|
||||||
should_verify = False
|
should_verify = False
|
||||||
elif params["openPercent"] == 100:
|
elif position == 100:
|
||||||
service = cover.SERVICE_OPEN_COVER
|
service = cover.SERVICE_OPEN_COVER
|
||||||
should_verify = True
|
should_verify = True
|
||||||
elif (
|
|
||||||
self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
|
||||||
& cover.SUPPORT_SET_POSITION
|
|
||||||
):
|
|
||||||
service = cover.SERVICE_SET_COVER_POSITION
|
|
||||||
should_verify = True
|
|
||||||
svc_params[cover.ATTR_POSITION] = params["openPercent"]
|
|
||||||
else:
|
else:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_FUNCTION_NOT_SUPPORTED, "Setting a position is not supported"
|
ERR_NOT_SUPPORTED, "No support for partial open close"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -839,7 +839,7 @@ async def test_device_class_cover(hass, device_class, google_type):
|
||||||
"agentUserId": "test-agent",
|
"agentUserId": "test-agent",
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"attributes": {},
|
"attributes": {"discreteOnlyOpenClose": True},
|
||||||
"id": "cover.demo_sensor",
|
"id": "cover.demo_sensor",
|
||||||
"name": {"name": "Demo Sensor"},
|
"name": {"name": "Demo Sensor"},
|
||||||
"traits": ["action.devices.traits.OpenClose"],
|
"traits": ["action.devices.traits.OpenClose"],
|
||||||
|
|
|
@ -1860,8 +1860,12 @@ async def test_openclose_cover(hass):
|
||||||
|
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
|
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {})
|
||||||
assert len(calls) == 1
|
await trt.execute(
|
||||||
|
trait.COMMAND_OPENCLOSE_RELATIVE, BASIC_DATA, {"openRelativePercent": 50}, {}
|
||||||
|
)
|
||||||
|
assert len(calls) == 2
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50}
|
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50}
|
||||||
|
assert calls[1].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 100}
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_unknown_state(hass):
|
async def test_openclose_cover_unknown_state(hass):
|
||||||
|
@ -1873,10 +1877,14 @@ async def test_openclose_cover_unknown_state(hass):
|
||||||
|
|
||||||
# No state
|
# No state
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
hass, State("cover.bla", STATE_UNKNOWN, {}), BASIC_CONFIG
|
hass,
|
||||||
|
State(
|
||||||
|
"cover.bla", STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: cover.SUPPORT_OPEN}
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert trt.sync_attributes() == {}
|
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
||||||
|
|
||||||
with pytest.raises(helpers.SmartHomeError):
|
with pytest.raises(helpers.SmartHomeError):
|
||||||
trt.query_attributes()
|
trt.query_attributes()
|
||||||
|
@ -1920,25 +1928,81 @@ async def test_openclose_cover_assumed_state(hass):
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 40}
|
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 40}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_openclose_cover_query_only(hass):
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
state = State(
|
||||||
|
"cover.bla",
|
||||||
|
cover.STATE_OPEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.OpenCloseTrait(
|
||||||
|
hass,
|
||||||
|
state,
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trt.sync_attributes() == {
|
||||||
|
"discreteOnlyOpenClose": True,
|
||||||
|
"queryOnlyOpenClose": True,
|
||||||
|
}
|
||||||
|
assert trt.query_attributes() == {"openPercent": 100}
|
||||||
|
|
||||||
|
|
||||||
async def test_openclose_cover_no_position(hass):
|
async def test_openclose_cover_no_position(hass):
|
||||||
"""Test OpenClose trait support for cover domain."""
|
"""Test OpenClose trait support for cover domain."""
|
||||||
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
||||||
assert trait.OpenCloseTrait.supported(
|
assert trait.OpenCloseTrait.supported(
|
||||||
cover.DOMAIN, cover.SUPPORT_SET_POSITION, None
|
cover.DOMAIN, cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE, None
|
||||||
|
)
|
||||||
|
|
||||||
|
state = State(
|
||||||
|
"cover.bla",
|
||||||
|
cover.STATE_OPEN,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
trt = trait.OpenCloseTrait(
|
trt = trait.OpenCloseTrait(
|
||||||
hass, State("cover.bla", cover.STATE_OPEN, {}), BASIC_CONFIG
|
hass,
|
||||||
|
state,
|
||||||
|
BASIC_CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert trt.sync_attributes() == {}
|
assert trt.sync_attributes() == {"discreteOnlyOpenClose": True}
|
||||||
assert trt.query_attributes() == {"openPercent": 100}
|
assert trt.query_attributes() == {"openPercent": 100}
|
||||||
|
|
||||||
|
state.state = cover.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, cover.DOMAIN, cover.SERVICE_CLOSE_COVER)
|
||||||
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: "cover.bla"}
|
||||||
|
|
||||||
|
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER)
|
||||||
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 100}, {})
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
SmartHomeError, match=r"Current position not know for relative command"
|
||||||
|
):
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_OPENCLOSE_RELATIVE,
|
||||||
|
BASIC_DATA,
|
||||||
|
{"openRelativePercent": 100},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(SmartHomeError, match=r"No support for partial open close"):
|
||||||
|
await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_class",
|
"device_class",
|
||||||
|
@ -1996,10 +2060,9 @@ async def test_openclose_cover_secure(hass, device_class):
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50}
|
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50}
|
||||||
|
|
||||||
# no challenge on close
|
# no challenge on close
|
||||||
calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER)
|
|
||||||
await trt.execute(trait.COMMAND_OPENCLOSE, PIN_DATA, {"openPercent": 0}, {})
|
await trt.execute(trait.COMMAND_OPENCLOSE, PIN_DATA, {"openPercent": 0}, {})
|
||||||
assert len(calls) == 1
|
assert len(calls) == 2
|
||||||
assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
assert calls[1].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 0}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue