Add new intents for cover, valve, vacuum, and media player (#110757)
* Add valve to HassTurnOn/Off * Add set position for valves * Add set position to covers * Add HassTurnOn/Off for vacuums * Add media player intents * Split out vacuum intents * Address comments * Extra testpull/108237/head
parent
015f9cdb35
commit
ec4bd9a421
|
@ -1,9 +1,16 @@
|
|||
"""Intents for the cover integration."""
|
||||
from homeassistant.const import SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
SERVICE_CLOSE_COVER,
|
||||
SERVICE_OPEN_COVER,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from . import DOMAIN
|
||||
from . import ATTR_POSITION, DOMAIN
|
||||
|
||||
INTENT_OPEN_COVER = "HassOpenCover"
|
||||
INTENT_CLOSE_COVER = "HassCloseCover"
|
||||
|
@ -23,3 +30,12 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
|
|||
INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}"
|
||||
),
|
||||
)
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_SET_POSITION,
|
||||
DOMAIN,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
extra_slots={ATTR_POSITION: vol.All(vol.Range(min=0, max=100))},
|
||||
),
|
||||
)
|
||||
|
|
|
@ -20,6 +20,11 @@ from homeassistant.components.lock import (
|
|||
SERVICE_LOCK,
|
||||
SERVICE_UNLOCK,
|
||||
)
|
||||
from homeassistant.components.valve import (
|
||||
DOMAIN as VALVE_DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TOGGLE,
|
||||
|
@ -82,10 +87,10 @@ class IntentPlatformProtocol(Protocol):
|
|||
|
||||
|
||||
class OnOffIntentHandler(intent.ServiceIntentHandler):
|
||||
"""Intent handler for on/off that handles covers too."""
|
||||
"""Intent handler for on/off that also supports covers, valves, locks, etc."""
|
||||
|
||||
async def async_call_service(self, intent_obj: intent.Intent, state: State) -> None:
|
||||
"""Call service on entity with special case for covers."""
|
||||
"""Call service on entity with handling for special cases."""
|
||||
hass = intent_obj.hass
|
||||
|
||||
if state.domain == COVER_DOMAIN:
|
||||
|
@ -130,6 +135,27 @@ class OnOffIntentHandler(intent.ServiceIntentHandler):
|
|||
)
|
||||
return
|
||||
|
||||
if state.domain == VALVE_DOMAIN:
|
||||
# on = opened
|
||||
# off = closed
|
||||
if self.service == SERVICE_TURN_ON:
|
||||
service_name = SERVICE_OPEN_VALVE
|
||||
else:
|
||||
service_name = SERVICE_CLOSE_VALVE
|
||||
|
||||
await self._run_then_background(
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(
|
||||
VALVE_DOMAIN,
|
||||
service_name,
|
||||
{ATTR_ENTITY_ID: state.entity_id},
|
||||
context=intent_obj.context,
|
||||
blocking=True,
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not hass.services.has_service(state.domain, self.service):
|
||||
raise intent.IntentHandleError(
|
||||
f"Service {self.service} does not support entity {state.entity_id}"
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"""Intents for the media_player integration."""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
SERVICE_VOLUME_SET,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from . import ATTR_MEDIA_VOLUME_LEVEL, DOMAIN
|
||||
|
||||
INTENT_MEDIA_PAUSE = "HassMediaPause"
|
||||
INTENT_MEDIA_UNPAUSE = "HassMediaUnpause"
|
||||
INTENT_MEDIA_NEXT = "HassMediaNext"
|
||||
INTENT_SET_VOLUME = "HassSetVolume"
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the media_player intents."""
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(INTENT_MEDIA_UNPAUSE, DOMAIN, SERVICE_MEDIA_PLAY),
|
||||
)
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(INTENT_MEDIA_PAUSE, DOMAIN, SERVICE_MEDIA_PAUSE),
|
||||
)
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_MEDIA_NEXT, DOMAIN, SERVICE_MEDIA_NEXT_TRACK
|
||||
),
|
||||
)
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_SET_VOLUME,
|
||||
DOMAIN,
|
||||
SERVICE_VOLUME_SET,
|
||||
extra_slots={
|
||||
ATTR_MEDIA_VOLUME_LEVEL: vol.All(
|
||||
vol.Range(min=0, max=100), lambda val: val / 100
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
"""Intents for the vacuum integration."""
|
||||
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from . import DOMAIN, SERVICE_RETURN_TO_BASE, SERVICE_START
|
||||
|
||||
INTENT_VACUUM_START = "HassVacuumStart"
|
||||
INTENT_VACUUM_RETURN_TO_BASE = "HassVacuumReturnToBase"
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the vacuum intents."""
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(INTENT_VACUUM_START, DOMAIN, SERVICE_START),
|
||||
)
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_VACUUM_RETURN_TO_BASE, DOMAIN, SERVICE_RETURN_TO_BASE
|
||||
),
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
"""Intents for the valve integration."""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import SERVICE_SET_VALVE_POSITION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from . import ATTR_POSITION, DOMAIN
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the valve intents."""
|
||||
intent.async_register(
|
||||
hass,
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_SET_POSITION,
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
extra_slots={ATTR_POSITION: vol.All(vol.Range(min=0, max=100))},
|
||||
),
|
||||
)
|
|
@ -7,6 +7,7 @@ from collections.abc import Collection, Coroutine, Iterable
|
|||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import cached_property
|
||||
import logging
|
||||
from typing import Any, TypeVar
|
||||
|
||||
|
@ -33,6 +34,7 @@ INTENT_TURN_ON = "HassTurnOn"
|
|||
INTENT_TOGGLE = "HassToggle"
|
||||
INTENT_GET_STATE = "HassGetState"
|
||||
INTENT_NEVERMIND = "HassNevermind"
|
||||
INTENT_SET_POSITION = "HassSetPosition"
|
||||
|
||||
SLOT_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
@ -347,7 +349,6 @@ class IntentHandler:
|
|||
|
||||
intent_type: str | None = None
|
||||
slot_schema: vol.Schema | None = None
|
||||
_slot_schema: vol.Schema | None = None
|
||||
platforms: Iterable[str] | None = []
|
||||
|
||||
@callback
|
||||
|
@ -361,17 +362,20 @@ class IntentHandler:
|
|||
if self.slot_schema is None:
|
||||
return slots
|
||||
|
||||
if self._slot_schema is None:
|
||||
self._slot_schema = vol.Schema(
|
||||
{
|
||||
key: SLOT_SCHEMA.extend({"value": validator})
|
||||
for key, validator in self.slot_schema.items()
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
return self._slot_schema(slots) # type: ignore[no-any-return]
|
||||
|
||||
@cached_property
|
||||
def _slot_schema(self) -> vol.Schema:
|
||||
"""Create validation schema for slots."""
|
||||
assert self.slot_schema is not None
|
||||
return vol.Schema(
|
||||
{
|
||||
key: SLOT_SCHEMA.extend({"value": validator})
|
||||
for key, validator in self.slot_schema.items()
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
async def async_handle(self, intent_obj: Intent) -> IntentResponse:
|
||||
"""Handle the intent."""
|
||||
raise NotImplementedError()
|
||||
|
@ -398,13 +402,44 @@ class ServiceIntentHandler(IntentHandler):
|
|||
service_timeout: float = 0.2
|
||||
|
||||
def __init__(
|
||||
self, intent_type: str, domain: str, service: str, speech: str | None = None
|
||||
self,
|
||||
intent_type: str,
|
||||
domain: str,
|
||||
service: str,
|
||||
speech: str | None = None,
|
||||
extra_slots: dict[str, vol.Schema] | None = None,
|
||||
) -> None:
|
||||
"""Create Service Intent Handler."""
|
||||
self.intent_type = intent_type
|
||||
self.domain = domain
|
||||
self.service = service
|
||||
self.speech = speech
|
||||
self.extra_slots = extra_slots
|
||||
|
||||
@cached_property
|
||||
def _slot_schema(self) -> vol.Schema:
|
||||
"""Create validation schema for slots (with extra required slots)."""
|
||||
if self.slot_schema is None:
|
||||
raise ValueError("Slot schema is not defined")
|
||||
|
||||
if self.extra_slots:
|
||||
slot_schema = {
|
||||
**self.slot_schema,
|
||||
**{
|
||||
vol.Required(key): schema
|
||||
for key, schema in self.extra_slots.items()
|
||||
},
|
||||
}
|
||||
else:
|
||||
slot_schema = self.slot_schema
|
||||
|
||||
return vol.Schema(
|
||||
{
|
||||
key: SLOT_SCHEMA.extend({"value": validator})
|
||||
for key, validator in slot_schema.items()
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
async def async_handle(self, intent_obj: Intent) -> IntentResponse:
|
||||
"""Handle the hass intent."""
|
||||
|
@ -467,6 +502,9 @@ class ServiceIntentHandler(IntentHandler):
|
|||
area=area_name or area_id,
|
||||
)
|
||||
|
||||
# Update intent slots to include any transformations done by the schemas
|
||||
intent_obj.slots = slots
|
||||
|
||||
response = await self.async_handle_states(intent_obj, states, area)
|
||||
|
||||
# Make the matched states available in the response
|
||||
|
@ -539,12 +577,19 @@ class ServiceIntentHandler(IntentHandler):
|
|||
async def async_call_service(self, intent_obj: Intent, state: State) -> None:
|
||||
"""Call service on entity."""
|
||||
hass = intent_obj.hass
|
||||
|
||||
service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id}
|
||||
if self.extra_slots:
|
||||
service_data.update(
|
||||
{key: intent_obj.slots[key]["value"] for key in self.extra_slots}
|
||||
)
|
||||
|
||||
await self._run_then_background(
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(
|
||||
self.domain,
|
||||
self.service,
|
||||
{ATTR_ENTITY_ID: state.entity_id},
|
||||
service_data,
|
||||
context=intent_obj.context,
|
||||
blocking=True,
|
||||
),
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
"""The tests for the cover platform."""
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_CURRENT_POSITION,
|
||||
DOMAIN,
|
||||
SERVICE_CLOSE_COVER,
|
||||
SERVICE_OPEN_COVER,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
intent as cover_intent,
|
||||
)
|
||||
from homeassistant.const import STATE_CLOSED, STATE_OPEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
|
@ -14,37 +19,66 @@ async def test_open_cover_intent(hass: HomeAssistant) -> None:
|
|||
"""Test HassOpenCover intent."""
|
||||
await cover_intent.async_setup_intents(hass)
|
||||
|
||||
hass.states.async_set("cover.garage_door", "closed")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
|
||||
hass.states.async_set(f"{DOMAIN}.garage_door", STATE_CLOSED)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_OPEN_COVER)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
|
||||
hass, "test", cover_intent.INTENT_OPEN_COVER, {"name": {"value": "garage door"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Opened garage door"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "cover"
|
||||
assert call.service == "open_cover"
|
||||
assert call.data == {"entity_id": "cover.garage_door"}
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_OPEN_COVER
|
||||
assert call.data == {"entity_id": f"{DOMAIN}.garage_door"}
|
||||
|
||||
|
||||
async def test_close_cover_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassCloseCover intent."""
|
||||
await cover_intent.async_setup_intents(hass)
|
||||
|
||||
hass.states.async_set("cover.garage_door", "open")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER)
|
||||
hass.states.async_set(f"{DOMAIN}.garage_door", STATE_OPEN)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_CLOSE_COVER)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassCloseCover", {"name": {"value": "garage door"}}
|
||||
hass,
|
||||
"test",
|
||||
cover_intent.INTENT_CLOSE_COVER,
|
||||
{"name": {"value": "garage door"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Closed garage door"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "cover"
|
||||
assert call.service == "close_cover"
|
||||
assert call.data == {"entity_id": "cover.garage_door"}
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_CLOSE_COVER
|
||||
assert call.data == {"entity_id": f"{DOMAIN}.garage_door"}
|
||||
|
||||
|
||||
async def test_set_cover_position(hass: HomeAssistant) -> None:
|
||||
"""Test HassSetPosition intent for covers."""
|
||||
await cover_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_cover"
|
||||
hass.states.async_set(
|
||||
entity_id, STATE_CLOSED, attributes={ATTR_CURRENT_POSITION: 0}
|
||||
)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_COVER_POSITION)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_SET_POSITION,
|
||||
{"name": {"value": "test cover"}, "position": {"value": 50}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_SET_COVER_POSITION
|
||||
assert call.data == {"entity_id": entity_id, "position": 50}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
"""The tests for the media_player platform."""
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
DOMAIN,
|
||||
SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
SERVICE_VOLUME_SET,
|
||||
intent as media_player_intent,
|
||||
)
|
||||
from homeassistant.const import STATE_IDLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_pause_media_player_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassMediaPause intent for media players."""
|
||||
await media_player_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_media_player"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_PAUSE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_MEDIA_PAUSE,
|
||||
{"name": {"value": "test media player"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_MEDIA_PAUSE
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_unpause_media_player_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassMediaUnpause intent for media players."""
|
||||
await media_player_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_media_player"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_PLAY)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_MEDIA_UNPAUSE,
|
||||
{"name": {"value": "test media player"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_MEDIA_PLAY
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_next_media_player_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassMediaNext intent for media players."""
|
||||
await media_player_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_media_player"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_NEXT_TRACK)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_MEDIA_NEXT,
|
||||
{"name": {"value": "test media player"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_MEDIA_NEXT_TRACK
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_volume_media_player_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassSetVolume intent for media players."""
|
||||
await media_player_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_media_player"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_VOLUME_SET)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_SET_VOLUME,
|
||||
{"name": {"value": "test media player"}, "volume_level": {"value": 50}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_VOLUME_SET
|
||||
assert call.data == {"entity_id": entity_id, "volume_level": 0.5}
|
|
@ -0,0 +1,61 @@
|
|||
"""The tests for the vacuum platform."""
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
DOMAIN,
|
||||
SERVICE_RETURN_TO_BASE,
|
||||
SERVICE_START,
|
||||
intent as vacuum_intent,
|
||||
)
|
||||
from homeassistant.const import STATE_IDLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_start_vacuum_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassTurnOn intent for vacuums."""
|
||||
await vacuum_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_vacuum"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_START)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
vacuum_intent.INTENT_VACUUM_START,
|
||||
{"name": {"value": "test vacuum"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_START
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_stop_vacuum_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassTurnOff intent for vacuums."""
|
||||
await vacuum_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_vacuum"
|
||||
hass.states.async_set(entity_id, STATE_IDLE)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_RETURN_TO_BASE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
vacuum_intent.INTENT_VACUUM_RETURN_TO_BASE,
|
||||
{"name": {"value": "test vacuum"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_RETURN_TO_BASE
|
||||
assert call.data == {"entity_id": entity_id}
|
|
@ -0,0 +1,84 @@
|
|||
"""The tests for the valve platform."""
|
||||
|
||||
from homeassistant.components.valve import (
|
||||
ATTR_CURRENT_POSITION,
|
||||
DOMAIN,
|
||||
SERVICE_CLOSE_VALVE,
|
||||
SERVICE_OPEN_VALVE,
|
||||
SERVICE_SET_VALVE_POSITION,
|
||||
intent as valve_intent,
|
||||
)
|
||||
from homeassistant.const import STATE_CLOSED, STATE_OPEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_open_valve_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassTurnOn intent for valves."""
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
entity_id = f"{DOMAIN}.test_valve"
|
||||
hass.states.async_set(entity_id, STATE_CLOSED)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_OPEN_VALVE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", intent.INTENT_TURN_ON, {"name": {"value": "test valve"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_OPEN_VALVE
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_close_valve_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassTurnOff intent for valves."""
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
entity_id = f"{DOMAIN}.test_valve"
|
||||
hass.states.async_set(entity_id, STATE_OPEN)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_CLOSE_VALVE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", intent.INTENT_TURN_OFF, {"name": {"value": "test valve"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_CLOSE_VALVE
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
|
||||
async def test_set_valve_position(hass: HomeAssistant) -> None:
|
||||
"""Test HassSetPosition intent for valves."""
|
||||
await valve_intent.async_setup_intents(hass)
|
||||
|
||||
entity_id = f"{DOMAIN}.test_valve"
|
||||
hass.states.async_set(
|
||||
entity_id, STATE_CLOSED, attributes={ATTR_CURRENT_POSITION: 0}
|
||||
)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_VALVE_POSITION)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_SET_POSITION,
|
||||
{"name": {"value": "test valve"}, "position": {"value": 50}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
assert call.service == SERVICE_SET_VALVE_POSITION
|
||||
assert call.data == {"entity_id": entity_id, "position": 50}
|
|
@ -1,4 +1,5 @@
|
|||
"""Tests for the intent helpers."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
@ -176,6 +177,14 @@ def test_async_validate_slots() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_async_validate_slots_no_schema() -> None:
|
||||
"""Test async_validate_slots of IntentHandler with no schema."""
|
||||
handler1 = MockIntentHandler(None)
|
||||
assert handler1.async_validate_slots({"name": {"value": "kitchen"}}) == {
|
||||
"name": {"value": "kitchen"}
|
||||
}
|
||||
|
||||
|
||||
async def test_cant_turn_on_lock(hass: HomeAssistant) -> None:
|
||||
"""Test that we can't turn on entities that don't support it."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
|
|
Loading…
Reference in New Issue