async def test_remote_ui_url(hass, mock_cloud_fixture): """Test getting remote ui url.""" cl = hass.data["cloud"] # Not logged in with pytest.raises(cloud.CloudNotAvailable): cloud.async_remote_ui_url(hass) with patch.object(cloud, "async_is_logged_in", return_value=True): # Remote not enabled with pytest.raises(cloud.CloudNotAvailable): cloud.async_remote_ui_url(hass) with patch.object(cl.remote, "connect"): await cl.client.prefs.async_update(remote_enabled=True) await hass.async_block_till_done() # No instance domain with pytest.raises(cloud.CloudNotAvailable): cloud.async_remote_ui_url(hass) # Remote finished initializing cl.client.prefs._prefs["remote_domain"] = "example.com" assert cloud.async_remote_ui_url(hass) == "https://example.com"
async def post(self, request: Request, data: Dict) -> Response: """Handle the POST request for registration.""" hass = request.app['hass'] if ATTR_APP_COMPONENT in data: component = get_component(hass, data[ATTR_APP_COMPONENT]) if component is None: fmt_str = "{} is not a valid component." msg = fmt_str.format(data[ATTR_APP_COMPONENT]) return error_response(ERR_INVALID_COMPONENT, msg) if (hasattr(component, 'DEPENDENCIES') is False or (hasattr(component, 'DEPENDENCIES') and DOMAIN not in component.DEPENDENCIES)): fmt_str = "{} is not compatible with mobile_app." msg = fmt_str.format(data[ATTR_APP_COMPONENT]) return error_response(ERR_INVALID_COMPONENT, msg) webhook_id = generate_secret() if hass.components.cloud.async_active_subscription(): data[CONF_CLOUDHOOK_URL] = \ await async_create_cloudhook(hass, webhook_id) data[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "") data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): from nacl.secret import SecretBox data[CONF_SECRET] = generate_secret(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request['hass_user'].id ctx = {'source': 'registration'} await hass.async_create_task( hass.config_entries.flow.async_init(DOMAIN, context=ctx, data=data)) remote_ui_url = None try: remote_ui_url = async_remote_ui_url(hass) except CloudNotAvailable: pass return self.json( { CONF_CLOUDHOOK_URL: data.get(CONF_CLOUDHOOK_URL), CONF_REMOTE_UI_URL: remote_ui_url, CONF_SECRET: data.get(CONF_SECRET), CONF_WEBHOOK_ID: data[CONF_WEBHOOK_ID], }, status_code=HTTP_CREATED)
async def post(self, request: Request, data: dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] webhook_id = secrets.token_hex() if cloud.async_active_subscription(hass): data[CONF_CLOUDHOOK_URL] = await cloud.async_create_cloudhook( hass, webhook_id) data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): data[CONF_SECRET] = secrets.token_hex(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request["hass_user"].id if slugify(data[ATTR_DEVICE_NAME], separator=""): # if slug is not empty and would not only be underscores # use DEVICE_NAME pass elif emoji.emoji_count(data[ATTR_DEVICE_NAME]): # If otherwise empty string contains emoji # use descriptive name of the first emoji data[ATTR_DEVICE_NAME] = emoji.demojize( emoji.emoji_lis(data[ATTR_DEVICE_NAME])[0]["emoji"]).replace( ":", "") else: # Fallback to DEVICE_ID data[ATTR_DEVICE_NAME] = data[ATTR_DEVICE_ID] await hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, data=data, context={"source": "registration"})) remote_ui_url = None with suppress(hass.components.cloud.CloudNotAvailable): remote_ui_url = cloud.async_remote_ui_url(hass) return self.json( { CONF_CLOUDHOOK_URL: data.get(CONF_CLOUDHOOK_URL), CONF_REMOTE_UI_URL: remote_ui_url, CONF_SECRET: data.get(CONF_SECRET), CONF_WEBHOOK_ID: data[CONF_WEBHOOK_ID], }, status_code=HTTPStatus.CREATED, )
async def post(self, request: Request, data: Dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] webhook_id = generate_secret() if hass.components.cloud.async_active_subscription(): data[CONF_CLOUDHOOK_URL] = await async_create_cloudhook( hass, webhook_id) data[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "") data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): from nacl.secret import SecretBox data[CONF_SECRET] = generate_secret(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request["hass_user"].id ctx = {"source": "registration"} await hass.async_create_task( hass.config_entries.flow.async_init(DOMAIN, context=ctx, data=data)) remote_ui_url = None try: remote_ui_url = async_remote_ui_url(hass) except CloudNotAvailable: pass return self.json( { CONF_CLOUDHOOK_URL: data.get(CONF_CLOUDHOOK_URL), CONF_REMOTE_UI_URL: remote_ui_url, CONF_SECRET: data.get(CONF_SECRET), CONF_WEBHOOK_ID: data[CONF_WEBHOOK_ID], }, status_code=HTTP_CREATED, )
async def post(self, request: Request, data: dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] webhook_id = secrets.token_hex() if cloud.async_active_subscription(hass): data[CONF_CLOUDHOOK_URL] = await cloud.async_create_cloudhook( hass, webhook_id) data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): data[CONF_SECRET] = secrets.token_hex(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request["hass_user"].id # Fallback to DEVICE_ID if slug is empty. if not slugify(data[ATTR_DEVICE_NAME], separator=""): data[ATTR_DEVICE_NAME] = data[ATTR_DEVICE_ID] await hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, data=data, context={"source": "registration"})) remote_ui_url = None with suppress(hass.components.cloud.CloudNotAvailable): remote_ui_url = cloud.async_remote_ui_url(hass) return self.json( { CONF_CLOUDHOOK_URL: data.get(CONF_CLOUDHOOK_URL), CONF_REMOTE_UI_URL: remote_ui_url, CONF_SECRET: data.get(CONF_SECRET), CONF_WEBHOOK_ID: data[CONF_WEBHOOK_ID], }, status_code=HTTPStatus.CREATED, )
async def post(self, request: Request, data: Dict) -> Response: """Handle the POST request for registration.""" hass = request.app['hass'] webhook_id = generate_secret() if hass.components.cloud.async_active_subscription(): data[CONF_CLOUDHOOK_URL] = \ await async_create_cloudhook(hass, webhook_id) data[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "") data[CONF_WEBHOOK_ID] = webhook_id if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): from nacl.secret import SecretBox data[CONF_SECRET] = generate_secret(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request['hass_user'].id ctx = {'source': 'registration'} await hass.async_create_task( hass.config_entries.flow.async_init(DOMAIN, context=ctx, data=data)) remote_ui_url = None try: remote_ui_url = async_remote_ui_url(hass) except CloudNotAvailable: pass return self.json({ CONF_CLOUDHOOK_URL: data.get(CONF_CLOUDHOOK_URL), CONF_REMOTE_UI_URL: remote_ui_url, CONF_SECRET: data.get(CONF_SECRET), CONF_WEBHOOK_ID: data[CONF_WEBHOOK_ID], }, status_code=HTTP_CREATED)
async def webhook_get_config(hass, config_entry, data): """Handle a get config webhook.""" hass_config = hass.config.as_dict() resp = { "latitude": hass_config["latitude"], "longitude": hass_config["longitude"], "elevation": hass_config["elevation"], "unit_system": hass_config["unit_system"], "location_name": hass_config["location_name"], "time_zone": hass_config["time_zone"], "components": hass_config["components"], "version": hass_config["version"], "theme_color": MANIFEST_JSON["theme_color"], } if CONF_CLOUDHOOK_URL in config_entry.data: resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] with suppress(hass.components.cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) return webhook_response(resp, registration=config_entry.data)
async def webhook_get_config(hass, config_entry, data): """Handle a get config webhook.""" hass_config = hass.config.as_dict() resp = { "latitude": hass_config["latitude"], "longitude": hass_config["longitude"], "elevation": hass_config["elevation"], "unit_system": hass_config["unit_system"], "location_name": hass_config["location_name"], "time_zone": hass_config["time_zone"], "components": hass_config["components"], "version": hass_config["version"], "theme_color": MANIFEST_JSON["theme_color"], } if CONF_CLOUDHOOK_URL in config_entry.data: resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] with suppress(hass.components.cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) webhook_id = config_entry.data[CONF_WEBHOOK_ID] entities = {} for entry in er.async_entries_for_config_entry(er.async_get(hass), config_entry.entry_id): if entry.domain in ("binary_sensor", "sensor"): unique_id = _extract_sensor_unique_id(webhook_id, entry.unique_id) else: unique_id = entry.unique_id entities[unique_id] = {"disabled": entry.disabled} resp["entities"] = entities return webhook_response(resp, registration=config_entry.data)
async def handle_webhook(hass: HomeAssistantType, webhook_id: str, request: Request) -> Response: """Handle webhook callback.""" if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]: return Response(status=410) headers = {} config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] registration = config_entry.data try: req_data = await request.json() except ValueError: _LOGGER.warning('Received invalid JSON from mobile_app') return empty_okay_response(status=HTTP_BAD_REQUEST) if (ATTR_WEBHOOK_ENCRYPTED not in req_data and registration[ATTR_SUPPORTS_ENCRYPTION]): _LOGGER.warning("Refusing to accept unencrypted webhook from %s", registration[ATTR_DEVICE_NAME]) return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") try: req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data) except vol.Invalid as ex: err = vol.humanize.humanize_error(req_data, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response() webhook_type = req_data[ATTR_WEBHOOK_TYPE] webhook_payload = req_data.get(ATTR_WEBHOOK_DATA, {}) if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data) if webhook_type not in WEBHOOK_TYPES: _LOGGER.error('Received invalid webhook type: %s', webhook_type) return empty_okay_response() data = webhook_payload _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type, data) if webhook_type in WEBHOOK_SCHEMAS: try: data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) except vol.Invalid as ex: err = vol.humanize.humanize_error(webhook_payload, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response(headers=headers) context = registration_context(registration) if webhook_type == WEBHOOK_TYPE_CALL_SERVICE: try: await hass.services.async_call(data[ATTR_DOMAIN], data[ATTR_SERVICE], data[ATTR_SERVICE_DATA], blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error( "Error when calling service during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) raise HTTPBadRequest() return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_FIRE_EVENT: event_type = data[ATTR_EVENT_TYPE] hass.bus.async_fire(event_type, data[ATTR_EVENT_DATA], EventOrigin.remote, context=context) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE: resp = {} for key, item in data.items(): try: tpl = item[ATTR_TEMPLATE] attach(hass, tpl) resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) # noqa: E722 pylint: disable=broad-except except TemplateError as ex: resp[key] = {"error": str(ex)} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION: hass.helpers.dispatcher.async_dispatcher_send( SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION: new_registration = {**registration, **data} device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]), (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID])}, manufacturer=new_registration[ATTR_MANUFACTURER], model=new_registration[ATTR_MODEL], name=new_registration[ATTR_DEVICE_NAME], sw_version=new_registration[ATTR_OS_VERSION]) hass.config_entries.async_update_entry(config_entry, data=new_registration) return webhook_response(safe_registration(new_registration), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR: entity_type = data[ATTR_SENSOR_TYPE] unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) return error_response(ERR_SENSOR_DUPLICATE_UNIQUE_ID, "{} {} already exists!".format( entity_type, unique_id), status=409) data[CONF_WEBHOOK_ID] = webhook_id hass.data[DOMAIN][entity_type][unique_store_key] = data try: await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) except HomeAssistantError as ex: _LOGGER.error("Error registering sensor: %s", ex) return empty_okay_response() register_signal = '{}_{}_register'.format(DOMAIN, data[ATTR_SENSOR_TYPE]) async_dispatcher_send(hass, register_signal, data) return webhook_response({'success': True}, registration=registration, status=HTTP_CREATED, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES: resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key not in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to update non-registered sensor: %s", unique_store_key) err_msg = '{} {} is not registered'.format( entity_type, unique_id) resp[unique_id] = { 'success': False, 'error': { 'code': ERR_SENSOR_NOT_REGISTERED, 'message': err_msg } } continue entry = hass.data[DOMAIN][entity_type][unique_store_key] new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state safe = savable_state(hass) try: await hass.data[DOMAIN][DATA_STORE].async_save(safe) except HomeAssistantError as ex: _LOGGER.error("Error updating mobile_app registration: %s", ex) return empty_okay_response() async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {'success': True} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_ZONES: zones = ( hass.states.get(entity_id) for entity_id in sorted(hass.states.async_entity_ids(ZONE_DOMAIN))) return webhook_response(list(zones), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_CONFIG: hass_config = hass.config.as_dict() resp = { 'latitude': hass_config['latitude'], 'longitude': hass_config['longitude'], 'elevation': hass_config['elevation'], 'unit_system': hass_config['unit_system'], 'location_name': hass_config['location_name'], 'time_zone': hass_config['time_zone'], 'components': hass_config['components'], 'version': hass_config['version'], 'theme_color': MANIFEST_JSON['theme_color'], } if CONF_CLOUDHOOK_URL in registration: resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] try: resp[CONF_REMOTE_UI_URL] = async_remote_ui_url(hass) except CloudNotAvailable: pass return webhook_response(resp, registration=registration, headers=headers)
async def handle_webhook(hass: HomeAssistantType, webhook_id: str, request: Request) -> Response: """Handle webhook callback.""" if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]: return Response(status=410) headers = {} config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] registration = config_entry.data try: req_data = await request.json() except ValueError: _LOGGER.warning('Received invalid JSON from mobile_app') return empty_okay_response(status=HTTP_BAD_REQUEST) if (ATTR_WEBHOOK_ENCRYPTED not in req_data and registration[ATTR_SUPPORTS_ENCRYPTION]): _LOGGER.warning("Refusing to accept unencrypted webhook from %s", registration[ATTR_DEVICE_NAME]) return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") try: req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data) except vol.Invalid as ex: err = vol.humanize.humanize_error(req_data, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response() webhook_type = req_data[ATTR_WEBHOOK_TYPE] webhook_payload = req_data.get(ATTR_WEBHOOK_DATA, {}) if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data) if webhook_type not in WEBHOOK_TYPES: _LOGGER.error('Received invalid webhook type: %s', webhook_type) return empty_okay_response() data = webhook_payload _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type, data) if webhook_type in WEBHOOK_SCHEMAS: try: data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) except vol.Invalid as ex: err = vol.humanize.humanize_error(webhook_payload, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response(headers=headers) context = registration_context(registration) if webhook_type == WEBHOOK_TYPE_CALL_SERVICE: try: await hass.services.async_call(data[ATTR_DOMAIN], data[ATTR_SERVICE], data[ATTR_SERVICE_DATA], blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error("Error when calling service during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) raise HTTPBadRequest() return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_FIRE_EVENT: event_type = data[ATTR_EVENT_TYPE] hass.bus.async_fire(event_type, data[ATTR_EVENT_DATA], EventOrigin.remote, context=context) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE: resp = {} for key, item in data.items(): try: tpl = item[ATTR_TEMPLATE] attach(hass, tpl) resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) # noqa: E722 pylint: disable=broad-except except TemplateError as ex: resp[key] = {"error": str(ex)} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION: see_payload = { ATTR_DEV_ID: registration[ATTR_DEVICE_ID], ATTR_GPS: data[ATTR_GPS], ATTR_GPS_ACCURACY: data[ATTR_GPS_ACCURACY], } for key in (ATTR_LOCATION_NAME, ATTR_BATTERY): value = data.get(key) if value is not None: see_payload[key] = value attrs = {} for key in (ATTR_ALTITUDE, ATTR_COURSE, ATTR_SPEED, ATTR_VERTICAL_ACCURACY): value = data.get(key) if value is not None: attrs[key] = value if attrs: see_payload[ATTR_ATTRIBUTES] = attrs try: await hass.services.async_call(DT_DOMAIN, DT_SEE, see_payload, blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error("Error when updating location during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION: new_registration = {**registration, **data} device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={ (ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]), (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID]) }, manufacturer=new_registration[ATTR_MANUFACTURER], model=new_registration[ATTR_MODEL], name=new_registration[ATTR_DEVICE_NAME], sw_version=new_registration[ATTR_OS_VERSION] ) hass.config_entries.async_update_entry(config_entry, data=new_registration) return webhook_response(safe_registration(new_registration), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR: entity_type = data[ATTR_SENSOR_TYPE] unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) return error_response(ERR_SENSOR_DUPLICATE_UNIQUE_ID, "{} {} already exists!".format(entity_type, unique_id), status=409) data[CONF_WEBHOOK_ID] = webhook_id hass.data[DOMAIN][entity_type][unique_store_key] = data try: await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) except HomeAssistantError as ex: _LOGGER.error("Error registering sensor: %s", ex) return empty_okay_response() register_signal = '{}_{}_register'.format(DOMAIN, data[ATTR_SENSOR_TYPE]) async_dispatcher_send(hass, register_signal, data) return webhook_response({'success': True}, registration=registration, status=HTTP_CREATED, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES: resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key not in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to update non-registered sensor: %s", unique_store_key) err_msg = '{} {} is not registered'.format(entity_type, unique_id) resp[unique_id] = { 'success': False, 'error': { 'code': ERR_SENSOR_NOT_REGISTERED, 'message': err_msg } } continue entry = hass.data[DOMAIN][entity_type][unique_store_key] new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state safe = savable_state(hass) try: await hass.data[DOMAIN][DATA_STORE].async_save(safe) except HomeAssistantError as ex: _LOGGER.error("Error updating mobile_app registration: %s", ex) return empty_okay_response() async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {'success': True} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_ZONES: zones = (hass.states.get(entity_id) for entity_id in sorted(hass.states.async_entity_ids(ZONE_DOMAIN))) return webhook_response(list(zones), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_CONFIG: hass_config = hass.config.as_dict() resp = { 'latitude': hass_config['latitude'], 'longitude': hass_config['longitude'], 'elevation': hass_config['elevation'], 'unit_system': hass_config['unit_system'], 'location_name': hass_config['location_name'], 'time_zone': hass_config['time_zone'], 'components': hass_config['components'], 'version': hass_config['version'], 'theme_color': MANIFEST_JSON['theme_color'], } if CONF_CLOUDHOOK_URL in registration: resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] try: resp[CONF_REMOTE_UI_URL] = async_remote_ui_url(hass) except CloudNotAvailable: pass return webhook_response(resp, registration=registration, headers=headers)