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)
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, )
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)
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
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
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
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