Esempio n. 1
0
    async def async_step_user(self, user_input=None):
        """Handle the initial step."""
        errors = {}

        if user_input is not None:
            await self.async_set_unique_id(user_input[CONF_EMAIL].lower())
            websession = aiohttp_client.async_get_clientsession(self.hass)
            mazda_client = MazdaAPI(
                user_input[CONF_EMAIL],
                user_input[CONF_PASSWORD],
                user_input[CONF_REGION],
                websession,
            )

            try:
                await mazda_client.validate_credentials()
            except MazdaAuthenticationException:
                errors["base"] = "invalid_auth"
            except MazdaAccountLockedException:
                errors["base"] = "account_locked"
            except aiohttp.ClientError:
                errors["base"] = "cannot_connect"
            except Exception as ex:  # pylint: disable=broad-except
                errors["base"] = "unknown"
                _LOGGER.exception(
                    "Unknown error occurred during Mazda login request: %s",
                    ex)
            else:
                return self.async_create_entry(title=user_input[CONF_EMAIL],
                                               data=user_input)

        return self.async_show_form(step_id="user",
                                    data_schema=DATA_SCHEMA,
                                    errors=errors)
Esempio n. 2
0
async def init_integration(hass: HomeAssistant,
                           use_nickname=True) -> MockConfigEntry:
    """Set up the Mazda Connected Services integration in Home Assistant."""
    get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json"))
    if not use_nickname:
        get_vehicles_fixture[0].pop("nickname")

    get_vehicle_status_fixture = json.loads(
        load_fixture("mazda/get_vehicle_status.json"))

    config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT)
    config_entry.add_to_hass(hass)

    client_mock = MagicMock(
        MazdaAPI(
            FIXTURE_USER_INPUT[CONF_EMAIL],
            FIXTURE_USER_INPUT[CONF_PASSWORD],
            FIXTURE_USER_INPUT[CONF_REGION],
            aiohttp_client.async_get_clientsession(hass),
        ))
    client_mock.get_vehicles = AsyncMock(return_value=get_vehicles_fixture)
    client_mock.get_vehicle_status = AsyncMock(
        return_value=get_vehicle_status_fixture)

    with patch(
            "homeassistant.components.mazda.config_flow.MazdaAPI",
            return_value=client_mock,
    ), patch("homeassistant.components.mazda.MazdaAPI",
             return_value=client_mock):
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    return config_entry
    async def async_step_user(self, user_input=None):
        """Handle the initial step."""
        errors = {}

        if user_input is not None:
            self._email = user_input[CONF_EMAIL]
            self._region = user_input[CONF_REGION]
            unique_id = user_input[CONF_EMAIL].lower()
            await self.async_set_unique_id(unique_id)
            if not self._reauth_entry:
                self._abort_if_unique_id_configured()
            websession = aiohttp_client.async_get_clientsession(self.hass)
            mazda_client = MazdaAPI(
                user_input[CONF_EMAIL],
                user_input[CONF_PASSWORD],
                user_input[CONF_REGION],
                websession,
            )

            try:
                await mazda_client.validate_credentials()
            except MazdaAuthenticationException:
                errors["base"] = "invalid_auth"
            except MazdaAccountLockedException:
                errors["base"] = "account_locked"
            except aiohttp.ClientError:
                errors["base"] = "cannot_connect"
            except Exception as ex:  # pylint: disable=broad-except
                errors["base"] = "unknown"
                _LOGGER.exception(
                    "Unknown error occurred during Mazda login request: %s", ex
                )
            else:
                if not self._reauth_entry:
                    return self.async_create_entry(
                        title=user_input[CONF_EMAIL], data=user_input
                    )
                self.hass.config_entries.async_update_entry(
                    self._reauth_entry, data=user_input, unique_id=unique_id
                )
                # Reload the config entry otherwise devices will remain unavailable
                self.hass.async_create_task(
                    self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
                )
                return self.async_abort(reason="reauth_successful")

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_EMAIL, default=self._email): str,
                    vol.Required(CONF_PASSWORD): str,
                    vol.Required(CONF_REGION, default=self._region): vol.In(
                        MAZDA_REGIONS
                    ),
                }
            ),
            errors=errors,
        )
Esempio n. 4
0
    async def async_step_reauth(self, user_input=None):
        """Perform reauth if the user credentials have changed."""
        errors = {}

        if user_input is not None:
            try:
                websession = aiohttp_client.async_get_clientsession(self.hass)
                mazda_client = MazdaAPI(
                    user_input[CONF_EMAIL],
                    user_input[CONF_PASSWORD],
                    user_input[CONF_REGION],
                    websession,
                )
                await mazda_client.validate_credentials()
            except MazdaAuthenticationException:
                errors["base"] = "invalid_auth"
            except MazdaAccountLockedException:
                errors["base"] = "account_locked"
            except aiohttp.ClientError:
                errors["base"] = "cannot_connect"
            except Exception as ex:  # pylint: disable=broad-except
                errors["base"] = "unknown"
                _LOGGER.exception(
                    "Unknown error occurred during Mazda login request: %s",
                    ex)
            else:
                await self.async_set_unique_id(user_input[CONF_EMAIL].lower())

                for entry in self._async_current_entries():
                    if entry.unique_id == self.unique_id:
                        self.hass.config_entries.async_update_entry(
                            entry, data=user_input)

                        # Reload the config entry otherwise devices will remain unavailable
                        self.hass.async_create_task(
                            self.hass.config_entries.async_reload(
                                entry.entry_id))

                        return self.async_abort(reason="reauth_successful")
                errors["base"] = "unknown"

        return self.async_show_form(step_id="reauth",
                                    data_schema=DATA_SCHEMA,
                                    errors=errors)
Esempio n. 5
0
async def init_integration(opp: OpenPeerPower,
                           use_nickname=True) -> MockConfigEntry:
    """Set up the Mazda Connected Services integration in Open Peer Power."""
    get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json"))
    if not use_nickname:
        get_vehicles_fixture[0].pop("nickname")

    get_vehicle_status_fixture = json.loads(
        load_fixture("mazda/get_vehicle_status.json"))

    config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT)
    config_entry.add_to_opp(opp)

    client_mock = MagicMock(
        MazdaAPI(
            FIXTURE_USER_INPUT[CONF_EMAIL],
            FIXTURE_USER_INPUT[CONF_PASSWORD],
            FIXTURE_USER_INPUT[CONF_REGION],
            aiohttp_client.async_get_clientsession(opp),
        ))
    client_mock.get_vehicles = AsyncMock(return_value=get_vehicles_fixture)
    client_mock.get_vehicle_status = AsyncMock(
        return_value=get_vehicle_status_fixture)
    client_mock.lock_doors = AsyncMock()
    client_mock.unlock_doors = AsyncMock()
    client_mock.send_poi = AsyncMock()
    client_mock.start_charging = AsyncMock()
    client_mock.start_engine = AsyncMock()
    client_mock.stop_charging = AsyncMock()
    client_mock.stop_engine = AsyncMock()
    client_mock.turn_off_hazard_lights = AsyncMock()
    client_mock.turn_on_hazard_lights = AsyncMock()

    with patch(
            "openpeerpower.components.mazda.config_flow.MazdaAPI",
            return_value=client_mock,
    ), patch("openpeerpower.components.mazda.MazdaAPI",
             return_value=client_mock):
        assert await opp.config_entries.async_setup(config_entry.entry_id)
        await opp.async_block_till_done()

    return client_mock
Esempio n. 6
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Mazda Connected Services from a config entry."""
    email = entry.data[CONF_EMAIL]
    password = entry.data[CONF_PASSWORD]
    region = entry.data[CONF_REGION]

    websession = aiohttp_client.async_get_clientsession(hass)
    mazda_client = MazdaAPI(email,
                            password,
                            region,
                            websession=websession,
                            use_cached_vehicle_list=True)

    try:
        await mazda_client.validate_credentials()
    except MazdaAuthenticationException as ex:
        raise ConfigEntryAuthFailed from ex
    except (
            MazdaException,
            MazdaAccountLockedException,
            MazdaTokenExpiredException,
            MazdaAPIEncryptionException,
    ) as ex:
        _LOGGER.error("Error occurred during Mazda login request: %s", ex)
        raise ConfigEntryNotReady from ex

    async def async_handle_service_call(service_call: ServiceCall) -> None:
        """Handle a service call."""
        # Get device entry from device registry
        dev_reg = device_registry.async_get(hass)
        device_id = service_call.data["device_id"]
        device_entry = dev_reg.async_get(device_id)
        if TYPE_CHECKING:
            # For mypy: it has already been checked in validate_mazda_device_id
            assert device_entry

        # Get vehicle VIN from device identifiers
        mazda_identifiers = (identifier
                             for identifier in device_entry.identifiers
                             if identifier[0] == DOMAIN)
        vin_identifier = next(mazda_identifiers)
        vin = vin_identifier[1]

        # Get vehicle ID and API client from hass.data
        vehicle_id = 0
        api_client = None
        for entry_data in hass.data[DOMAIN].values():
            for vehicle in entry_data[DATA_VEHICLES]:
                if vehicle["vin"] == vin:
                    vehicle_id = vehicle["id"]
                    api_client = entry_data[DATA_CLIENT]
                    break

        if vehicle_id == 0 or api_client is None:
            raise HomeAssistantError("Vehicle ID not found")

        api_method = getattr(api_client, service_call.service)
        try:
            latitude = service_call.data["latitude"]
            longitude = service_call.data["longitude"]
            poi_name = service_call.data["poi_name"]
            await api_method(vehicle_id, latitude, longitude, poi_name)
        except Exception as ex:
            raise HomeAssistantError(ex) from ex

    def validate_mazda_device_id(device_id):
        """Check that a device ID exists in the registry and has at least one 'mazda' identifier."""
        dev_reg = device_registry.async_get(hass)

        if (device_entry := dev_reg.async_get(device_id)) is None:
            raise vol.Invalid("Invalid device ID")

        mazda_identifiers = [
            identifier for identifier in device_entry.identifiers
            if identifier[0] == DOMAIN
        ]
        if not mazda_identifiers:
            raise vol.Invalid("Device ID is not a Mazda vehicle")

        return device_id
Esempio n. 7
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
    """Set up Mazda Connected Services from a config entry."""
    email = entry.data[CONF_EMAIL]
    password = entry.data[CONF_PASSWORD]
    region = entry.data[CONF_REGION]

    websession = aiohttp_client.async_get_clientsession(hass)
    mazda_client = MazdaAPI(email, password, region, websession)

    try:
        await mazda_client.validate_credentials()
    except MazdaAuthenticationException:
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN,
                context={"source": SOURCE_REAUTH},
                data=entry.data,
            ))
        return False
    except (
            MazdaException,
            MazdaAccountLockedException,
            MazdaTokenExpiredException,
            MazdaAPIEncryptionException,
    ) as ex:
        _LOGGER.error("Error occurred during Mazda login request: %s", ex)
        raise ConfigEntryNotReady from ex

    async def async_update_data():
        """Fetch data from Mazda API."""
        async def with_timeout(task):
            async with async_timeout.timeout(10):
                return await task

        try:
            vehicles = await with_timeout(mazda_client.get_vehicles())

            vehicle_status_tasks = [
                with_timeout(mazda_client.get_vehicle_status(vehicle["id"]))
                for vehicle in vehicles
            ]
            statuses = await gather_with_concurrency(5, *vehicle_status_tasks)

            for vehicle, status in zip(vehicles, statuses):
                vehicle["status"] = status

            return vehicles
        except MazdaAuthenticationException as ex:
            hass.async_create_task(
                hass.config_entries.flow.async_init(
                    DOMAIN,
                    context={"source": SOURCE_REAUTH},
                    data=entry.data,
                ))
            raise UpdateFailed("Not authenticated with Mazda API") from ex
        except Exception as ex:
            _LOGGER.exception(
                "Unknown error occurred during Mazda update request: %s", ex)
            raise UpdateFailed(ex) from ex

    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=DOMAIN,
        update_method=async_update_data,
        update_interval=timedelta(seconds=60),
    )

    hass.data[DOMAIN][entry.entry_id] = {
        DATA_CLIENT: mazda_client,
        DATA_COORDINATOR: coordinator,
    }

    # Fetch initial data so we have data when entities subscribe
    await coordinator.async_refresh()
    if not coordinator.last_update_success:
        raise ConfigEntryNotReady

    # Setup components
    for component in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, component))

    return True
Esempio n. 8
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Mazda Connected Services from a config entry."""
    email = entry.data[CONF_EMAIL]
    password = entry.data[CONF_PASSWORD]
    region = entry.data[CONF_REGION]

    websession = aiohttp_client.async_get_clientsession(hass)
    mazda_client = MazdaAPI(email, password, region, websession)

    try:
        await mazda_client.validate_credentials()
    except MazdaAuthenticationException as ex:
        raise ConfigEntryAuthFailed from ex
    except (
            MazdaException,
            MazdaAccountLockedException,
            MazdaTokenExpiredException,
            MazdaAPIEncryptionException,
    ) as ex:
        _LOGGER.error("Error occurred during Mazda login request: %s", ex)
        raise ConfigEntryNotReady from ex

    async def async_handle_service_call(service_call=None):
        """Handle a service call."""
        # Get device entry from device registry
        dev_reg = device_registry.async_get(hass)
        device_id = service_call.data["device_id"]
        device_entry = dev_reg.async_get(device_id)

        # Get vehicle VIN from device identifiers
        mazda_identifiers = (identifier
                             for identifier in device_entry.identifiers
                             if identifier[0] == DOMAIN)
        vin_identifier = next(mazda_identifiers)
        vin = vin_identifier[1]

        # Get vehicle ID and API client from hass.data
        vehicle_id = 0
        api_client = None
        for entry_data in hass.data[DOMAIN].values():
            for vehicle in entry_data[DATA_VEHICLES]:
                if vehicle["vin"] == vin:
                    vehicle_id = vehicle["id"]
                    api_client = entry_data[DATA_CLIENT]
                    break

        if vehicle_id == 0 or api_client is None:
            raise HomeAssistantError("Vehicle ID not found")

        api_method = getattr(api_client, service_call.service)
        try:
            if service_call.service == "send_poi":
                latitude = service_call.data["latitude"]
                longitude = service_call.data["longitude"]
                poi_name = service_call.data["poi_name"]
                await api_method(vehicle_id, latitude, longitude, poi_name)
            else:
                await api_method(vehicle_id)
        except Exception as ex:
            raise HomeAssistantError(ex) from ex

    def validate_mazda_device_id(device_id):
        """Check that a device ID exists in the registry and has at least one 'mazda' identifier."""
        dev_reg = device_registry.async_get(hass)
        device_entry = dev_reg.async_get(device_id)

        if device_entry is None:
            raise vol.Invalid("Invalid device ID")

        mazda_identifiers = [
            identifier for identifier in device_entry.identifiers
            if identifier[0] == DOMAIN
        ]
        if not mazda_identifiers:
            raise vol.Invalid("Device ID is not a Mazda vehicle")

        return device_id

    service_schema = vol.Schema({
        vol.Required("device_id"):
        vol.All(cv.string, validate_mazda_device_id)
    })

    service_schema_send_poi = service_schema.extend({
        vol.Required("latitude"):
        cv.latitude,
        vol.Required("longitude"):
        cv.longitude,
        vol.Required("poi_name"):
        cv.string,
    })

    async def async_update_data():
        """Fetch data from Mazda API."""
        try:
            vehicles = await with_timeout(mazda_client.get_vehicles())

            vehicle_status_tasks = [
                with_timeout(mazda_client.get_vehicle_status(vehicle["id"]))
                for vehicle in vehicles
            ]
            statuses = await gather_with_concurrency(5, *vehicle_status_tasks)

            for vehicle, status in zip(vehicles, statuses):
                vehicle["status"] = status

            hass.data[DOMAIN][entry.entry_id][DATA_VEHICLES] = vehicles

            return vehicles
        except MazdaAuthenticationException as ex:
            raise ConfigEntryAuthFailed(
                "Not authenticated with Mazda API") from ex
        except Exception as ex:
            _LOGGER.exception(
                "Unknown error occurred during Mazda update request: %s", ex)
            raise UpdateFailed(ex) from ex

    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=DOMAIN,
        update_method=async_update_data,
        update_interval=timedelta(seconds=60),
    )

    hass.data.setdefault(DOMAIN, {})
    hass.data[DOMAIN][entry.entry_id] = {
        DATA_CLIENT: mazda_client,
        DATA_COORDINATOR: coordinator,
        DATA_VEHICLES: [],
    }

    # Fetch initial data so we have data when entities subscribe
    await coordinator.async_config_entry_first_refresh()

    # Setup components
    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    # Register services
    for service in SERVICES:
        if service == "send_poi":
            hass.services.async_register(
                DOMAIN,
                service,
                async_handle_service_call,
                schema=service_schema_send_poi,
            )
        else:
            hass.services.async_register(DOMAIN,
                                         service,
                                         async_handle_service_call,
                                         schema=service_schema)

    return True