Support asking covers to stop using google assistant (#43537)
This commit is contained in:
parent
f221bfae52
commit
aade4e63b8
5 changed files with 123 additions and 14 deletions
|
@ -104,6 +104,7 @@ ERR_UNSUPPORTED_INPUT = "unsupportedInput"
|
||||||
|
|
||||||
ERR_ALREADY_DISARMED = "alreadyDisarmed"
|
ERR_ALREADY_DISARMED = "alreadyDisarmed"
|
||||||
ERR_ALREADY_ARMED = "alreadyArmed"
|
ERR_ALREADY_ARMED = "alreadyArmed"
|
||||||
|
ERR_ALREADY_STOPPED = "alreadyStopped"
|
||||||
|
|
||||||
ERR_CHALLENGE_NEEDED = "challengeNeeded"
|
ERR_CHALLENGE_NEEDED = "challengeNeeded"
|
||||||
ERR_CHALLENGE_NOT_SETUP = "challengeFailedNotSetup"
|
ERR_CHALLENGE_NOT_SETUP = "challengeFailedNotSetup"
|
||||||
|
|
|
@ -67,6 +67,7 @@ from .const import (
|
||||||
CHALLENGE_PIN_NEEDED,
|
CHALLENGE_PIN_NEEDED,
|
||||||
ERR_ALREADY_ARMED,
|
ERR_ALREADY_ARMED,
|
||||||
ERR_ALREADY_DISARMED,
|
ERR_ALREADY_DISARMED,
|
||||||
|
ERR_ALREADY_STOPPED,
|
||||||
ERR_CHALLENGE_NOT_SETUP,
|
ERR_CHALLENGE_NOT_SETUP,
|
||||||
ERR_NOT_SUPPORTED,
|
ERR_NOT_SUPPORTED,
|
||||||
ERR_UNSUPPORTED_INPUT,
|
ERR_UNSUPPORTED_INPUT,
|
||||||
|
@ -564,24 +565,49 @@ class StartStopTrait(_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."""
|
||||||
return domain == vacuum.DOMAIN
|
if domain == vacuum.DOMAIN:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if domain == cover.DOMAIN and features & cover.SUPPORT_STOP:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
"""Return StartStop attributes for a sync request."""
|
"""Return StartStop attributes for a sync request."""
|
||||||
return {
|
domain = self.state.domain
|
||||||
"pausable": self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
if domain == vacuum.DOMAIN:
|
||||||
& vacuum.SUPPORT_PAUSE
|
return {
|
||||||
!= 0
|
"pausable": self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
}
|
& vacuum.SUPPORT_PAUSE
|
||||||
|
!= 0
|
||||||
|
}
|
||||||
|
if domain == cover.DOMAIN:
|
||||||
|
return {}
|
||||||
|
|
||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
"""Return StartStop query attributes."""
|
"""Return StartStop query attributes."""
|
||||||
return {
|
domain = self.state.domain
|
||||||
"isRunning": self.state.state == vacuum.STATE_CLEANING,
|
state = self.state.state
|
||||||
"isPaused": self.state.state == vacuum.STATE_PAUSED,
|
|
||||||
}
|
if domain == vacuum.DOMAIN:
|
||||||
|
return {
|
||||||
|
"isRunning": state == vacuum.STATE_CLEANING,
|
||||||
|
"isPaused": state == vacuum.STATE_PAUSED,
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain == cover.DOMAIN:
|
||||||
|
return {"isRunning": state in (cover.STATE_CLOSING, cover.STATE_OPENING)}
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
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)
|
||||||
|
|
||||||
|
async def _execute_vacuum(self, command, data, params, challenge):
|
||||||
"""Execute a StartStop command."""
|
"""Execute a StartStop command."""
|
||||||
if command == COMMAND_STARTSTOP:
|
if command == COMMAND_STARTSTOP:
|
||||||
if params["start"]:
|
if params["start"]:
|
||||||
|
@ -618,6 +644,31 @@ class StartStopTrait(_Trait):
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _execute_cover(self, command, data, params, challenge):
|
||||||
|
"""Execute a StartStop command."""
|
||||||
|
if command == COMMAND_STARTSTOP:
|
||||||
|
if params["start"] is False:
|
||||||
|
if self.state.state in (cover.STATE_CLOSING, cover.STATE_OPENING):
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
self.state.domain,
|
||||||
|
cover.SERVICE_STOP_COVER,
|
||||||
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
|
blocking=True,
|
||||||
|
context=data.context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise SmartHomeError(
|
||||||
|
ERR_ALREADY_STOPPED, "Cover is already stopped"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise SmartHomeError(
|
||||||
|
ERR_NOT_SUPPORTED, "Starting a cover is not supported"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise SmartHomeError(
|
||||||
|
ERR_NOT_SUPPORTED, f"Command {command} is not supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_trait
|
@register_trait
|
||||||
class TemperatureSettingTrait(_Trait):
|
class TemperatureSettingTrait(_Trait):
|
||||||
|
|
|
@ -136,14 +136,20 @@ DEMO_DEVICES = [
|
||||||
{
|
{
|
||||||
"id": "cover.living_room_window",
|
"id": "cover.living_room_window",
|
||||||
"name": {"name": "Living Room Window"},
|
"name": {"name": "Living Room Window"},
|
||||||
"traits": ["action.devices.traits.OpenClose"],
|
"traits": [
|
||||||
|
"action.devices.traits.StartStop",
|
||||||
|
"action.devices.traits.OpenClose",
|
||||||
|
],
|
||||||
"type": "action.devices.types.BLINDS",
|
"type": "action.devices.types.BLINDS",
|
||||||
"willReportState": False,
|
"willReportState": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cover.hall_window",
|
"id": "cover.hall_window",
|
||||||
"name": {"name": "Hall Window"},
|
"name": {"name": "Hall Window"},
|
||||||
"traits": ["action.devices.traits.OpenClose"],
|
"traits": [
|
||||||
|
"action.devices.traits.StartStop",
|
||||||
|
"action.devices.traits.OpenClose",
|
||||||
|
],
|
||||||
"type": "action.devices.types.BLINDS",
|
"type": "action.devices.types.BLINDS",
|
||||||
"willReportState": False,
|
"willReportState": False,
|
||||||
},
|
},
|
||||||
|
@ -157,7 +163,10 @@ DEMO_DEVICES = [
|
||||||
{
|
{
|
||||||
"id": "cover.kitchen_window",
|
"id": "cover.kitchen_window",
|
||||||
"name": {"name": "Kitchen Window"},
|
"name": {"name": "Kitchen Window"},
|
||||||
"traits": ["action.devices.traits.OpenClose"],
|
"traits": [
|
||||||
|
"action.devices.traits.StartStop",
|
||||||
|
"action.devices.traits.OpenClose",
|
||||||
|
],
|
||||||
"type": "action.devices.types.BLINDS",
|
"type": "action.devices.types.BLINDS",
|
||||||
"willReportState": False,
|
"willReportState": False,
|
||||||
},
|
},
|
||||||
|
|
|
@ -842,7 +842,10 @@ async def test_device_class_cover(hass, device_class, google_type):
|
||||||
"attributes": {"discreteOnlyOpenClose": True},
|
"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.StartStop",
|
||||||
|
"action.devices.traits.OpenClose",
|
||||||
|
],
|
||||||
"type": google_type,
|
"type": google_type,
|
||||||
"willReportState": False,
|
"willReportState": False,
|
||||||
}
|
}
|
||||||
|
|
|
@ -384,6 +384,51 @@ async def test_startstop_vacuum(hass):
|
||||||
assert unpause_calls[0].data == {ATTR_ENTITY_ID: "vacuum.bla"}
|
assert unpause_calls[0].data == {ATTR_ENTITY_ID: "vacuum.bla"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_startstop_covert(hass):
|
||||||
|
"""Test startStop trait support for vacuum domain."""
|
||||||
|
assert helpers.get_google_type(cover.DOMAIN, None) is not None
|
||||||
|
assert trait.StartStopTrait.supported(cover.DOMAIN, cover.SUPPORT_STOP, None)
|
||||||
|
|
||||||
|
state = State(
|
||||||
|
"cover.bla",
|
||||||
|
cover.STATE_CLOSED,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: cover.SUPPORT_STOP},
|
||||||
|
)
|
||||||
|
|
||||||
|
trt = trait.StartStopTrait(
|
||||||
|
hass,
|
||||||
|
state,
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trt.sync_attributes() == {}
|
||||||
|
|
||||||
|
for state_value in (cover.STATE_CLOSING, cover.STATE_OPENING):
|
||||||
|
state.state = state_value
|
||||||
|
assert trt.query_attributes() == {"isRunning": True}
|
||||||
|
|
||||||
|
stop_calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_STOP_COVER)
|
||||||
|
await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {"start": False}, {})
|
||||||
|
assert len(stop_calls) == 1
|
||||||
|
assert stop_calls[0].data == {ATTR_ENTITY_ID: "cover.bla"}
|
||||||
|
|
||||||
|
for state_value in (cover.STATE_CLOSED, cover.STATE_OPEN):
|
||||||
|
state.state = state_value
|
||||||
|
assert trt.query_attributes() == {"isRunning": False}
|
||||||
|
|
||||||
|
with pytest.raises(SmartHomeError, match="Cover is already stopped"):
|
||||||
|
await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {"start": False}, {})
|
||||||
|
|
||||||
|
with pytest.raises(SmartHomeError, match="Starting a cover is not supported"):
|
||||||
|
await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {"start": True}, {})
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
SmartHomeError,
|
||||||
|
match="Command action.devices.commands.PauseUnpause is not supported",
|
||||||
|
):
|
||||||
|
await trt.execute(trait.COMMAND_PAUSEUNPAUSE, BASIC_DATA, {"start": True}, {})
|
||||||
|
|
||||||
|
|
||||||
async def test_color_setting_color_light(hass):
|
async def test_color_setting_color_light(hass):
|
||||||
"""Test ColorSpectrum trait support for light domain."""
|
"""Test ColorSpectrum trait support for light domain."""
|
||||||
assert helpers.get_google_type(light.DOMAIN, None) is not None
|
assert helpers.get_google_type(light.DOMAIN, None) is not None
|
||||||
|
|
Loading…
Add table
Reference in a new issue