コード例 #1
0
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"
コード例 #2
0
ファイル: http_api.py プロジェクト: zezo010/home-assistant
    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)
コード例 #3
0
ファイル: http_api.py プロジェクト: 2Fake/core
    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,
        )
コード例 #4
0
    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,
        )
コード例 #5
0
    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,
        )
コード例 #6
0
    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)
コード例 #7
0
ファイル: webhook.py プロジェクト: nickovs/home-assistant
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)
コード例 #8
0
ファイル: webhook.py プロジェクト: jcgoette/core
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)
コード例 #9
0
ファイル: webhook.py プロジェクト: fabiandevia/home
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)
コード例 #10
0
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)