1 """Google config for Cloud."""
3 from __future__
import annotations
6 from http
import HTTPStatus
8 from typing
import TYPE_CHECKING, Any
10 from hass_nabucasa
import Cloud, cloud_api
11 from hass_nabucasa.google_report_state
import ErrorResponse
18 async_get_assistant_settings,
19 async_get_entity_settings,
20 async_listen_entity_updates,
21 async_set_assistant_option,
44 DOMAIN
as CLOUD_DOMAIN,
48 from .prefs
import GOOGLE_SETTINGS_VERSION, CloudPreferences
51 from .client
import CloudClient
53 _LOGGER = logging.getLogger(__name__)
55 CLOUD_GOOGLE = f
"{CLOUD_DOMAIN}.{GOOGLE_DOMAIN}"
59 "alarm_control_panel",
80 SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES = {
81 BinarySensorDeviceClass.DOOR,
82 BinarySensorDeviceClass.GARAGE_DOOR,
83 BinarySensorDeviceClass.LOCK,
84 BinarySensorDeviceClass.MOTION,
85 BinarySensorDeviceClass.OPENING,
86 BinarySensorDeviceClass.PRESENCE,
87 BinarySensorDeviceClass.WINDOW,
90 SUPPORTED_SENSOR_DEVICE_CLASSES = {
91 SensorDeviceClass.AQI,
93 SensorDeviceClass.CO2,
94 SensorDeviceClass.HUMIDITY,
95 SensorDeviceClass.PM10,
96 SensorDeviceClass.PM25,
97 SensorDeviceClass.TEMPERATURE,
98 SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
103 """Return if the entity is supported.
105 This is called when migrating from legacy config format to avoid exposing
106 all binary sensors and sensors.
109 if domain
in SUPPORTED_DOMAINS:
114 except HomeAssistantError:
119 domain ==
"binary_sensor"
120 and device_class
in SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES
124 if domain ==
"sensor" and device_class
in SUPPORTED_SENSOR_DEVICE_CLASSES:
131 """HA Cloud Configuration for Google Assistant."""
136 config: dict[str, Any],
138 prefs: CloudPreferences,
139 cloud: Cloud[CloudClient],
141 """Initialize the Google config."""
151 """Return if Google is enabled."""
153 self.
_cloud_cloud.is_logged_in
154 and not self.
_cloud_cloud.subscription_expired
155 and self.
_prefs_prefs.google_enabled
160 """Return entity config."""
161 return self.
_config_config.
get(CONF_ENTITY_CONFIG)
or {}
165 """Return entity config."""
166 return self.
_prefs_prefs.google_secure_devices_pin
170 """Return if states should be proactively reported."""
174 """Return the webhook ID to be used for actions for a given agent user id via the local SDK."""
175 return self.
_prefs_prefs.google_local_webhook_id
178 """Map webhook ID to a Home Assistant user ID.
180 Any action initiated by Google Assistant via the local SDK will be attributed
181 to the returned user ID.
183 return self.
_user_user
187 """Return Cloud User account."""
188 return self.
_user_user
191 """Migrate Google entity settings to entity registry options."""
192 if not self.
_config_config[CONF_FILTER].empty_filter:
197 *self.
hasshass.states.async_entity_ids(),
198 *self.
_prefs_prefs.google_entity_configs,
216 """Perform async initialization of config."""
217 _LOGGER.debug(
"async_initialize")
220 async
def on_hass_started(hass: HomeAssistant) ->
None:
221 _LOGGER.debug(
"async_initialize on_hass_started")
222 if self.
_prefs_prefs.google_settings_version != GOOGLE_SETTINGS_VERSION:
224 "Start migration of Google Assistant settings from v%s to v%s",
225 self.
_prefs_prefs.google_settings_version,
226 GOOGLE_SETTINGS_VERSION,
228 if self.
_prefs_prefs.google_settings_version < 2
or (
230 self.
_prefs_prefs.google_settings_version < 3
232 settings.get(
"should_expose",
False)
241 "Finished migration of Google Assistant settings from v%s to v%s",
242 self.
_prefs_prefs.google_settings_version,
243 GOOGLE_SETTINGS_VERSION,
246 google_settings_version=GOOGLE_SETTINGS_VERSION
248 self._on_deinitialize.append(
254 async
def on_hass_start(hass: HomeAssistant) ->
None:
255 _LOGGER.debug(
"async_initialize on_hass_start")
256 if self.
enabledenabledenabled
and GOOGLE_DOMAIN
not in self.
hasshass.config.components:
259 self._on_deinitialize.append(start.async_at_start(self.
hasshass, on_hass_start))
260 self._on_deinitialize.append(start.async_at_started(self.
hasshass, on_hass_started))
262 self._on_deinitialize.append(
265 self._on_deinitialize.append(
266 self.
hasshass.bus.async_listen(
267 er.EVENT_ENTITY_REGISTRY_UPDATED,
271 self._on_deinitialize.append(
272 self.
hasshass.bus.async_listen(
273 dr.EVENT_DEVICE_REGISTRY_UPDATED,
279 """If a state object should be exposed."""
283 """If an entity ID should be exposed."""
284 if entity_id
in CLOUD_NEVER_EXPOSED_ENTITIES:
287 entity_configs = self.
_prefs_prefs.google_entity_configs
288 entity_config = entity_configs.get(entity_id, {})
289 entity_expose: bool |
None = entity_config.get(PREF_SHOULD_EXPOSE)
290 if entity_expose
is not None:
293 entity_registry = er.async_get(self.
hasshass)
294 if registry_entry := entity_registry.async_get(entity_id):
296 registry_entry.entity_category
is not None
297 or registry_entry.hidden_by
is not None
300 auxiliary_entity =
False
302 default_expose = self.
_prefs_prefs.google_default_expose
305 if default_expose
is None:
315 """If an entity should be exposed."""
316 entity_filter: EntityFilter = self.
_config_config[CONF_FILTER]
317 if not entity_filter.empty_filter:
318 if entity_id
in CLOUD_NEVER_EXPOSED_ENTITIES:
320 return entity_filter(entity_id)
326 """Return Agent User Id to use for query responses."""
327 return self.
_cloud_cloud.username
331 """Return if we have a Agent User Id registered."""
335 """Get agent user ID making request."""
339 """Map webhook ID to a Google agent user ID.
341 Return None if no agent user id is found for the webhook_id.
343 if webhook_id != self.
_prefs_prefs.google_local_webhook_id:
349 """If an entity should be checked for 2FA."""
350 entity_configs = self.
_prefs_prefs.google_entity_configs
351 entity_config = entity_configs.get(entity_id, {})
352 return entity_config.get(PREF_DISABLE_2FA)
355 """If an entity should be checked for 2FA."""
358 except HomeAssistantError:
362 assistant_options = settings.get(CLOUD_GOOGLE, {})
363 return not assistant_options.get(PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
366 self, message: Any, agent_user_id: str, event_id: str |
None =
None
368 """Send a state report to Google."""
370 await self.
_cloud_cloud.google_report_state.async_send_message(message)
371 except ErrorResponse
as err:
372 _LOGGER.warning(
"Error reporting state - %s: %s", err.code, err.message)
375 """Trigger a sync with Google."""
380 resp = await cloud_api.async_google_actions_request_sync(self.
_cloud_cloud)
384 """Add a synced and known agent_user_id.
386 Called before sending a sync response to Google.
391 """Turn off report state and disable further state reporting.
394 - The user disconnects their account from Google.
395 - When the cloud configuration is initialized
396 - When sync entities fails with 404
402 """Return known agent users."""
404 not self.
_cloud_cloud.is_logged_in
405 or not self.
_prefs_prefs.google_connected
406 or not self.
_cloud_cloud.username
409 return (self.
_cloud_cloud.username,)
412 """Handle updated preferences."""
413 _LOGGER.debug(
"_async_prefs_updated")
414 if not self.
_cloud_cloud.is_logged_in:
423 and GOOGLE_DOMAIN
not in self.
hasshass.config.components
424 and self.
hasshass.is_running
428 sync_entities =
False
447 if sync_entities
and self.
hasshass.is_running:
452 """Handle updated preferences."""
457 self, event: Event[er.EventEntityRegistryUpdatedData]
459 """Handle when entity registry updated."""
462 or not self.
_cloud_cloud.is_logged_in
463 or self.
hasshass.state
is not CoreState.running
468 if event.data[
"action"] ==
"update" and not bool(
469 set(event.data[
"changes"]) & er.ENTITY_DESCRIBING_ATTRIBUTES
473 entity_id = event.data[
"entity_id"]
482 self, event: Event[dr.EventDeviceRegistryUpdatedData]
484 """Handle when device registry updated."""
487 or not self.
_cloud_cloud.is_logged_in
488 or self.
hasshass.state
is not CoreState.running
493 if event.data[
"action"] !=
"update" or "area_id" not in event.data[
"changes"]:
498 entity_entry.area_id
is None
500 for entity_entry
in er.async_entries_for_device(
501 er.async_get(self.
hasshass), event.data[
"device_id"]
None async_initialize(self)
bool|None _2fa_disabled_legacy(self, str entity_id)
None _handle_device_registry_updated(self, Event[dr.EventDeviceRegistryUpdatedData] event)
None async_connect_agent_user(self, str agent_user_id)
None _handle_entity_registry_updated(self, Event[er.EventEntityRegistryUpdatedData] event)
bool should_expose(self, State state)
dict[str, Any] entity_config(self)
None _async_exposed_entities_updated(self)
None async_report_state(self, Any message, str agent_user_id, str|None event_id=None)
str|None secure_devices_pin(self)
None async_disconnect_agent_user(self, str agent_user_id)
bool should_2fa(self, State state)
str|None get_agent_user_id_from_webhook(self, str webhook_id)
HTTPStatus|int _async_request_sync_devices(self, str agent_user_id)
bool _should_expose_legacy(self, str entity_id)
None _migrate_google_entity_settings_v1(self)
None __init__(self, HomeAssistant hass, dict[str, Any] config, str cloud_user, CloudPreferences prefs, Cloud[CloudClient] cloud)
str get_local_user_id(self, Any webhook_id)
str get_local_webhook_id(self, Any agent_user_id)
None _async_prefs_updated(self, CloudPreferences prefs)
tuple async_get_agent_users(self)
str get_agent_user_id_from_context(self, Any context)
bool should_report_state(self)
bool has_registered_user_agent(self)
bool _should_expose_entity_id(self, str entity_id)
def is_reporting_state(self)
def is_local_sdk_active(self)
None async_schedule_google_sync_all(self)
None async_disable_local_sdk(self)
int async_sync_entities_all(self)
None async_enable_report_state(self)
None async_enable_local_sdk(self)
Collection[str] async_get_agent_users(self)
None async_disable_report_state(self)
def should_report_state(self)
bool _supported_legacy(HomeAssistant hass, str entity_id)
web.Response get(self, web.Request request, str config_key)
None async_set_assistant_option(HomeAssistant hass, str assistant, str entity_id, str option, Any value)
CALLBACK_TYPE async_listen_entity_updates(HomeAssistant hass, str assistant, Callable[[], None] listener)
bool async_should_expose(HomeAssistant hass, str assistant, str entity_id)
None async_expose_entity(HomeAssistant hass, str assistant, str entity_id, bool should_expose)
dict[str, Mapping[str, Any]] async_get_entity_settings(HomeAssistant hass, str entity_id)
dict[str, Mapping[str, Any]] async_get_assistant_settings(HomeAssistant hass, str assistant)
tuple[str, str] split_entity_id(str entity_id)
str|None get_device_class(HomeAssistant hass, str entity_id)
bool async_setup_component(core.HomeAssistant hass, str domain, ConfigType config)