mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Don't wait for Google Assistant service calls when reporting state (#59832)
* Don't wait for Google Assistant service calls when reporting state * Update tests * Add test
This commit is contained in:
parent
d980ca7e04
commit
814a742518
@ -17,6 +17,8 @@ from .const import (
|
|||||||
from .error import SmartHomeError
|
from .error import SmartHomeError
|
||||||
from .helpers import GoogleEntity, RequestData, async_get_entities
|
from .helpers import GoogleEntity, RequestData, async_get_entities
|
||||||
|
|
||||||
|
EXECUTE_LIMIT = 2 # Wait 2 seconds for execute to finish
|
||||||
|
|
||||||
HANDLERS = Registry()
|
HANDLERS = Registry()
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -207,16 +209,23 @@ async def handle_devices_execute(hass, data, payload):
|
|||||||
entities[entity_id] = GoogleEntity(hass, data.config, state)
|
entities[entity_id] = GoogleEntity(hass, data.config, state)
|
||||||
executions[entity_id] = [execution]
|
executions[entity_id] = [execution]
|
||||||
|
|
||||||
execute_results = await asyncio.gather(
|
try:
|
||||||
*(
|
execute_results = await asyncio.wait_for(
|
||||||
_entity_execute(entities[entity_id], data, execution)
|
asyncio.shield(
|
||||||
for entity_id, execution in executions.items()
|
asyncio.gather(
|
||||||
|
*(
|
||||||
|
_entity_execute(entities[entity_id], data, execution)
|
||||||
|
for entity_id, execution in executions.items()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
EXECUTE_LIMIT,
|
||||||
)
|
)
|
||||||
)
|
for entity_id, result in zip(executions, execute_results):
|
||||||
|
if result is not None:
|
||||||
for entity_id, result in zip(executions, execute_results):
|
results[entity_id] = result
|
||||||
if result is not None:
|
except asyncio.TimeoutError:
|
||||||
results[entity_id] = result
|
pass
|
||||||
|
|
||||||
final_results = list(results.values())
|
final_results = list(results.values())
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ class BrightnessTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
light.ATTR_BRIGHTNESS_PCT: params["brightness"],
|
light.ATTR_BRIGHTNESS_PCT: params["brightness"],
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ class OnOffTrait(_Trait):
|
|||||||
service_domain,
|
service_domain,
|
||||||
service,
|
service,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -464,7 +464,7 @@ class ColorSettingTrait(_Trait):
|
|||||||
light.DOMAIN,
|
light.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_COLOR_TEMP: temp},
|
{ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_COLOR_TEMP: temp},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -479,7 +479,7 @@ class ColorSettingTrait(_Trait):
|
|||||||
light.DOMAIN,
|
light.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_HS_COLOR: color},
|
{ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_HS_COLOR: color},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -496,7 +496,7 @@ class ColorSettingTrait(_Trait):
|
|||||||
light.ATTR_HS_COLOR: [color["hue"], saturation],
|
light.ATTR_HS_COLOR: [color["hue"], saturation],
|
||||||
light.ATTR_BRIGHTNESS: brightness,
|
light.ATTR_BRIGHTNESS: brightness,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -536,7 +536,8 @@ class SceneTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
service,
|
service,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=self.state.domain not in (button.DOMAIN, script.DOMAIN),
|
blocking=(not self.config.should_report_state)
|
||||||
|
and self.state.domain not in (button.DOMAIN, script.DOMAIN),
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -570,7 +571,7 @@ class DockTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_RETURN_TO_BASE,
|
vacuum.SERVICE_RETURN_TO_BASE,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -610,7 +611,7 @@ class LocatorTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_LOCATE,
|
vacuum.SERVICE_LOCATE,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -731,7 +732,7 @@ class StartStopTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_START,
|
vacuum.SERVICE_START,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -739,7 +740,7 @@ class StartStopTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_STOP,
|
vacuum.SERVICE_STOP,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
elif command == COMMAND_PAUSEUNPAUSE:
|
elif command == COMMAND_PAUSEUNPAUSE:
|
||||||
@ -748,7 +749,7 @@ class StartStopTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_PAUSE,
|
vacuum.SERVICE_PAUSE,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -756,7 +757,7 @@ class StartStopTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
vacuum.SERVICE_START,
|
vacuum.SERVICE_START,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -776,7 +777,7 @@ class StartStopTrait(_Trait):
|
|||||||
self.state.domain,
|
self.state.domain,
|
||||||
cover.SERVICE_STOP_COVER,
|
cover.SERVICE_STOP_COVER,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -993,7 +994,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
climate.DOMAIN,
|
climate.DOMAIN,
|
||||||
climate.SERVICE_SET_TEMPERATURE,
|
climate.SERVICE_SET_TEMPERATURE,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id, ATTR_TEMPERATURE: temp},
|
{ATTR_ENTITY_ID: self.state.entity_id, ATTR_TEMPERATURE: temp},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1041,7 +1042,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
climate.DOMAIN,
|
climate.DOMAIN,
|
||||||
climate.SERVICE_SET_TEMPERATURE,
|
climate.SERVICE_SET_TEMPERATURE,
|
||||||
svc_data,
|
svc_data,
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1054,7 +1055,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
climate.DOMAIN,
|
climate.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1064,7 +1065,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
climate.DOMAIN,
|
climate.DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1077,7 +1078,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
climate.ATTR_PRESET_MODE: self.google_to_preset[target_mode],
|
climate.ATTR_PRESET_MODE: self.google_to_preset[target_mode],
|
||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1089,7 +1090,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
climate.ATTR_HVAC_MODE: self.google_to_hvac[target_mode],
|
climate.ATTR_HVAC_MODE: self.google_to_hvac[target_mode],
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1170,7 +1171,7 @@ class HumiditySettingTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
humidifier.ATTR_HUMIDITY: params["humidity"],
|
humidifier.ATTR_HUMIDITY: params["humidity"],
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1219,7 +1220,7 @@ class LockUnlockTrait(_Trait):
|
|||||||
lock.DOMAIN,
|
lock.DOMAIN,
|
||||||
service,
|
service,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1342,7 +1343,7 @@ class ArmDisArmTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
ATTR_CODE: data.config.secure_devices_pin,
|
ATTR_CODE: data.config.secure_devices_pin,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1430,7 +1431,7 @@ class FanSpeedTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
climate.ATTR_FAN_MODE: params["fanSpeed"],
|
climate.ATTR_FAN_MODE: params["fanSpeed"],
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1442,7 +1443,7 @@ class FanSpeedTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
fan.ATTR_PERCENTAGE: params["fanSpeedPercent"],
|
fan.ATTR_PERCENTAGE: params["fanSpeedPercent"],
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1458,7 +1459,7 @@ class FanSpeedTrait(_Trait):
|
|||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_SET_DIRECTION,
|
fan.SERVICE_SET_DIRECTION,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_DIRECTION: direction},
|
{ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_DIRECTION: direction},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1599,7 +1600,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
fan.ATTR_PRESET_MODE: preset_mode,
|
fan.ATTR_PRESET_MODE: preset_mode,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1613,7 +1614,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
input_select.ATTR_OPTION: option,
|
input_select.ATTR_OPTION: option,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1627,7 +1628,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
select.ATTR_OPTION: option,
|
select.ATTR_OPTION: option,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1641,7 +1642,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_MODE: requested_mode,
|
ATTR_MODE: requested_mode,
|
||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1655,7 +1656,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
light.ATTR_EFFECT: requested_effect,
|
light.ATTR_EFFECT: requested_effect,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -1670,7 +1671,7 @@ class ModesTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
media_player.ATTR_SOUND_MODE: sound_mode,
|
media_player.ATTR_SOUND_MODE: sound_mode,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1744,7 +1745,7 @@ class InputSelectorTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
media_player.ATTR_INPUT_SOURCE: requested_source,
|
media_player.ATTR_INPUT_SOURCE: requested_source,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1888,7 +1889,11 @@ 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, service, svc_params, blocking=True, context=data.context
|
cover.DOMAIN,
|
||||||
|
service,
|
||||||
|
svc_params,
|
||||||
|
blocking=not self.config.should_report_state,
|
||||||
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1950,7 +1955,7 @@ class VolumeTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
media_player.ATTR_MEDIA_VOLUME_LEVEL: level,
|
media_player.ATTR_MEDIA_VOLUME_LEVEL: level,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1986,7 +1991,7 @@ class VolumeTrait(_Trait):
|
|||||||
media_player.DOMAIN,
|
media_player.DOMAIN,
|
||||||
svc,
|
svc,
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -2008,7 +2013,7 @@ class VolumeTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
|
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2170,7 +2175,7 @@ class TransportControlTrait(_Trait):
|
|||||||
media_player.DOMAIN,
|
media_player.DOMAIN,
|
||||||
service,
|
service,
|
||||||
service_attrs,
|
service_attrs,
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2275,7 +2280,7 @@ class ChannelTrait(_Trait):
|
|||||||
media_player.ATTR_MEDIA_CONTENT_ID: channel_number,
|
media_player.ATTR_MEDIA_CONTENT_ID: channel_number,
|
||||||
media_player.ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_CHANNEL,
|
media_player.ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_CHANNEL,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=not self.config.should_report_state,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,25 +20,27 @@ class MockConfig(helpers.AbstractConfig):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
secure_devices_pin=None,
|
agent_user_ids=None,
|
||||||
should_expose=None,
|
enabled=True,
|
||||||
should_2fa=None,
|
|
||||||
entity_config=None,
|
entity_config=None,
|
||||||
hass=None,
|
hass=None,
|
||||||
local_sdk_webhook_id=None,
|
|
||||||
local_sdk_user_id=None,
|
local_sdk_user_id=None,
|
||||||
enabled=True,
|
local_sdk_webhook_id=None,
|
||||||
agent_user_ids=None,
|
secure_devices_pin=None,
|
||||||
|
should_2fa=None,
|
||||||
|
should_expose=None,
|
||||||
|
should_report_state=False,
|
||||||
):
|
):
|
||||||
"""Initialize config."""
|
"""Initialize config."""
|
||||||
super().__init__(hass)
|
super().__init__(hass)
|
||||||
self._should_expose = should_expose
|
|
||||||
self._should_2fa = should_2fa
|
|
||||||
self._secure_devices_pin = secure_devices_pin
|
|
||||||
self._entity_config = entity_config or {}
|
|
||||||
self._local_sdk_webhook_id = local_sdk_webhook_id
|
|
||||||
self._local_sdk_user_id = local_sdk_user_id
|
|
||||||
self._enabled = enabled
|
self._enabled = enabled
|
||||||
|
self._entity_config = entity_config or {}
|
||||||
|
self._local_sdk_user_id = local_sdk_user_id
|
||||||
|
self._local_sdk_webhook_id = local_sdk_webhook_id
|
||||||
|
self._secure_devices_pin = secure_devices_pin
|
||||||
|
self._should_2fa = should_2fa
|
||||||
|
self._should_expose = should_expose
|
||||||
|
self._should_report_state = should_report_state
|
||||||
self._store = mock_google_config_store(agent_user_ids)
|
self._store = mock_google_config_store(agent_user_ids)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -74,6 +76,11 @@ class MockConfig(helpers.AbstractConfig):
|
|||||||
"""Expose it all."""
|
"""Expose it all."""
|
||||||
return self._should_expose is None or self._should_expose(state)
|
return self._should_expose is None or self._should_expose(state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_report_state(self):
|
||||||
|
"""Return if states should be proactively reported."""
|
||||||
|
return self._should_report_state
|
||||||
|
|
||||||
def should_2fa(self, state):
|
def should_2fa(self, state):
|
||||||
"""Expose it all."""
|
"""Expose it all."""
|
||||||
return self._should_2fa is None or self._should_2fa(state)
|
return self._should_2fa is None or self._should_2fa(state)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Test Google Smart Home."""
|
"""Test Google Smart Home."""
|
||||||
from unittest.mock import patch
|
import asyncio
|
||||||
|
from unittest.mock import ANY, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ from homeassistant.components.google_assistant import (
|
|||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.config import async_process_ha_core_config
|
||||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, __version__
|
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, __version__
|
||||||
from homeassistant.core import EVENT_CALL_SERVICE, State
|
from homeassistant.core import EVENT_CALL_SERVICE, State
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry, entity_platform
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from . import BASIC_CONFIG, MockConfig
|
from . import BASIC_CONFIG, MockConfig
|
||||||
@ -367,7 +368,10 @@ async def test_query_message(hass):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_execute(hass):
|
@pytest.mark.parametrize(
|
||||||
|
"report_state,on,brightness,value", [(False, True, 20, 0.2), (True, ANY, ANY, ANY)]
|
||||||
|
)
|
||||||
|
async def test_execute(hass, report_state, on, brightness, value):
|
||||||
"""Test an execute command."""
|
"""Test an execute command."""
|
||||||
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -375,45 +379,82 @@ async def test_execute(hass):
|
|||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True
|
"light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
events = async_capture_events(hass, EVENT_COMMAND_RECEIVED)
|
events = async_capture_events(hass, EVENT_COMMAND_RECEIVED)
|
||||||
service_events = async_capture_events(hass, EVENT_CALL_SERVICE)
|
service_events = async_capture_events(hass, EVENT_CALL_SERVICE)
|
||||||
|
|
||||||
result = await sh.async_handle_message(
|
with patch.object(
|
||||||
hass,
|
hass.services, "async_call", wraps=hass.services.async_call
|
||||||
BASIC_CONFIG,
|
) as call_service_mock:
|
||||||
None,
|
result = await sh.async_handle_message(
|
||||||
{
|
hass,
|
||||||
"requestId": REQ_ID,
|
MockConfig(should_report_state=report_state),
|
||||||
"inputs": [
|
None,
|
||||||
{
|
{
|
||||||
"intent": "action.devices.EXECUTE",
|
"requestId": REQ_ID,
|
||||||
"payload": {
|
"inputs": [
|
||||||
"commands": [
|
{
|
||||||
{
|
"intent": "action.devices.EXECUTE",
|
||||||
"devices": [
|
"payload": {
|
||||||
{"id": "light.non_existing"},
|
"commands": [
|
||||||
{"id": "light.ceiling_lights"},
|
{
|
||||||
{"id": "light.kitchen_lights"},
|
"devices": [
|
||||||
],
|
{"id": "light.non_existing"},
|
||||||
"execution": [
|
{"id": "light.ceiling_lights"},
|
||||||
{
|
{"id": "light.kitchen_lights"},
|
||||||
"command": "action.devices.commands.OnOff",
|
],
|
||||||
"params": {"on": True},
|
"execution": [
|
||||||
},
|
{
|
||||||
{
|
"command": "action.devices.commands.OnOff",
|
||||||
"command": "action.devices.commands.BrightnessAbsolute",
|
"params": {"on": True},
|
||||||
"params": {"brightness": 20},
|
},
|
||||||
},
|
{
|
||||||
],
|
"command": "action.devices.commands.BrightnessAbsolute",
|
||||||
}
|
"params": {"brightness": 20},
|
||||||
]
|
},
|
||||||
},
|
],
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
const.SOURCE_CLOUD,
|
}
|
||||||
)
|
],
|
||||||
|
},
|
||||||
|
const.SOURCE_CLOUD,
|
||||||
|
)
|
||||||
|
assert call_service_mock.call_count == 4
|
||||||
|
expected_calls = [
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.ceiling_lights"},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.kitchen_lights"},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.ceiling_lights", "brightness_pct": 20},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.kitchen_lights", "brightness_pct": 20},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
call_service_mock.assert_has_awaits(expected_calls, any_order=True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result == {
|
assert result == {
|
||||||
"requestId": REQ_ID,
|
"requestId": REQ_ID,
|
||||||
@ -428,9 +469,9 @@ async def test_execute(hass):
|
|||||||
"ids": ["light.ceiling_lights"],
|
"ids": ["light.ceiling_lights"],
|
||||||
"status": "SUCCESS",
|
"status": "SUCCESS",
|
||||||
"states": {
|
"states": {
|
||||||
"on": True,
|
"on": on,
|
||||||
"online": True,
|
"online": True,
|
||||||
"brightness": 20,
|
"brightness": brightness,
|
||||||
"color": {"temperatureK": 2631},
|
"color": {"temperatureK": 2631},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -440,12 +481,12 @@ async def test_execute(hass):
|
|||||||
"states": {
|
"states": {
|
||||||
"on": True,
|
"on": True,
|
||||||
"online": True,
|
"online": True,
|
||||||
"brightness": 20,
|
"brightness": brightness,
|
||||||
"color": {
|
"color": {
|
||||||
"spectrumHsv": {
|
"spectrumHsv": {
|
||||||
"hue": 345,
|
"hue": 345,
|
||||||
"saturation": 0.75,
|
"saturation": 0.75,
|
||||||
"value": 0.2,
|
"value": value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -506,6 +547,209 @@ async def test_execute(hass):
|
|||||||
assert service_events[3].context == events[0].context
|
assert service_events[3].context == events[0].context
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("report_state,on,brightness,value", [(False, False, ANY, ANY)])
|
||||||
|
async def test_execute_times_out(hass, report_state, on, brightness, value):
|
||||||
|
"""Test an execute command which times out."""
|
||||||
|
orig_execute_limit = sh.EXECUTE_LIMIT
|
||||||
|
sh.EXECUTE_LIMIT = 0.02 # Decrease timeout to 20ms
|
||||||
|
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
events = async_capture_events(hass, EVENT_COMMAND_RECEIVED)
|
||||||
|
service_events = async_capture_events(hass, EVENT_CALL_SERVICE)
|
||||||
|
|
||||||
|
platforms = entity_platform.async_get_platforms(hass, "demo")
|
||||||
|
assert platforms[0].domain == "light"
|
||||||
|
assert platforms[0].entities["light.ceiling_lights"]
|
||||||
|
|
||||||
|
turn_on_wait = asyncio.Event()
|
||||||
|
|
||||||
|
async def slow_turn_on(*args, **kwargs):
|
||||||
|
# Make DemoLigt.async_turn_on hang waiting for the turn_on_wait event
|
||||||
|
await turn_on_wait.wait(),
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
hass.services, "async_call", wraps=hass.services.async_call
|
||||||
|
) as call_service_mock, patch.object(
|
||||||
|
DemoLight, "async_turn_on", wraps=slow_turn_on
|
||||||
|
):
|
||||||
|
result = await sh.async_handle_message(
|
||||||
|
hass,
|
||||||
|
MockConfig(should_report_state=report_state),
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"requestId": REQ_ID,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"intent": "action.devices.EXECUTE",
|
||||||
|
"payload": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"devices": [
|
||||||
|
{"id": "light.non_existing"},
|
||||||
|
{"id": "light.ceiling_lights"},
|
||||||
|
{"id": "light.kitchen_lights"},
|
||||||
|
],
|
||||||
|
"execution": [
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.OnOff",
|
||||||
|
"params": {"on": True},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.BrightnessAbsolute",
|
||||||
|
"params": {"brightness": 20},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
const.SOURCE_CLOUD,
|
||||||
|
)
|
||||||
|
# Only the two first calls are executed
|
||||||
|
assert call_service_mock.call_count == 2
|
||||||
|
expected_calls = [
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.ceiling_lights"},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.kitchen_lights"},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
call_service_mock.assert_has_awaits(expected_calls, any_order=True)
|
||||||
|
|
||||||
|
turn_on_wait.set()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
# The remaining two calls should now have executed
|
||||||
|
assert call_service_mock.call_count == 4
|
||||||
|
expected_calls.extend(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.ceiling_lights", "brightness_pct": 20},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "light.kitchen_lights", "brightness_pct": 20},
|
||||||
|
blocking=not report_state,
|
||||||
|
context=ANY,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
call_service_mock.assert_has_awaits(expected_calls, any_order=True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"requestId": REQ_ID,
|
||||||
|
"payload": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"ids": ["light.non_existing"],
|
||||||
|
"status": "ERROR",
|
||||||
|
"errorCode": "deviceOffline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ids": ["light.ceiling_lights"],
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"states": {
|
||||||
|
"on": on,
|
||||||
|
"online": True,
|
||||||
|
"brightness": brightness,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ids": ["light.kitchen_lights"],
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"states": {
|
||||||
|
"on": True,
|
||||||
|
"online": True,
|
||||||
|
"brightness": brightness,
|
||||||
|
"color": {
|
||||||
|
"spectrumHsv": {
|
||||||
|
"hue": 345,
|
||||||
|
"saturation": 0.75,
|
||||||
|
"value": value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[0].data == {
|
||||||
|
"request_id": REQ_ID,
|
||||||
|
"entity_id": [
|
||||||
|
"light.non_existing",
|
||||||
|
"light.ceiling_lights",
|
||||||
|
"light.kitchen_lights",
|
||||||
|
],
|
||||||
|
"execution": [
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.OnOff",
|
||||||
|
"params": {"on": True},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.BrightnessAbsolute",
|
||||||
|
"params": {"brightness": 20},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"source": "cloud",
|
||||||
|
}
|
||||||
|
|
||||||
|
service_events = sorted(
|
||||||
|
service_events, key=lambda ev: ev.data["service_data"]["entity_id"]
|
||||||
|
)
|
||||||
|
assert len(service_events) == 4
|
||||||
|
assert service_events[0].data == {
|
||||||
|
"domain": "light",
|
||||||
|
"service": "turn_on",
|
||||||
|
"service_data": {"entity_id": "light.ceiling_lights"},
|
||||||
|
}
|
||||||
|
assert service_events[1].data == {
|
||||||
|
"domain": "light",
|
||||||
|
"service": "turn_on",
|
||||||
|
"service_data": {"brightness_pct": 20, "entity_id": "light.ceiling_lights"},
|
||||||
|
}
|
||||||
|
assert service_events[0].context == events[0].context
|
||||||
|
assert service_events[1].context == events[0].context
|
||||||
|
assert service_events[2].data == {
|
||||||
|
"domain": "light",
|
||||||
|
"service": "turn_on",
|
||||||
|
"service_data": {"entity_id": "light.kitchen_lights"},
|
||||||
|
}
|
||||||
|
assert service_events[3].data == {
|
||||||
|
"domain": "light",
|
||||||
|
"service": "turn_on",
|
||||||
|
"service_data": {"brightness_pct": 20, "entity_id": "light.kitchen_lights"},
|
||||||
|
}
|
||||||
|
assert service_events[2].context == events[0].context
|
||||||
|
assert service_events[3].context == events[0].context
|
||||||
|
|
||||||
|
sh.EXECUTE_LIMIT = orig_execute_limit
|
||||||
|
|
||||||
|
|
||||||
async def test_raising_error_trait(hass):
|
async def test_raising_error_trait(hass):
|
||||||
"""Test raising an error while executing a trait command."""
|
"""Test raising an error while executing a trait command."""
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user