diff --git a/CODEOWNERS b/CODEOWNERS index 8dacaa7021e..880dd552cbc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -738,8 +738,6 @@ build.json @home-assistant/supervisor /tests/components/matrix/ @PaarthShah /homeassistant/components/matter/ @home-assistant/matter /tests/components/matter/ @home-assistant/matter -/homeassistant/components/mazda/ @bdr99 -/tests/components/mazda/ @bdr99 /homeassistant/components/meater/ @Sotolotl @emontnemery /tests/components/meater/ @Sotolotl @emontnemery /homeassistant/components/medcom_ble/ @elafargue diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index f375b8a75cd..75e7baf7413 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -1,213 +1,26 @@ """The Mazda Connected Services integration.""" from __future__ import annotations -import asyncio -from datetime import timedelta -import logging -from typing import TYPE_CHECKING +from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir -from pymazda import ( - Client as MazdaAPI, - MazdaAccountLockedException, - MazdaAPIEncryptionException, - MazdaAuthenticationException, - MazdaException, - MazdaTokenExpiredException, -) -import voluptuous as vol - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION, Platform -from homeassistant.core import HomeAssistant, ServiceCall -from homeassistant.exceptions import ( - ConfigEntryAuthFailed, - ConfigEntryNotReady, - HomeAssistantError, -) -from homeassistant.helpers import ( - aiohttp_client, - config_validation as cv, - device_registry as dr, -) -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) - -from .const import DATA_CLIENT, DATA_COORDINATOR, DATA_REGION, DATA_VEHICLES, DOMAIN - -_LOGGER = logging.getLogger(__name__) - -PLATFORMS = [ - Platform.BINARY_SENSOR, - Platform.BUTTON, - Platform.CLIMATE, - Platform.DEVICE_TRACKER, - Platform.LOCK, - Platform.SENSOR, - Platform.SWITCH, -] +DOMAIN = "mazda" -async def with_timeout(task, timeout_seconds=30): - """Run an async task with a timeout.""" - async with asyncio.timeout(timeout_seconds): - return await task - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, _: 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 = dr.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 = dr.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 - - service_schema_send_poi = vol.Schema( - { - vol.Required("device_id"): vol.All(cv.string, validate_mazda_device_id), - 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()) - - # The Mazda API can throw an error when multiple simultaneous requests are - # made for the same account, so we can only make one request at a time here - for vehicle in vehicles: - vehicle["status"] = await with_timeout( - mazda_client.get_vehicle_status(vehicle["id"]) - ) - - # If vehicle is electric, get additional EV-specific status info - if vehicle["isElectric"]: - vehicle["evStatus"] = await with_timeout( - mazda_client.get_ev_vehicle_status(vehicle["id"]) - ) - vehicle["hvacSetting"] = await with_timeout( - mazda_client.get_hvac_setting(vehicle["id"]) - ) - - 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( + ir.async_create_issue( hass, - _LOGGER, - name=DOMAIN, - update_method=async_update_data, - update_interval=timedelta(seconds=180), - ) - - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = { - DATA_CLIENT: mazda_client, - DATA_COORDINATOR: coordinator, - DATA_REGION: region, - DATA_VEHICLES: [], - } - - # Fetch initial data so we have data when entities subscribe - await coordinator.async_config_entry_first_refresh() - - # Setup components - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - - # Register services - hass.services.async_register( DOMAIN, - "send_poi", - async_handle_service_call, - schema=service_schema_send_poi, + DOMAIN, + is_fixable=False, + severity=ir.IssueSeverity.ERROR, + translation_key="integration_removed", + translation_placeholders={ + "dmca": "https://github.com/github/dmca/blob/master/2023/10/2023-10-10-mazda.md", + "entries": "/config/integrations/integration/mazda", + }, ) return True @@ -215,45 +28,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if all( + config_entry.state is ConfigEntryState.NOT_LOADED + for config_entry in hass.config_entries.async_entries(DOMAIN) + if config_entry.entry_id != entry.entry_id + ): + ir.async_delete_issue(hass, DOMAIN, DOMAIN) - # Only remove services if it is the last config entry - if len(hass.data[DOMAIN]) == 1: - hass.services.async_remove(DOMAIN, "send_poi") - - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok - - -class MazdaEntity(CoordinatorEntity): - """Defines a base Mazda entity.""" - - _attr_has_entity_name = True - - def __init__(self, client, coordinator, index): - """Initialize the Mazda entity.""" - super().__init__(coordinator) - self.client = client - self.index = index - self.vin = self.data["vin"] - self.vehicle_id = self.data["id"] - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, self.vin)}, - manufacturer="Mazda", - model=f"{self.data['modelYear']} {self.data['carlineName']}", - name=self.vehicle_name, - ) - - @property - def data(self): - """Shortcut to access coordinator data for the entity.""" - return self.coordinator.data[self.index] - - @property - def vehicle_name(self): - """Return the vehicle name, to be used as a prefix for names of other entities.""" - if "nickname" in self.data and len(self.data["nickname"]) > 0: - return self.data["nickname"] - return f"{self.data['modelYear']} {self.data['carlineName']}" + return True diff --git a/homeassistant/components/mazda/binary_sensor.py b/homeassistant/components/mazda/binary_sensor.py deleted file mode 100644 index 36c3ba27463..00000000000 --- a/homeassistant/components/mazda/binary_sensor.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Platform for Mazda binary sensor integration.""" -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import dataclass -from typing import Any - -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntity, - BinarySensorEntityDescription, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -@dataclass -class MazdaBinarySensorRequiredKeysMixin: - """Mixin for required keys.""" - - # Function to determine the value for this binary sensor, given the coordinator data - value_fn: Callable[[dict[str, Any]], bool] - - -@dataclass -class MazdaBinarySensorEntityDescription( - BinarySensorEntityDescription, MazdaBinarySensorRequiredKeysMixin -): - """Describes a Mazda binary sensor entity.""" - - # Function to determine whether the vehicle supports this binary sensor, given the coordinator data - is_supported: Callable[[dict[str, Any]], bool] = lambda data: True - - -def _plugged_in_supported(data): - """Determine if 'plugged in' binary sensor is supported.""" - return ( - data["isElectric"] and data["evStatus"]["chargeInfo"]["pluggedIn"] is not None - ) - - -BINARY_SENSOR_ENTITIES = [ - MazdaBinarySensorEntityDescription( - key="driver_door", - translation_key="driver_door", - icon="mdi:car-door", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["driverDoorOpen"], - ), - MazdaBinarySensorEntityDescription( - key="passenger_door", - translation_key="passenger_door", - icon="mdi:car-door", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["passengerDoorOpen"], - ), - MazdaBinarySensorEntityDescription( - key="rear_left_door", - translation_key="rear_left_door", - icon="mdi:car-door", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["rearLeftDoorOpen"], - ), - MazdaBinarySensorEntityDescription( - key="rear_right_door", - translation_key="rear_right_door", - icon="mdi:car-door", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["rearRightDoorOpen"], - ), - MazdaBinarySensorEntityDescription( - key="trunk", - translation_key="trunk", - icon="mdi:car-back", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["trunkOpen"], - ), - MazdaBinarySensorEntityDescription( - key="hood", - translation_key="hood", - icon="mdi:car", - device_class=BinarySensorDeviceClass.DOOR, - value_fn=lambda data: data["status"]["doors"]["hoodOpen"], - ), - MazdaBinarySensorEntityDescription( - key="ev_plugged_in", - translation_key="ev_plugged_in", - device_class=BinarySensorDeviceClass.PLUG, - is_supported=_plugged_in_supported, - value_fn=lambda data: data["evStatus"]["chargeInfo"]["pluggedIn"], - ), -] - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the sensor platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - async_add_entities( - MazdaBinarySensorEntity(client, coordinator, index, description) - for index, data in enumerate(coordinator.data) - for description in BINARY_SENSOR_ENTITIES - if description.is_supported(data) - ) - - -class MazdaBinarySensorEntity(MazdaEntity, BinarySensorEntity): - """Representation of a Mazda vehicle binary sensor.""" - - entity_description: MazdaBinarySensorEntityDescription - - def __init__(self, client, coordinator, index, description): - """Initialize Mazda binary sensor.""" - super().__init__(client, coordinator, index) - self.entity_description = description - - self._attr_unique_id = f"{self.vin}_{description.key}" - - @property - def is_on(self): - """Return the state of the binary sensor.""" - return self.entity_description.value_fn(self.data) diff --git a/homeassistant/components/mazda/button.py b/homeassistant/components/mazda/button.py deleted file mode 100644 index ced1094981f..00000000000 --- a/homeassistant/components/mazda/button.py +++ /dev/null @@ -1,150 +0,0 @@ -"""Platform for Mazda button integration.""" -from __future__ import annotations - -from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from typing import Any - -from pymazda import ( - Client as MazdaAPIClient, - MazdaAccountLockedException, - MazdaAPIEncryptionException, - MazdaAuthenticationException, - MazdaException, - MazdaLoginFailedException, - MazdaTokenExpiredException, -) - -from homeassistant.components.button import ButtonEntity, ButtonEntityDescription -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -async def handle_button_press( - client: MazdaAPIClient, - key: str, - vehicle_id: int, - coordinator: DataUpdateCoordinator, -) -> None: - """Handle a press for a Mazda button entity.""" - api_method = getattr(client, key) - - try: - await api_method(vehicle_id) - except ( - MazdaException, - MazdaAuthenticationException, - MazdaAccountLockedException, - MazdaTokenExpiredException, - MazdaAPIEncryptionException, - MazdaLoginFailedException, - ) as ex: - raise HomeAssistantError(ex) from ex - - -async def handle_refresh_vehicle_status( - client: MazdaAPIClient, - key: str, - vehicle_id: int, - coordinator: DataUpdateCoordinator, -) -> None: - """Handle a request to refresh the vehicle status.""" - await handle_button_press(client, key, vehicle_id, coordinator) - - await coordinator.async_request_refresh() - - -@dataclass -class MazdaButtonEntityDescription(ButtonEntityDescription): - """Describes a Mazda button entity.""" - - # Function to determine whether the vehicle supports this button, - # given the coordinator data - is_supported: Callable[[dict[str, Any]], bool] = lambda data: True - - async_press: Callable[ - [MazdaAPIClient, str, int, DataUpdateCoordinator], Awaitable - ] = handle_button_press - - -BUTTON_ENTITIES = [ - MazdaButtonEntityDescription( - key="start_engine", - translation_key="start_engine", - icon="mdi:engine", - is_supported=lambda data: not data["isElectric"], - ), - MazdaButtonEntityDescription( - key="stop_engine", - translation_key="stop_engine", - icon="mdi:engine-off", - is_supported=lambda data: not data["isElectric"], - ), - MazdaButtonEntityDescription( - key="turn_on_hazard_lights", - translation_key="turn_on_hazard_lights", - icon="mdi:hazard-lights", - is_supported=lambda data: not data["isElectric"], - ), - MazdaButtonEntityDescription( - key="turn_off_hazard_lights", - translation_key="turn_off_hazard_lights", - icon="mdi:hazard-lights", - is_supported=lambda data: not data["isElectric"], - ), - MazdaButtonEntityDescription( - key="refresh_vehicle_status", - translation_key="refresh_vehicle_status", - icon="mdi:refresh", - async_press=handle_refresh_vehicle_status, - is_supported=lambda data: data["isElectric"], - ), -] - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the button platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - async_add_entities( - MazdaButtonEntity(client, coordinator, index, description) - for index, data in enumerate(coordinator.data) - for description in BUTTON_ENTITIES - if description.is_supported(data) - ) - - -class MazdaButtonEntity(MazdaEntity, ButtonEntity): - """Representation of a Mazda button.""" - - entity_description: MazdaButtonEntityDescription - - def __init__( - self, - client: MazdaAPIClient, - coordinator: DataUpdateCoordinator, - index: int, - description: MazdaButtonEntityDescription, - ) -> None: - """Initialize Mazda button.""" - super().__init__(client, coordinator, index) - self.entity_description = description - - self._attr_unique_id = f"{self.vin}_{description.key}" - - async def async_press(self) -> None: - """Press the button.""" - await self.entity_description.async_press( - self.client, self.entity_description.key, self.vehicle_id, self.coordinator - ) diff --git a/homeassistant/components/mazda/climate.py b/homeassistant/components/mazda/climate.py deleted file mode 100644 index 43dc4b4151d..00000000000 --- a/homeassistant/components/mazda/climate.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Platform for Mazda climate integration.""" -from __future__ import annotations - -from typing import Any - -from pymazda import Client as MazdaAPIClient - -from homeassistant.components.climate import ( - ClimateEntity, - ClimateEntityFeature, - HVACMode, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_TEMPERATURE, - PRECISION_HALVES, - PRECISION_WHOLE, - UnitOfTemperature, -) -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util.unit_conversion import TemperatureConverter - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DATA_REGION, DOMAIN - -PRESET_DEFROSTER_OFF = "Defroster Off" -PRESET_DEFROSTER_FRONT = "Front Defroster" -PRESET_DEFROSTER_REAR = "Rear Defroster" -PRESET_DEFROSTER_FRONT_AND_REAR = "Front and Rear Defroster" - - -def _front_defroster_enabled(preset_mode: str | None) -> bool: - return preset_mode in [ - PRESET_DEFROSTER_FRONT_AND_REAR, - PRESET_DEFROSTER_FRONT, - ] - - -def _rear_defroster_enabled(preset_mode: str | None) -> bool: - return preset_mode in [ - PRESET_DEFROSTER_FRONT_AND_REAR, - PRESET_DEFROSTER_REAR, - ] - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the climate platform.""" - entry_data = hass.data[DOMAIN][config_entry.entry_id] - client = entry_data[DATA_CLIENT] - coordinator = entry_data[DATA_COORDINATOR] - region = entry_data[DATA_REGION] - - async_add_entities( - MazdaClimateEntity(client, coordinator, index, region) - for index, data in enumerate(coordinator.data) - if data["isElectric"] - ) - - -class MazdaClimateEntity(MazdaEntity, ClimateEntity): - """Class for a Mazda climate entity.""" - - _attr_translation_key = "climate" - _attr_supported_features = ( - ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE - ) - _attr_hvac_modes = [HVACMode.HEAT_COOL, HVACMode.OFF] - _attr_preset_modes = [ - PRESET_DEFROSTER_OFF, - PRESET_DEFROSTER_FRONT, - PRESET_DEFROSTER_REAR, - PRESET_DEFROSTER_FRONT_AND_REAR, - ] - - def __init__( - self, - client: MazdaAPIClient, - coordinator: DataUpdateCoordinator, - index: int, - region: str, - ) -> None: - """Initialize Mazda climate entity.""" - super().__init__(client, coordinator, index) - - self.region = region - self._attr_unique_id = self.vin - - if self.data["hvacSetting"]["temperatureUnit"] == "F": - self._attr_precision = PRECISION_WHOLE - self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT - self._attr_min_temp = 61.0 - self._attr_max_temp = 83.0 - else: - self._attr_precision = PRECISION_HALVES - self._attr_temperature_unit = UnitOfTemperature.CELSIUS - if region == "MJO": - self._attr_min_temp = 18.5 - self._attr_max_temp = 31.5 - else: - self._attr_min_temp = 15.5 - self._attr_max_temp = 28.5 - - self._update_state_attributes() - - @callback - def _handle_coordinator_update(self) -> None: - """Update attributes when the coordinator data updates.""" - self._update_state_attributes() - - super()._handle_coordinator_update() - - def _update_state_attributes(self) -> None: - # Update the HVAC mode - hvac_on = self.client.get_assumed_hvac_mode(self.vehicle_id) - self._attr_hvac_mode = HVACMode.HEAT_COOL if hvac_on else HVACMode.OFF - - # Update the target temperature - hvac_setting = self.client.get_assumed_hvac_setting(self.vehicle_id) - self._attr_target_temperature = hvac_setting.get("temperature") - - # Update the current temperature - current_temperature_celsius = self.data["evStatus"]["hvacInfo"][ - "interiorTemperatureCelsius" - ] - if self.data["hvacSetting"]["temperatureUnit"] == "F": - self._attr_current_temperature = TemperatureConverter.convert( - current_temperature_celsius, - UnitOfTemperature.CELSIUS, - UnitOfTemperature.FAHRENHEIT, - ) - else: - self._attr_current_temperature = current_temperature_celsius - - # Update the preset mode based on the state of the front and rear defrosters - front_defroster = hvac_setting.get("frontDefroster") - rear_defroster = hvac_setting.get("rearDefroster") - if front_defroster and rear_defroster: - self._attr_preset_mode = PRESET_DEFROSTER_FRONT_AND_REAR - elif front_defroster: - self._attr_preset_mode = PRESET_DEFROSTER_FRONT - elif rear_defroster: - self._attr_preset_mode = PRESET_DEFROSTER_REAR - else: - self._attr_preset_mode = PRESET_DEFROSTER_OFF - - async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: - """Set a new HVAC mode.""" - if hvac_mode == HVACMode.HEAT_COOL: - await self.client.turn_on_hvac(self.vehicle_id) - elif hvac_mode == HVACMode.OFF: - await self.client.turn_off_hvac(self.vehicle_id) - - self._handle_coordinator_update() - - async def async_set_temperature(self, **kwargs: Any) -> None: - """Set a new target temperature.""" - if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None: - precision = self.precision - rounded_temperature = round(temperature / precision) * precision - - await self.client.set_hvac_setting( - self.vehicle_id, - rounded_temperature, - self.data["hvacSetting"]["temperatureUnit"], - _front_defroster_enabled(self._attr_preset_mode), - _rear_defroster_enabled(self._attr_preset_mode), - ) - - self._handle_coordinator_update() - - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Turn on/off the front/rear defrosters according to the chosen preset mode.""" - await self.client.set_hvac_setting( - self.vehicle_id, - self._attr_target_temperature, - self.data["hvacSetting"]["temperatureUnit"], - _front_defroster_enabled(preset_mode), - _rear_defroster_enabled(preset_mode), - ) - - self._handle_coordinator_update() diff --git a/homeassistant/components/mazda/config_flow.py b/homeassistant/components/mazda/config_flow.py index 0b255483da1..78a939df69d 100644 --- a/homeassistant/components/mazda/config_flow.py +++ b/homeassistant/components/mazda/config_flow.py @@ -1,110 +1,11 @@ -"""Config flow for Mazda Connected Services integration.""" -from collections.abc import Mapping -import logging -from typing import Any +"""The Mazda Connected Services integration.""" -import aiohttp -from pymazda import ( - Client as MazdaAPI, - MazdaAccountLockedException, - MazdaAuthenticationException, -) -import voluptuous as vol +from homeassistant.config_entries import ConfigFlow -from homeassistant import config_entries -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION -from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers import aiohttp_client - -from .const import DOMAIN, MAZDA_REGIONS - -_LOGGER = logging.getLogger(__name__) - -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_EMAIL): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_REGION): vol.In(MAZDA_REGIONS), - } -) +from . import DOMAIN -class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MazdaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Mazda Connected Services.""" VERSION = 1 - - def __init__(self): - """Start the mazda config flow.""" - self._reauth_entry = None - self._email = None - self._region = None - - 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, entry_data: Mapping[str, Any]) -> FlowResult: - """Perform reauth if the user credentials have changed.""" - self._reauth_entry = self.hass.config_entries.async_get_entry( - self.context["entry_id"] - ) - self._email = entry_data[CONF_EMAIL] - self._region = entry_data[CONF_REGION] - return await self.async_step_user() diff --git a/homeassistant/components/mazda/const.py b/homeassistant/components/mazda/const.py deleted file mode 100644 index ebfa7f05301..00000000000 --- a/homeassistant/components/mazda/const.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Constants for the Mazda Connected Services integration.""" - -DOMAIN = "mazda" - -DATA_CLIENT = "mazda_client" -DATA_COORDINATOR = "coordinator" -DATA_REGION = "region" -DATA_VEHICLES = "vehicles" - -MAZDA_REGIONS = {"MNAO": "North America", "MME": "Europe", "MJO": "Japan"} diff --git a/homeassistant/components/mazda/device_tracker.py b/homeassistant/components/mazda/device_tracker.py deleted file mode 100644 index 2af191f97bc..00000000000 --- a/homeassistant/components/mazda/device_tracker.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Platform for Mazda device tracker integration.""" -from homeassistant.components.device_tracker import SourceType, TrackerEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the device tracker platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - entities = [] - - for index, _ in enumerate(coordinator.data): - entities.append(MazdaDeviceTracker(client, coordinator, index)) - - async_add_entities(entities) - - -class MazdaDeviceTracker(MazdaEntity, TrackerEntity): - """Class for the device tracker.""" - - _attr_translation_key = "device_tracker" - _attr_icon = "mdi:car" - _attr_force_update = False - - def __init__(self, client, coordinator, index) -> None: - """Initialize Mazda device tracker.""" - super().__init__(client, coordinator, index) - - self._attr_unique_id = self.vin - - @property - def source_type(self) -> SourceType: - """Return the source type, eg gps or router, of the device.""" - return SourceType.GPS - - @property - def latitude(self): - """Return latitude value of the device.""" - return self.data["status"]["latitude"] - - @property - def longitude(self): - """Return longitude value of the device.""" - return self.data["status"]["longitude"] diff --git a/homeassistant/components/mazda/diagnostics.py b/homeassistant/components/mazda/diagnostics.py deleted file mode 100644 index 421410f4a34..00000000000 --- a/homeassistant/components/mazda/diagnostics.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Diagnostics support for the Mazda integration.""" -from __future__ import annotations - -from typing import Any - -from homeassistant.components.diagnostics.util import async_redact_data -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.device_registry import DeviceEntry - -from .const import DATA_COORDINATOR, DOMAIN - -TO_REDACT_INFO = [CONF_EMAIL, CONF_PASSWORD] -TO_REDACT_DATA = ["vin", "id", "latitude", "longitude"] - - -async def async_get_config_entry_diagnostics( - hass: HomeAssistant, config_entry: ConfigEntry -) -> dict[str, Any]: - """Return diagnostics for a config entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - diagnostics_data = { - "info": async_redact_data(config_entry.data, TO_REDACT_INFO), - "data": [ - async_redact_data(vehicle, TO_REDACT_DATA) for vehicle in coordinator.data - ], - } - - return diagnostics_data - - -async def async_get_device_diagnostics( - hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry -) -> dict[str, Any]: - """Return diagnostics for a device.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - vin = next(iter(device.identifiers))[1] - - target_vehicle = None - for vehicle in coordinator.data: - if vehicle["vin"] == vin: - target_vehicle = vehicle - break - - if target_vehicle is None: - raise HomeAssistantError("Vehicle not found") - - diagnostics_data = { - "info": async_redact_data(config_entry.data, TO_REDACT_INFO), - "data": async_redact_data(target_vehicle, TO_REDACT_DATA), - } - - return diagnostics_data diff --git a/homeassistant/components/mazda/lock.py b/homeassistant/components/mazda/lock.py deleted file mode 100644 index d095ac81955..00000000000 --- a/homeassistant/components/mazda/lock.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Platform for Mazda lock integration.""" -from __future__ import annotations - -from typing import Any - -from homeassistant.components.lock import LockEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the lock platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - entities = [] - - for index, _ in enumerate(coordinator.data): - entities.append(MazdaLock(client, coordinator, index)) - - async_add_entities(entities) - - -class MazdaLock(MazdaEntity, LockEntity): - """Class for the lock.""" - - _attr_translation_key = "lock" - - def __init__(self, client, coordinator, index) -> None: - """Initialize Mazda lock.""" - super().__init__(client, coordinator, index) - - self._attr_unique_id = self.vin - - @property - def is_locked(self) -> bool | None: - """Return true if lock is locked.""" - return self.client.get_assumed_lock_state(self.vehicle_id) - - async def async_lock(self, **kwargs: Any) -> None: - """Lock the vehicle doors.""" - await self.client.lock_doors(self.vehicle_id) - - self.async_write_ha_state() - - async def async_unlock(self, **kwargs: Any) -> None: - """Unlock the vehicle doors.""" - await self.client.unlock_doors(self.vehicle_id) - - self.async_write_ha_state() diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index 881120a0677..75a83a9f468 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -1,11 +1,9 @@ { "domain": "mazda", "name": "Mazda Connected Services", - "codeowners": ["@bdr99"], - "config_flow": true, + "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/mazda", + "integration_type": "system", "iot_class": "cloud_polling", - "loggers": ["pymazda"], - "quality_scale": "platinum", - "requirements": ["pymazda==0.3.11"] + "requirements": [] } diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py deleted file mode 100644 index f50533e339a..00000000000 --- a/homeassistant/components/mazda/sensor.py +++ /dev/null @@ -1,263 +0,0 @@ -"""Platform for Mazda sensor integration.""" -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import dataclass -from typing import Any - -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, UnitOfLength, UnitOfPressure -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import StateType - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -@dataclass -class MazdaSensorRequiredKeysMixin: - """Mixin for required keys.""" - - # Function to determine the value for this sensor, given the coordinator data - # and the configured unit system - value: Callable[[dict[str, Any]], StateType] - - -@dataclass -class MazdaSensorEntityDescription( - SensorEntityDescription, MazdaSensorRequiredKeysMixin -): - """Describes a Mazda sensor entity.""" - - # Function to determine whether the vehicle supports this sensor, - # given the coordinator data - is_supported: Callable[[dict[str, Any]], bool] = lambda data: True - - -def _fuel_remaining_percentage_supported(data): - """Determine if fuel remaining percentage is supported.""" - return (not data["isElectric"]) and ( - data["status"]["fuelRemainingPercent"] is not None - ) - - -def _fuel_distance_remaining_supported(data): - """Determine if fuel distance remaining is supported.""" - return (not data["isElectric"]) and ( - data["status"]["fuelDistanceRemainingKm"] is not None - ) - - -def _front_left_tire_pressure_supported(data): - """Determine if front left tire pressure is supported.""" - return data["status"]["tirePressure"]["frontLeftTirePressurePsi"] is not None - - -def _front_right_tire_pressure_supported(data): - """Determine if front right tire pressure is supported.""" - return data["status"]["tirePressure"]["frontRightTirePressurePsi"] is not None - - -def _rear_left_tire_pressure_supported(data): - """Determine if rear left tire pressure is supported.""" - return data["status"]["tirePressure"]["rearLeftTirePressurePsi"] is not None - - -def _rear_right_tire_pressure_supported(data): - """Determine if rear right tire pressure is supported.""" - return data["status"]["tirePressure"]["rearRightTirePressurePsi"] is not None - - -def _ev_charge_level_supported(data): - """Determine if charge level is supported.""" - return ( - data["isElectric"] - and data["evStatus"]["chargeInfo"]["batteryLevelPercentage"] is not None - ) - - -def _ev_remaining_range_supported(data): - """Determine if remaining range is supported.""" - return ( - data["isElectric"] - and data["evStatus"]["chargeInfo"]["drivingRangeKm"] is not None - ) - - -def _fuel_distance_remaining_value(data): - """Get the fuel distance remaining value.""" - return round(data["status"]["fuelDistanceRemainingKm"]) - - -def _odometer_value(data): - """Get the odometer value.""" - # In order to match the behavior of the Mazda mobile app, we always round down - return int(data["status"]["odometerKm"]) - - -def _front_left_tire_pressure_value(data): - """Get the front left tire pressure value.""" - return round(data["status"]["tirePressure"]["frontLeftTirePressurePsi"]) - - -def _front_right_tire_pressure_value(data): - """Get the front right tire pressure value.""" - return round(data["status"]["tirePressure"]["frontRightTirePressurePsi"]) - - -def _rear_left_tire_pressure_value(data): - """Get the rear left tire pressure value.""" - return round(data["status"]["tirePressure"]["rearLeftTirePressurePsi"]) - - -def _rear_right_tire_pressure_value(data): - """Get the rear right tire pressure value.""" - return round(data["status"]["tirePressure"]["rearRightTirePressurePsi"]) - - -def _ev_charge_level_value(data): - """Get the charge level value.""" - return round(data["evStatus"]["chargeInfo"]["batteryLevelPercentage"]) - - -def _ev_remaining_range_value(data): - """Get the remaining range value.""" - return round(data["evStatus"]["chargeInfo"]["drivingRangeKm"]) - - -SENSOR_ENTITIES = [ - MazdaSensorEntityDescription( - key="fuel_remaining_percentage", - translation_key="fuel_remaining_percentage", - icon="mdi:gas-station", - native_unit_of_measurement=PERCENTAGE, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_fuel_remaining_percentage_supported, - value=lambda data: data["status"]["fuelRemainingPercent"], - ), - MazdaSensorEntityDescription( - key="fuel_distance_remaining", - translation_key="fuel_distance_remaining", - icon="mdi:gas-station", - device_class=SensorDeviceClass.DISTANCE, - native_unit_of_measurement=UnitOfLength.KILOMETERS, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_fuel_distance_remaining_supported, - value=_fuel_distance_remaining_value, - ), - MazdaSensorEntityDescription( - key="odometer", - translation_key="odometer", - icon="mdi:speedometer", - device_class=SensorDeviceClass.DISTANCE, - native_unit_of_measurement=UnitOfLength.KILOMETERS, - state_class=SensorStateClass.TOTAL_INCREASING, - is_supported=lambda data: data["status"]["odometerKm"] is not None, - value=_odometer_value, - ), - MazdaSensorEntityDescription( - key="front_left_tire_pressure", - translation_key="front_left_tire_pressure", - icon="mdi:car-tire-alert", - device_class=SensorDeviceClass.PRESSURE, - native_unit_of_measurement=UnitOfPressure.PSI, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_front_left_tire_pressure_supported, - value=_front_left_tire_pressure_value, - ), - MazdaSensorEntityDescription( - key="front_right_tire_pressure", - translation_key="front_right_tire_pressure", - icon="mdi:car-tire-alert", - device_class=SensorDeviceClass.PRESSURE, - native_unit_of_measurement=UnitOfPressure.PSI, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_front_right_tire_pressure_supported, - value=_front_right_tire_pressure_value, - ), - MazdaSensorEntityDescription( - key="rear_left_tire_pressure", - translation_key="rear_left_tire_pressure", - icon="mdi:car-tire-alert", - device_class=SensorDeviceClass.PRESSURE, - native_unit_of_measurement=UnitOfPressure.PSI, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_rear_left_tire_pressure_supported, - value=_rear_left_tire_pressure_value, - ), - MazdaSensorEntityDescription( - key="rear_right_tire_pressure", - translation_key="rear_right_tire_pressure", - icon="mdi:car-tire-alert", - device_class=SensorDeviceClass.PRESSURE, - native_unit_of_measurement=UnitOfPressure.PSI, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_rear_right_tire_pressure_supported, - value=_rear_right_tire_pressure_value, - ), - MazdaSensorEntityDescription( - key="ev_charge_level", - translation_key="ev_charge_level", - device_class=SensorDeviceClass.BATTERY, - native_unit_of_measurement=PERCENTAGE, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_ev_charge_level_supported, - value=_ev_charge_level_value, - ), - MazdaSensorEntityDescription( - key="ev_remaining_range", - translation_key="ev_remaining_range", - icon="mdi:ev-station", - device_class=SensorDeviceClass.DISTANCE, - native_unit_of_measurement=UnitOfLength.KILOMETERS, - state_class=SensorStateClass.MEASUREMENT, - is_supported=_ev_remaining_range_supported, - value=_ev_remaining_range_value, - ), -] - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the sensor platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - entities: list[SensorEntity] = [] - - for index, data in enumerate(coordinator.data): - for description in SENSOR_ENTITIES: - if description.is_supported(data): - entities.append( - MazdaSensorEntity(client, coordinator, index, description) - ) - - async_add_entities(entities) - - -class MazdaSensorEntity(MazdaEntity, SensorEntity): - """Representation of a Mazda vehicle sensor.""" - - entity_description: MazdaSensorEntityDescription - - def __init__(self, client, coordinator, index, description): - """Initialize Mazda sensor.""" - super().__init__(client, coordinator, index) - self.entity_description = description - - self._attr_unique_id = f"{self.vin}_{description.key}" - - @property - def native_value(self) -> StateType: - """Return the state of the sensor.""" - return self.entity_description.value(self.data) diff --git a/homeassistant/components/mazda/services.yaml b/homeassistant/components/mazda/services.yaml deleted file mode 100644 index b401c01f3a3..00000000000 --- a/homeassistant/components/mazda/services.yaml +++ /dev/null @@ -1,30 +0,0 @@ -send_poi: - fields: - device_id: - required: true - selector: - device: - integration: mazda - latitude: - example: 12.34567 - required: true - selector: - number: - min: -90 - max: 90 - unit_of_measurement: ° - mode: box - longitude: - example: -34.56789 - required: true - selector: - number: - min: -180 - max: 180 - unit_of_measurement: ° - mode: box - poi_name: - example: Work - required: true - selector: - text: diff --git a/homeassistant/components/mazda/strings.json b/homeassistant/components/mazda/strings.json index 6c1214f76c6..1d0fedf3e97 100644 --- a/homeassistant/components/mazda/strings.json +++ b/homeassistant/components/mazda/strings.json @@ -1,139 +1,8 @@ { - "config": { - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" - }, - "error": { - "account_locked": "Account locked. Please try again later.", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "step": { - "user": { - "data": { - "email": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]", - "region": "Region" - }, - "description": "Please enter the email address and password you use to log into the MyMazda mobile app." - } - } - }, - "entity": { - "binary_sensor": { - "driver_door": { - "name": "Driver door" - }, - "passenger_door": { - "name": "Passenger door" - }, - "rear_left_door": { - "name": "Rear left door" - }, - "rear_right_door": { - "name": "Rear right door" - }, - "trunk": { - "name": "Trunk" - }, - "hood": { - "name": "Hood" - }, - "ev_plugged_in": { - "name": "Plugged in" - } - }, - "button": { - "start_engine": { - "name": "Start engine" - }, - "stop_engine": { - "name": "Stop engine" - }, - "turn_on_hazard_lights": { - "name": "Turn on hazard lights" - }, - "turn_off_hazard_lights": { - "name": "Turn off hazard lights" - }, - "refresh_vehicle_status": { - "name": "Refresh status" - } - }, - "climate": { - "climate": { - "name": "[%key:component::climate::title%]" - } - }, - "device_tracker": { - "device_tracker": { - "name": "[%key:component::device_tracker::title%]" - } - }, - "lock": { - "lock": { - "name": "[%key:component::lock::title%]" - } - }, - "sensor": { - "fuel_remaining_percentage": { - "name": "Fuel remaining percentage" - }, - "fuel_distance_remaining": { - "name": "Fuel distance remaining" - }, - "odometer": { - "name": "Odometer" - }, - "front_left_tire_pressure": { - "name": "Front left tire pressure" - }, - "front_right_tire_pressure": { - "name": "Front right tire pressure" - }, - "rear_left_tire_pressure": { - "name": "Rear left tire pressure" - }, - "rear_right_tire_pressure": { - "name": "Rear right tire pressure" - }, - "ev_charge_level": { - "name": "Charge level" - }, - "ev_remaining_range": { - "name": "Remaining range" - } - }, - "switch": { - "charging": { - "name": "Charging" - } - } - }, - "services": { - "send_poi": { - "name": "Send POI", - "description": "Sends a GPS location to the vehicle's navigation system as a POI (Point of Interest). Requires a navigation SD card installed in the vehicle.", - "fields": { - "device_id": { - "name": "Vehicle", - "description": "The vehicle to send the GPS location to." - }, - "latitude": { - "name": "[%key:common::config_flow::data::latitude%]", - "description": "The latitude of the location to send." - }, - "longitude": { - "name": "[%key:common::config_flow::data::longitude%]", - "description": "The longitude of the location to send." - }, - "poi_name": { - "name": "POI name", - "description": "A friendly name for the location." - } - } + "issues": { + "integration_removed": { + "title": "The Mazda integration has been removed", + "description": "The Mazda integration has been removed from Home Assistant.\n\nThe library that Home Assistant uses to connect with their services, [has been taken offline by Mazda]({dmca}).\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing Mazda integration entries]({entries})." } } } diff --git a/homeassistant/components/mazda/switch.py b/homeassistant/components/mazda/switch.py deleted file mode 100644 index 327d371769b..00000000000 --- a/homeassistant/components/mazda/switch.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Platform for Mazda switch integration.""" -from typing import Any - -from pymazda import Client as MazdaAPIClient - -from homeassistant.components.switch import SwitchEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from . import MazdaEntity -from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the switch platform.""" - client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] - - async_add_entities( - MazdaChargingSwitch(client, coordinator, index) - for index, data in enumerate(coordinator.data) - if data["isElectric"] - ) - - -class MazdaChargingSwitch(MazdaEntity, SwitchEntity): - """Class for the charging switch.""" - - _attr_translation_key = "charging" - _attr_icon = "mdi:ev-station" - - def __init__( - self, - client: MazdaAPIClient, - coordinator: DataUpdateCoordinator, - index: int, - ) -> None: - """Initialize Mazda charging switch.""" - super().__init__(client, coordinator, index) - - self._attr_unique_id = self.vin - - @property - def is_on(self): - """Return true if the vehicle is charging.""" - return self.data["evStatus"]["chargeInfo"]["charging"] - - async def refresh_status_and_write_state(self): - """Request a status update, retrieve it through the coordinator, and write the state.""" - await self.client.refresh_vehicle_status(self.vehicle_id) - - await self.coordinator.async_request_refresh() - - self.async_write_ha_state() - - async def async_turn_on(self, **kwargs: Any) -> None: - """Start charging the vehicle.""" - await self.client.start_charging(self.vehicle_id) - - await self.refresh_status_and_write_state() - - async def async_turn_off(self, **kwargs: Any) -> None: - """Stop charging the vehicle.""" - await self.client.stop_charging(self.vehicle_id) - - await self.refresh_status_and_write_state() diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b9e1fcf5259..25d8a6f0d73 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -271,7 +271,6 @@ FLOWS = { "lyric", "mailgun", "matter", - "mazda", "meater", "medcom_ble", "melcloud", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 9ee022473a2..9c53d998bcd 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3262,12 +3262,6 @@ "config_flow": true, "iot_class": "local_push" }, - "mazda": { - "name": "Mazda Connected Services", - "integration_type": "hub", - "config_flow": true, - "iot_class": "cloud_polling" - }, "meater": { "name": "Meater", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 6cb59b2527d..53dd9407863 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1847,9 +1847,6 @@ pymailgunner==1.4 # homeassistant.components.firmata pymata-express==1.19 -# homeassistant.components.mazda -pymazda==0.3.11 - # homeassistant.components.mediaroom pymediaroom==0.6.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 519767ca943..983c2fa0a5f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1390,9 +1390,6 @@ pymailgunner==1.4 # homeassistant.components.firmata pymata-express==1.19 -# homeassistant.components.mazda -pymazda==0.3.11 - # homeassistant.components.melcloud pymelcloud==2.5.8 diff --git a/tests/components/mazda/__init__.py b/tests/components/mazda/__init__.py index 59b1d474140..cc3d81df4dd 100644 --- a/tests/components/mazda/__init__.py +++ b/tests/components/mazda/__init__.py @@ -1,80 +1 @@ """Tests for the Mazda Connected Services integration.""" - -import json -from unittest.mock import AsyncMock, MagicMock, Mock, patch - -from pymazda import Client as MazdaAPI - -from homeassistant.components.mazda.const import DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION -from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client - -from tests.common import MockConfigEntry, load_fixture - -FIXTURE_USER_INPUT = { - CONF_EMAIL: "example@example.com", - CONF_PASSWORD: "password", - CONF_REGION: "MNAO", -} - - -async def init_integration( - hass: HomeAssistant, use_nickname=True, electric_vehicle=False -) -> 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") - if electric_vehicle: - get_vehicles_fixture[0]["isElectric"] = True - - get_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_vehicle_status.json") - ) - get_ev_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_ev_vehicle_status.json") - ) - get_hvac_setting_fixture = json.loads(load_fixture("mazda/get_hvac_setting.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) - client_mock.get_ev_vehicle_status = AsyncMock( - return_value=get_ev_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() - client_mock.refresh_vehicle_status = AsyncMock() - client_mock.get_hvac_setting = AsyncMock(return_value=get_hvac_setting_fixture) - client_mock.get_assumed_hvac_setting = Mock(return_value=get_hvac_setting_fixture) - client_mock.get_assumed_hvac_mode = Mock(return_value=True) - client_mock.set_hvac_setting = AsyncMock() - client_mock.turn_on_hvac = AsyncMock() - client_mock.turn_off_hvac = AsyncMock() - - 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 client_mock diff --git a/tests/components/mazda/fixtures/diagnostics_config_entry.json b/tests/components/mazda/fixtures/diagnostics_config_entry.json deleted file mode 100644 index 87f49bc29cb..00000000000 --- a/tests/components/mazda/fixtures/diagnostics_config_entry.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "info": { - "email": "**REDACTED**", - "password": "**REDACTED**", - "region": "MNAO" - }, - "data": [ - { - "vin": "**REDACTED**", - "id": "**REDACTED**", - "nickname": "My Mazda3", - "carlineCode": "M3S", - "carlineName": "MAZDA3 2.5 S SE AWD", - "modelYear": "2021", - "modelCode": "M3S SE XA", - "modelName": "W/ SELECT PKG AWD SDN", - "automaticTransmission": true, - "interiorColorCode": "BY3", - "interiorColorName": "BLACK", - "exteriorColorCode": "42M", - "exteriorColorName": "DEEP CRYSTAL BLUE MICA", - "isElectric": false, - "status": { - "lastUpdatedTimestamp": "20210123143809", - "latitude": "**REDACTED**", - "longitude": "**REDACTED**", - "positionTimestamp": "20210123143808", - "fuelRemainingPercent": 87.0, - "fuelDistanceRemainingKm": 380.8, - "odometerKm": 2795.8, - "doors": { - "driverDoorOpen": false, - "passengerDoorOpen": true, - "rearLeftDoorOpen": false, - "rearRightDoorOpen": false, - "trunkOpen": false, - "hoodOpen": true, - "fuelLidOpen": false - }, - "doorLocks": { - "driverDoorUnlocked": false, - "passengerDoorUnlocked": false, - "rearLeftDoorUnlocked": false, - "rearRightDoorUnlocked": false - }, - "windows": { - "driverWindowOpen": false, - "passengerWindowOpen": false, - "rearLeftWindowOpen": false, - "rearRightWindowOpen": false - }, - "hazardLightsOn": false, - "tirePressure": { - "frontLeftTirePressurePsi": 35.0, - "frontRightTirePressurePsi": 35.0, - "rearLeftTirePressurePsi": 33.0, - "rearRightTirePressurePsi": 33.0 - } - } - } - ] -} diff --git a/tests/components/mazda/fixtures/diagnostics_device.json b/tests/components/mazda/fixtures/diagnostics_device.json deleted file mode 100644 index f2ddd658f70..00000000000 --- a/tests/components/mazda/fixtures/diagnostics_device.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "info": { - "email": "**REDACTED**", - "password": "**REDACTED**", - "region": "MNAO" - }, - "data": { - "vin": "**REDACTED**", - "id": "**REDACTED**", - "nickname": "My Mazda3", - "carlineCode": "M3S", - "carlineName": "MAZDA3 2.5 S SE AWD", - "modelYear": "2021", - "modelCode": "M3S SE XA", - "modelName": "W/ SELECT PKG AWD SDN", - "automaticTransmission": true, - "interiorColorCode": "BY3", - "interiorColorName": "BLACK", - "exteriorColorCode": "42M", - "exteriorColorName": "DEEP CRYSTAL BLUE MICA", - "isElectric": false, - "status": { - "lastUpdatedTimestamp": "20210123143809", - "latitude": "**REDACTED**", - "longitude": "**REDACTED**", - "positionTimestamp": "20210123143808", - "fuelRemainingPercent": 87.0, - "fuelDistanceRemainingKm": 380.8, - "odometerKm": 2795.8, - "doors": { - "driverDoorOpen": false, - "passengerDoorOpen": true, - "rearLeftDoorOpen": false, - "rearRightDoorOpen": false, - "trunkOpen": false, - "hoodOpen": true, - "fuelLidOpen": false - }, - "doorLocks": { - "driverDoorUnlocked": false, - "passengerDoorUnlocked": false, - "rearLeftDoorUnlocked": false, - "rearRightDoorUnlocked": false - }, - "windows": { - "driverWindowOpen": false, - "passengerWindowOpen": false, - "rearLeftWindowOpen": false, - "rearRightWindowOpen": false - }, - "hazardLightsOn": false, - "tirePressure": { - "frontLeftTirePressurePsi": 35.0, - "frontRightTirePressurePsi": 35.0, - "rearLeftTirePressurePsi": 33.0, - "rearRightTirePressurePsi": 33.0 - } - } - } -} diff --git a/tests/components/mazda/fixtures/get_ev_vehicle_status.json b/tests/components/mazda/fixtures/get_ev_vehicle_status.json deleted file mode 100644 index a577cab3054..00000000000 --- a/tests/components/mazda/fixtures/get_ev_vehicle_status.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "lastUpdatedTimestamp": "20210807083956", - "chargeInfo": { - "batteryLevelPercentage": 80, - "drivingRangeKm": 218, - "pluggedIn": true, - "charging": true, - "basicChargeTimeMinutes": 30, - "quickChargeTimeMinutes": 15, - "batteryHeaterAuto": true, - "batteryHeaterOn": true - }, - "hvacInfo": { - "hvacOn": true, - "frontDefroster": false, - "rearDefroster": false, - "interiorTemperatureCelsius": 15.1 - } -} diff --git a/tests/components/mazda/fixtures/get_hvac_setting.json b/tests/components/mazda/fixtures/get_hvac_setting.json deleted file mode 100644 index 3b95832ba65..00000000000 --- a/tests/components/mazda/fixtures/get_hvac_setting.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "temperature": 20, - "temperatureUnit": "C", - "frontDefroster": true, - "rearDefroster": false -} diff --git a/tests/components/mazda/fixtures/get_vehicle_status.json b/tests/components/mazda/fixtures/get_vehicle_status.json deleted file mode 100644 index 17fe86c642b..00000000000 --- a/tests/components/mazda/fixtures/get_vehicle_status.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "lastUpdatedTimestamp": "20210123143809", - "latitude": 1.234567, - "longitude": -2.345678, - "positionTimestamp": "20210123143808", - "fuelRemainingPercent": 87.0, - "fuelDistanceRemainingKm": 380.8, - "odometerKm": 2795.8, - "doors": { - "driverDoorOpen": false, - "passengerDoorOpen": true, - "rearLeftDoorOpen": false, - "rearRightDoorOpen": false, - "trunkOpen": false, - "hoodOpen": true, - "fuelLidOpen": false - }, - "doorLocks": { - "driverDoorUnlocked": false, - "passengerDoorUnlocked": false, - "rearLeftDoorUnlocked": false, - "rearRightDoorUnlocked": false - }, - "windows": { - "driverWindowOpen": false, - "passengerWindowOpen": false, - "rearLeftWindowOpen": false, - "rearRightWindowOpen": false - }, - "hazardLightsOn": false, - "tirePressure": { - "frontLeftTirePressurePsi": 35.0, - "frontRightTirePressurePsi": 35.0, - "rearLeftTirePressurePsi": 33.0, - "rearRightTirePressurePsi": 33.0 - } -} diff --git a/tests/components/mazda/fixtures/get_vehicles.json b/tests/components/mazda/fixtures/get_vehicles.json deleted file mode 100644 index a80a09f380a..00000000000 --- a/tests/components/mazda/fixtures/get_vehicles.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "vin": "JM000000000000000", - "id": 12345, - "nickname": "My Mazda3", - "carlineCode": "M3S", - "carlineName": "MAZDA3 2.5 S SE AWD", - "modelYear": "2021", - "modelCode": "M3S SE XA", - "modelName": "W/ SELECT PKG AWD SDN", - "automaticTransmission": true, - "interiorColorCode": "BY3", - "interiorColorName": "BLACK", - "exteriorColorCode": "42M", - "exteriorColorName": "DEEP CRYSTAL BLUE MICA", - "isElectric": false - } -] diff --git a/tests/components/mazda/test_binary_sensor.py b/tests/components/mazda/test_binary_sensor.py deleted file mode 100644 index d5bae776320..00000000000 --- a/tests/components/mazda/test_binary_sensor.py +++ /dev/null @@ -1,98 +0,0 @@ -"""The binary sensor tests for the Mazda Connected Services integration.""" -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import init_integration - - -async def test_binary_sensors(hass: HomeAssistant) -> None: - """Test creation of the binary sensors.""" - await init_integration(hass) - - entity_registry = er.async_get(hass) - - # Driver Door - state = hass.states.get("binary_sensor.my_mazda3_driver_door") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Driver door" - assert state.attributes.get(ATTR_ICON) == "mdi:car-door" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "off" - entry = entity_registry.async_get("binary_sensor.my_mazda3_driver_door") - assert entry - assert entry.unique_id == "JM000000000000000_driver_door" - - # Passenger Door - state = hass.states.get("binary_sensor.my_mazda3_passenger_door") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Passenger door" - assert state.attributes.get(ATTR_ICON) == "mdi:car-door" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "on" - entry = entity_registry.async_get("binary_sensor.my_mazda3_passenger_door") - assert entry - assert entry.unique_id == "JM000000000000000_passenger_door" - - # Rear Left Door - state = hass.states.get("binary_sensor.my_mazda3_rear_left_door") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear left door" - assert state.attributes.get(ATTR_ICON) == "mdi:car-door" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "off" - entry = entity_registry.async_get("binary_sensor.my_mazda3_rear_left_door") - assert entry - assert entry.unique_id == "JM000000000000000_rear_left_door" - - # Rear Right Door - state = hass.states.get("binary_sensor.my_mazda3_rear_right_door") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear right door" - assert state.attributes.get(ATTR_ICON) == "mdi:car-door" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "off" - entry = entity_registry.async_get("binary_sensor.my_mazda3_rear_right_door") - assert entry - assert entry.unique_id == "JM000000000000000_rear_right_door" - - # Trunk - state = hass.states.get("binary_sensor.my_mazda3_trunk") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Trunk" - assert state.attributes.get(ATTR_ICON) == "mdi:car-back" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "off" - entry = entity_registry.async_get("binary_sensor.my_mazda3_trunk") - assert entry - assert entry.unique_id == "JM000000000000000_trunk" - - # Hood - state = hass.states.get("binary_sensor.my_mazda3_hood") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Hood" - assert state.attributes.get(ATTR_ICON) == "mdi:car" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.DOOR - assert state.state == "on" - entry = entity_registry.async_get("binary_sensor.my_mazda3_hood") - assert entry - assert entry.unique_id == "JM000000000000000_hood" - - -async def test_electric_vehicle_binary_sensors(hass: HomeAssistant) -> None: - """Test sensors which are specific to electric vehicles.""" - - await init_integration(hass, electric_vehicle=True) - - entity_registry = er.async_get(hass) - - # Plugged In - state = hass.states.get("binary_sensor.my_mazda3_plugged_in") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Plugged in" - assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.PLUG - assert state.state == "on" - entry = entity_registry.async_get("binary_sensor.my_mazda3_plugged_in") - assert entry - assert entry.unique_id == "JM000000000000000_ev_plugged_in" diff --git a/tests/components/mazda/test_button.py b/tests/components/mazda/test_button.py deleted file mode 100644 index ba80c10b38d..00000000000 --- a/tests/components/mazda/test_button.py +++ /dev/null @@ -1,145 +0,0 @@ -"""The button tests for the Mazda Connected Services integration.""" - -from pymazda import MazdaException -import pytest - -from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import entity_registry as er - -from . import init_integration - - -async def test_button_setup_non_electric_vehicle(hass: HomeAssistant) -> None: - """Test creation of button entities.""" - await init_integration(hass) - - entity_registry = er.async_get(hass) - - entry = entity_registry.async_get("button.my_mazda3_start_engine") - assert entry - assert entry.unique_id == "JM000000000000000_start_engine" - state = hass.states.get("button.my_mazda3_start_engine") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Start engine" - assert state.attributes.get(ATTR_ICON) == "mdi:engine" - - entry = entity_registry.async_get("button.my_mazda3_stop_engine") - assert entry - assert entry.unique_id == "JM000000000000000_stop_engine" - state = hass.states.get("button.my_mazda3_stop_engine") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Stop engine" - assert state.attributes.get(ATTR_ICON) == "mdi:engine-off" - - entry = entity_registry.async_get("button.my_mazda3_turn_on_hazard_lights") - assert entry - assert entry.unique_id == "JM000000000000000_turn_on_hazard_lights" - state = hass.states.get("button.my_mazda3_turn_on_hazard_lights") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Turn on hazard lights" - assert state.attributes.get(ATTR_ICON) == "mdi:hazard-lights" - - entry = entity_registry.async_get("button.my_mazda3_turn_off_hazard_lights") - assert entry - assert entry.unique_id == "JM000000000000000_turn_off_hazard_lights" - state = hass.states.get("button.my_mazda3_turn_off_hazard_lights") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Turn off hazard lights" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:hazard-lights" - - # Since this is a non-electric vehicle, electric vehicle buttons should not be created - entry = entity_registry.async_get("button.my_mazda3_refresh_vehicle_status") - assert entry is None - state = hass.states.get("button.my_mazda3_refresh_vehicle_status") - assert state is None - - -async def test_button_setup_electric_vehicle(hass: HomeAssistant) -> None: - """Test creation of button entities for an electric vehicle.""" - await init_integration(hass, electric_vehicle=True) - - entity_registry = er.async_get(hass) - - entry = entity_registry.async_get("button.my_mazda3_refresh_status") - assert entry - assert entry.unique_id == "JM000000000000000_refresh_vehicle_status" - state = hass.states.get("button.my_mazda3_refresh_status") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Refresh status" - assert state.attributes.get(ATTR_ICON) == "mdi:refresh" - - -@pytest.mark.parametrize( - ("electric_vehicle", "entity_id_suffix"), - [ - (True, "start_engine"), - (True, "stop_engine"), - (True, "turn_on_hazard_lights"), - (True, "turn_off_hazard_lights"), - (False, "refresh_status"), - ], -) -async def test_button_not_created( - hass: HomeAssistant, electric_vehicle, entity_id_suffix -) -> None: - """Test that button entities are not created when they should not be.""" - await init_integration(hass, electric_vehicle=electric_vehicle) - - entity_registry = er.async_get(hass) - - entity_id = f"button.my_mazda3_{entity_id_suffix}" - entry = entity_registry.async_get(entity_id) - assert entry is None - state = hass.states.get(entity_id) - assert state is None - - -@pytest.mark.parametrize( - ("electric_vehicle", "entity_id_suffix", "api_method_name"), - [ - (False, "start_engine", "start_engine"), - (False, "stop_engine", "stop_engine"), - (False, "turn_on_hazard_lights", "turn_on_hazard_lights"), - (False, "turn_off_hazard_lights", "turn_off_hazard_lights"), - (True, "refresh_status", "refresh_vehicle_status"), - ], -) -async def test_button_press( - hass: HomeAssistant, electric_vehicle, entity_id_suffix, api_method_name -) -> None: - """Test pressing the button entities.""" - client_mock = await init_integration(hass, electric_vehicle=electric_vehicle) - - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: f"button.my_mazda3_{entity_id_suffix}"}, - blocking=True, - ) - await hass.async_block_till_done() - - api_method = getattr(client_mock, api_method_name) - api_method.assert_called_once_with(12345) - - -async def test_button_press_error(hass: HomeAssistant) -> None: - """Test the Mazda API raising an error when a button entity is pressed.""" - client_mock = await init_integration(hass) - - client_mock.start_engine.side_effect = MazdaException("Test error") - - with pytest.raises(HomeAssistantError) as err: - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.my_mazda3_start_engine"}, - blocking=True, - ) - await hass.async_block_till_done() - - assert str(err.value) == "Test error" diff --git a/tests/components/mazda/test_climate.py b/tests/components/mazda/test_climate.py deleted file mode 100644 index ef3840f5cee..00000000000 --- a/tests/components/mazda/test_climate.py +++ /dev/null @@ -1,341 +0,0 @@ -"""The climate tests for the Mazda Connected Services integration.""" -import json -from unittest.mock import patch - -import pytest - -from homeassistant.components.climate import ( - ATTR_HVAC_MODE, - ATTR_PRESET_MODE, - DOMAIN as CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - SERVICE_SET_PRESET_MODE, - SERVICE_SET_TEMPERATURE, -) -from homeassistant.components.climate.const import ( - ATTR_CURRENT_TEMPERATURE, - ATTR_HVAC_MODES, - ATTR_MAX_TEMP, - ATTR_MIN_TEMP, - ATTR_PRESET_MODES, - ClimateEntityFeature, - HVACMode, -) -from homeassistant.components.mazda.climate import ( - PRESET_DEFROSTER_FRONT, - PRESET_DEFROSTER_FRONT_AND_REAR, - PRESET_DEFROSTER_OFF, - PRESET_DEFROSTER_REAR, -) -from homeassistant.components.mazda.const import DOMAIN -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_FRIENDLY_NAME, - ATTR_SUPPORTED_FEATURES, - ATTR_TEMPERATURE, - CONF_EMAIL, - CONF_PASSWORD, - CONF_REGION, - UnitOfTemperature, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM - -from . import init_integration - -from tests.common import MockConfigEntry, load_fixture - - -async def test_climate_setup(hass: HomeAssistant) -> None: - """Test the setup of the climate entity.""" - await init_integration(hass, electric_vehicle=True) - - entity_registry = er.async_get(hass) - entry = entity_registry.async_get("climate.my_mazda3_climate") - assert entry - assert entry.unique_id == "JM000000000000000" - - state = hass.states.get("climate.my_mazda3_climate") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Climate" - - -@pytest.mark.parametrize( - ( - "region", - "hvac_on", - "target_temperature", - "temperature_unit", - "front_defroster", - "rear_defroster", - "current_temperature_celsius", - "expected_hvac_mode", - "expected_preset_mode", - "expected_min_temp", - "expected_max_temp", - ), - [ - # Test with HVAC off - ( - "MNAO", - False, - 20, - "C", - False, - False, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_OFF, - 15.5, - 28.5, - ), - # Test with HVAC on - ( - "MNAO", - True, - 20, - "C", - False, - False, - 22, - HVACMode.HEAT_COOL, - PRESET_DEFROSTER_OFF, - 15.5, - 28.5, - ), - # Test with front defroster on - ( - "MNAO", - False, - 20, - "C", - True, - False, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_FRONT, - 15.5, - 28.5, - ), - # Test with rear defroster on - ( - "MNAO", - False, - 20, - "C", - False, - True, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_REAR, - 15.5, - 28.5, - ), - # Test with front and rear defrosters on - ( - "MNAO", - False, - 20, - "C", - True, - True, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_FRONT_AND_REAR, - 15.5, - 28.5, - ), - # Test with temperature unit F - ( - "MNAO", - False, - 70, - "F", - False, - False, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_OFF, - 61.0, - 83.0, - ), - # Test with Japan region (uses different min/max temp settings) - ( - "MJO", - False, - 20, - "C", - False, - False, - 22, - HVACMode.OFF, - PRESET_DEFROSTER_OFF, - 18.5, - 31.5, - ), - ], -) -async def test_climate_state( - hass: HomeAssistant, - region, - hvac_on, - target_temperature, - temperature_unit, - front_defroster, - rear_defroster, - current_temperature_celsius, - expected_hvac_mode, - expected_preset_mode, - expected_min_temp, - expected_max_temp, -) -> None: - """Test getting the state of the climate entity.""" - if temperature_unit == "F": - hass.config.units = US_CUSTOMARY_SYSTEM - - get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) - get_vehicles_fixture[0]["isElectric"] = True - get_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_vehicle_status.json") - ) - get_ev_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_ev_vehicle_status.json") - ) - get_ev_vehicle_status_fixture["hvacInfo"][ - "interiorTemperatureCelsius" - ] = current_temperature_celsius - get_hvac_setting_fixture = { - "temperature": target_temperature, - "temperatureUnit": temperature_unit, - "frontDefroster": front_defroster, - "rearDefroster": rear_defroster, - } - - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - return_value=get_vehicles_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", - return_value=get_vehicle_status_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_ev_vehicle_status", - return_value=get_ev_vehicle_status_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_assumed_hvac_mode", - return_value=hvac_on, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_assumed_hvac_setting", - return_value=get_hvac_setting_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_hvac_setting", - return_value=get_hvac_setting_fixture, - ): - config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_EMAIL: "example@example.com", - CONF_PASSWORD: "password", - CONF_REGION: region, - }, - ) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get("climate.my_mazda3_climate") - assert state - assert state.state == expected_hvac_mode - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Climate" - assert ( - state.attributes.get(ATTR_SUPPORTED_FEATURES) - == ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE - ) - assert state.attributes.get(ATTR_HVAC_MODES) == [HVACMode.HEAT_COOL, HVACMode.OFF] - assert state.attributes.get(ATTR_PRESET_MODES) == [ - PRESET_DEFROSTER_OFF, - PRESET_DEFROSTER_FRONT, - PRESET_DEFROSTER_REAR, - PRESET_DEFROSTER_FRONT_AND_REAR, - ] - assert state.attributes.get(ATTR_MIN_TEMP) == expected_min_temp - assert state.attributes.get(ATTR_MAX_TEMP) == expected_max_temp - assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == round( - hass.config.units.temperature( - current_temperature_celsius, UnitOfTemperature.CELSIUS - ) - ) - assert state.attributes.get(ATTR_TEMPERATURE) == target_temperature - assert state.attributes.get(ATTR_PRESET_MODE) == expected_preset_mode - - -@pytest.mark.parametrize( - ("hvac_mode", "api_method"), - [ - (HVACMode.HEAT_COOL, "turn_on_hvac"), - (HVACMode.OFF, "turn_off_hvac"), - ], -) -async def test_set_hvac_mode(hass: HomeAssistant, hvac_mode, api_method) -> None: - """Test turning on and off the HVAC system.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.my_mazda3_climate", ATTR_HVAC_MODE: hvac_mode}, - blocking=True, - ) - await hass.async_block_till_done() - - getattr(client_mock, api_method).assert_called_once_with(12345) - - -async def test_set_target_temperature(hass: HomeAssistant) -> None: - """Test setting the target temperature of the climate entity.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.my_mazda3_climate", ATTR_TEMPERATURE: 22}, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.set_hvac_setting.assert_called_once_with(12345, 22, "C", True, False) - - -@pytest.mark.parametrize( - ("preset_mode", "front_defroster", "rear_defroster"), - [ - (PRESET_DEFROSTER_OFF, False, False), - (PRESET_DEFROSTER_FRONT, True, False), - (PRESET_DEFROSTER_REAR, False, True), - (PRESET_DEFROSTER_FRONT_AND_REAR, True, True), - ], -) -async def test_set_preset_mode( - hass: HomeAssistant, preset_mode, front_defroster, rear_defroster -) -> None: - """Test turning on and off the front and rear defrosters.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: "climate.my_mazda3_climate", - ATTR_PRESET_MODE: preset_mode, - }, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.set_hvac_setting.assert_called_once_with( - 12345, 20, "C", front_defroster, rear_defroster - ) diff --git a/tests/components/mazda/test_config_flow.py b/tests/components/mazda/test_config_flow.py deleted file mode 100644 index da7f0369079..00000000000 --- a/tests/components/mazda/test_config_flow.py +++ /dev/null @@ -1,423 +0,0 @@ -"""Test the Mazda Connected Services config flow.""" -from unittest.mock import patch - -import aiohttp - -from homeassistant import config_entries, data_entry_flow -from homeassistant.components.mazda.config_flow import ( - MazdaAccountLockedException, - MazdaAuthenticationException, -) -from homeassistant.components.mazda.const import DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - -FIXTURE_USER_INPUT = { - CONF_EMAIL: "example@example.com", - CONF_PASSWORD: "password", - CONF_REGION: "MNAO", -} -FIXTURE_USER_INPUT_REAUTH = { - CONF_EMAIL: "example@example.com", - CONF_PASSWORD: "password_fixed", - CONF_REGION: "MNAO", -} -FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL = { - CONF_EMAIL: "example2@example.com", - CONF_PASSWORD: "password_fixed", - CONF_REGION: "MNAO", -} - - -async def test_form(hass: HomeAssistant) -> None: - """Test the entire flow.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - await hass.async_block_till_done() - - assert result2["type"] == "create_entry" - assert result2["title"] == FIXTURE_USER_INPUT[CONF_EMAIL] - assert result2["data"] == FIXTURE_USER_INPUT - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_account_already_exists(hass: HomeAssistant) -> None: - """Test account already exists.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - return_value=True, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - await hass.async_block_till_done() - - assert result2["type"] == "abort" - assert result2["reason"] == "already_configured" - - -async def test_form_invalid_auth(hass: HomeAssistant) -> None: - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Failed to authenticate"), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_account_locked(hass: HomeAssistant) -> None: - """Test we handle account locked error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=MazdaAccountLockedException("Account locked"), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "account_locked"} - - -async def test_form_cannot_connect(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=aiohttp.ClientError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_form_unknown_error(hass: HomeAssistant) -> None: - """Test we handle unknown error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=Exception, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "unknown"} - - -async def test_reauth_flow(hass: HomeAssistant) -> None: - """Test reauth works.""" - - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Failed to authenticate"), - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - await hass.config_entries.async_setup(mock_config.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - return_value=True, - ), patch("homeassistant.components.mazda.async_setup_entry", return_value=True): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.ABORT - assert result2["reason"] == "reauth_successful" - - -async def test_reauth_authorization_error(hass: HomeAssistant) -> None: - """Test we show user form on authorization error.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Failed to authenticate"), - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_reauth_account_locked(hass: HomeAssistant) -> None: - """Test we show user form on account_locked error.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=MazdaAccountLockedException("Account locked"), - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "account_locked"} - - -async def test_reauth_connection_error(hass: HomeAssistant) -> None: - """Test we show user form on connection error.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=aiohttp.ClientError, - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_reauth_unknown_error(hass: HomeAssistant) -> None: - """Test we show user form on unknown error.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - side_effect=Exception, - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH, - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "unknown"} - - -async def test_reauth_user_has_new_email_address(hass: HomeAssistant) -> None: - """Test reauth with a new email address but same account.""" - mock_config = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], - data=FIXTURE_USER_INPUT, - ) - mock_config.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.async_setup_entry", - return_value=True, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - data=FIXTURE_USER_INPUT, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" - - # Change the email and ensure the entry and its unique id gets - # updated in the event the user has changed their email with mazda - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL, - ) - await hass.async_block_till_done() - - assert ( - mock_config.unique_id == FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL[CONF_EMAIL] - ) - assert result2["type"] == data_entry_flow.FlowResultType.ABORT - assert result2["reason"] == "reauth_successful" diff --git a/tests/components/mazda/test_device_tracker.py b/tests/components/mazda/test_device_tracker.py deleted file mode 100644 index 72168ef3c27..00000000000 --- a/tests/components/mazda/test_device_tracker.py +++ /dev/null @@ -1,30 +0,0 @@ -"""The device tracker tests for the Mazda Connected Services integration.""" -from homeassistant.components.device_tracker import ATTR_SOURCE_TYPE, SourceType -from homeassistant.const import ( - ATTR_FRIENDLY_NAME, - ATTR_ICON, - ATTR_LATITUDE, - ATTR_LONGITUDE, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import init_integration - - -async def test_device_tracker(hass: HomeAssistant) -> None: - """Test creation of the device tracker.""" - await init_integration(hass) - - entity_registry = er.async_get(hass) - - state = hass.states.get("device_tracker.my_mazda3_device_tracker") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Device tracker" - assert state.attributes.get(ATTR_ICON) == "mdi:car" - assert state.attributes.get(ATTR_LATITUDE) == 1.234567 - assert state.attributes.get(ATTR_LONGITUDE) == -2.345678 - assert state.attributes.get(ATTR_SOURCE_TYPE) == SourceType.GPS - entry = entity_registry.async_get("device_tracker.my_mazda3_device_tracker") - assert entry - assert entry.unique_id == "JM000000000000000" diff --git a/tests/components/mazda/test_diagnostics.py b/tests/components/mazda/test_diagnostics.py deleted file mode 100644 index 9dccf8f6afd..00000000000 --- a/tests/components/mazda/test_diagnostics.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Test Mazda diagnostics.""" -import json - -import pytest - -from homeassistant.components.mazda.const import DATA_COORDINATOR, DOMAIN -from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr - -from . import init_integration - -from tests.common import load_fixture -from tests.components.diagnostics import ( - get_diagnostics_for_config_entry, - get_diagnostics_for_device, -) -from tests.typing import ClientSessionGenerator - - -async def test_config_entry_diagnostics( - hass: HomeAssistant, hass_client: ClientSessionGenerator -) -> None: - """Test config entry diagnostics.""" - await init_integration(hass) - assert hass.data[DOMAIN] - - config_entry = hass.config_entries.async_entries(DOMAIN)[0] - - diagnostics_fixture = json.loads( - load_fixture("mazda/diagnostics_config_entry.json") - ) - - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == diagnostics_fixture - ) - - -async def test_device_diagnostics( - hass: HomeAssistant, hass_client: ClientSessionGenerator -) -> None: - """Test device diagnostics.""" - await init_integration(hass) - assert hass.data[DOMAIN] - - config_entry = hass.config_entries.async_entries(DOMAIN)[0] - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - assert reg_device is not None - - diagnostics_fixture = json.loads(load_fixture("mazda/diagnostics_device.json")) - - assert ( - await get_diagnostics_for_device(hass, hass_client, config_entry, reg_device) - == diagnostics_fixture - ) - - -async def test_device_diagnostics_vehicle_not_found( - hass: HomeAssistant, hass_client: ClientSessionGenerator -) -> None: - """Test device diagnostics when the vehicle cannot be found.""" - await init_integration(hass) - assert hass.data[DOMAIN] - - config_entry = hass.config_entries.async_entries(DOMAIN)[0] - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - assert reg_device is not None - - # Remove vehicle info from hass.data so that vehicle will not be found - hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR].data = [] - - with pytest.raises(AssertionError): - await get_diagnostics_for_device(hass, hass_client, config_entry, reg_device) diff --git a/tests/components/mazda/test_init.py b/tests/components/mazda/test_init.py index 3556f687989..5d15f01389b 100644 --- a/tests/components/mazda/test_init.py +++ b/tests/components/mazda/test_init.py @@ -1,365 +1,50 @@ """Tests for the Mazda Connected Services integration.""" -from datetime import timedelta -import json -from unittest.mock import patch -from pymazda import MazdaAuthenticationException, MazdaException -import pytest -import voluptuous as vol - -from homeassistant.components.mazda.const import DOMAIN +from homeassistant.components.mazda import DOMAIN from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ( - CONF_EMAIL, - CONF_PASSWORD, - CONF_REGION, - STATE_UNAVAILABLE, -) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import device_registry as dr -from homeassistant.util import dt as dt_util +from homeassistant.helpers import issue_registry as ir -from . import init_integration - -from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture - -FIXTURE_USER_INPUT = { - CONF_EMAIL: "example@example.com", - CONF_PASSWORD: "password", - CONF_REGION: "MNAO", -} +from tests.common import MockConfigEntry -async def test_config_entry_not_ready(hass: HomeAssistant) -> None: - """Test the Mazda configuration entry not ready.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - side_effect=MazdaException("Unknown error"), - ): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_init_auth_failure(hass: HomeAssistant) -> None: - """Test auth failure during setup.""" - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Login failed"), - ): - config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.SETUP_ERROR - - flows = hass.config_entries.flow.async_progress() - assert len(flows) == 1 - assert flows[0]["step_id"] == "user" - - -async def test_update_auth_failure(hass: HomeAssistant) -> None: - """Test auth failure during data update.""" - get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) - get_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_vehicle_status.json") - ) - - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - return_value=get_vehicles_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", - return_value=get_vehicle_status_fixture, - ): - config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - - with patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - side_effect=MazdaAuthenticationException("Login failed"), - ): - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=181)) - await hass.async_block_till_done() - - flows = hass.config_entries.flow.async_progress() - assert len(flows) == 1 - assert flows[0]["step_id"] == "user" - - -async def test_update_general_failure(hass: HomeAssistant) -> None: - """Test general failure during data update.""" - get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) - get_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_vehicle_status.json") - ) - - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - return_value=get_vehicles_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", - return_value=get_vehicle_status_fixture, - ): - config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - - with patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - side_effect=Exception("Unknown exception"), - ): - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=181)) - await hass.async_block_till_done() - - entity = hass.states.get("sensor.my_mazda3_fuel_remaining_percentage") - assert entity is not None - assert entity.state == STATE_UNAVAILABLE - - -async def test_unload_config_entry(hass: HomeAssistant) -> None: - """Test the Mazda configuration entry unloading.""" - await init_integration(hass) - assert hass.data[DOMAIN] - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - - await hass.config_entries.async_unload(entries[0].entry_id) - await hass.async_block_till_done() - assert entries[0].state is ConfigEntryState.NOT_LOADED - - -async def test_init_electric_vehicle(hass: HomeAssistant) -> None: - """Test initialization of the integration with an electric vehicle.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - client_mock.get_vehicles.assert_called_once() - client_mock.get_vehicle_status.assert_called_once() - client_mock.get_ev_vehicle_status.assert_called_once() - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - - -async def test_device_nickname(hass: HomeAssistant) -> None: - """Test creation of the device when vehicle has a nickname.""" - await init_integration(hass, use_nickname=True) - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - - assert reg_device.model == "2021 MAZDA3 2.5 S SE AWD" - assert reg_device.manufacturer == "Mazda" - assert reg_device.name == "My Mazda3" - - -async def test_device_no_nickname(hass: HomeAssistant) -> None: - """Test creation of the device when vehicle has no nickname.""" - await init_integration(hass, use_nickname=False) - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - - assert reg_device.model == "2021 MAZDA3 2.5 S SE AWD" - assert reg_device.manufacturer == "Mazda" - assert reg_device.name == "2021 MAZDA3 2.5 S SE AWD" - - -@pytest.mark.parametrize( - ("service", "service_data", "expected_args"), - [ - ( - "send_poi", - {"latitude": 1.2345, "longitude": 2.3456, "poi_name": "Work"}, - [12345, 1.2345, 2.3456, "Work"], - ), - ], -) -async def test_services( - hass: HomeAssistant, service, service_data, expected_args +async def test_mazda_repair_issue( + hass: HomeAssistant, issue_registry: ir.IssueRegistry ) -> None: - """Test service calls.""" - client_mock = await init_integration(hass) - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, + """Test the Mazda configuration entry loading/unloading handles the repair.""" + config_entry_1 = MockConfigEntry( + title="Example 1", + domain=DOMAIN, ) - device_id = reg_device.id + config_entry_1.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry_1.entry_id) + await hass.async_block_till_done() + assert config_entry_1.state is ConfigEntryState.LOADED - service_data["device_id"] = device_id - - await hass.services.async_call(DOMAIN, service, service_data, blocking=True) + # Add a second one + config_entry_2 = MockConfigEntry( + title="Example 2", + domain=DOMAIN, + ) + config_entry_2.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry_2.entry_id) await hass.async_block_till_done() - api_method = getattr(client_mock, service) - api_method.assert_called_once_with(*expected_args) + assert config_entry_2.state is ConfigEntryState.LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) + # Remove the first one + await hass.config_entries.async_remove(config_entry_1.entry_id) + await hass.async_block_till_done() -async def test_service_invalid_device_id(hass: HomeAssistant) -> None: - """Test service call when the specified device ID is invalid.""" - await init_integration(hass) + assert config_entry_1.state is ConfigEntryState.NOT_LOADED + assert config_entry_2.state is ConfigEntryState.LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) - with pytest.raises(vol.error.MultipleInvalid) as err: - await hass.services.async_call( - DOMAIN, - "send_poi", - { - "device_id": "invalid", - "latitude": 1.2345, - "longitude": 6.7890, - "poi_name": "poi_name", - }, - blocking=True, - ) - await hass.async_block_till_done() + # Remove the second one + await hass.config_entries.async_remove(config_entry_2.entry_id) + await hass.async_block_till_done() - assert "Invalid device ID" in str(err.value) - - -async def test_service_device_id_not_mazda_vehicle(hass: HomeAssistant) -> None: - """Test service call when the specified device ID is not the device ID of a Mazda vehicle.""" - await init_integration(hass) - - device_registry = dr.async_get(hass) - # Create another device and pass its device ID. - # Service should fail because device is from wrong domain. - other_config_entry = MockConfigEntry() - other_config_entry.add_to_hass(hass) - other_device = device_registry.async_get_or_create( - config_entry_id=other_config_entry.entry_id, - identifiers={("OTHER_INTEGRATION", "ID_FROM_OTHER_INTEGRATION")}, - ) - - with pytest.raises(vol.error.MultipleInvalid) as err: - await hass.services.async_call( - DOMAIN, - "send_poi", - { - "device_id": other_device.id, - "latitude": 1.2345, - "longitude": 6.7890, - "poi_name": "poi_name", - }, - blocking=True, - ) - await hass.async_block_till_done() - - assert "Device ID is not a Mazda vehicle" in str(err.value) - - -async def test_service_vehicle_id_not_found(hass: HomeAssistant) -> None: - """Test service call when the vehicle ID is not found.""" - await init_integration(hass) - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - device_id = reg_device.id - - entries = hass.config_entries.async_entries(DOMAIN) - entry_id = entries[0].entry_id - - # Remove vehicle info from hass.data so that vehicle ID will not be found - hass.data[DOMAIN][entry_id]["vehicles"] = [] - - with pytest.raises(HomeAssistantError) as err: - await hass.services.async_call( - DOMAIN, - "send_poi", - { - "device_id": device_id, - "latitude": 1.2345, - "longitude": 6.7890, - "poi_name": "poi_name", - }, - blocking=True, - ) - await hass.async_block_till_done() - - assert str(err.value) == "Vehicle ID not found" - - -async def test_service_mazda_api_error(hass: HomeAssistant) -> None: - """Test the Mazda API raising an error when a service is called.""" - get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) - get_vehicle_status_fixture = json.loads( - load_fixture("mazda/get_vehicle_status.json") - ) - - with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - return_value=True, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicles", - return_value=get_vehicles_fixture, - ), patch( - "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", - return_value=get_vehicle_status_fixture, - ): - config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - device_registry = dr.async_get(hass) - reg_device = device_registry.async_get_device( - identifiers={(DOMAIN, "JM000000000000000")}, - ) - device_id = reg_device.id - - with patch( - "homeassistant.components.mazda.MazdaAPI.send_poi", - side_effect=MazdaException("Test error"), - ), pytest.raises(HomeAssistantError) as err: - await hass.services.async_call( - DOMAIN, - "send_poi", - { - "device_id": device_id, - "latitude": 1.2345, - "longitude": 6.7890, - "poi_name": "poi_name", - }, - blocking=True, - ) - await hass.async_block_till_done() - - assert str(err.value) == "Test error" + assert config_entry_1.state is ConfigEntryState.NOT_LOADED + assert config_entry_2.state is ConfigEntryState.NOT_LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None diff --git a/tests/components/mazda/test_lock.py b/tests/components/mazda/test_lock.py deleted file mode 100644 index 0de4573c5f8..00000000000 --- a/tests/components/mazda/test_lock.py +++ /dev/null @@ -1,58 +0,0 @@ -"""The lock tests for the Mazda Connected Services integration.""" -from homeassistant.components.lock import ( - DOMAIN as LOCK_DOMAIN, - SERVICE_LOCK, - SERVICE_UNLOCK, - STATE_LOCKED, -) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import init_integration - - -async def test_lock_setup(hass: HomeAssistant) -> None: - """Test locking and unlocking the vehicle.""" - await init_integration(hass) - - entity_registry = er.async_get(hass) - entry = entity_registry.async_get("lock.my_mazda3_lock") - assert entry - assert entry.unique_id == "JM000000000000000" - - state = hass.states.get("lock.my_mazda3_lock") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Lock" - - assert state.state == STATE_LOCKED - - -async def test_locking(hass: HomeAssistant) -> None: - """Test locking the vehicle.""" - client_mock = await init_integration(hass) - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_LOCK, - {ATTR_ENTITY_ID: "lock.my_mazda3_lock"}, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.lock_doors.assert_called_once() - - -async def test_unlocking(hass: HomeAssistant) -> None: - """Test unlocking the vehicle.""" - client_mock = await init_integration(hass) - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {ATTR_ENTITY_ID: "lock.my_mazda3_lock"}, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.unlock_doors.assert_called_once() diff --git a/tests/components/mazda/test_sensor.py b/tests/components/mazda/test_sensor.py deleted file mode 100644 index 0fb92c34baf..00000000000 --- a/tests/components/mazda/test_sensor.py +++ /dev/null @@ -1,195 +0,0 @@ -"""The sensor tests for the Mazda Connected Services integration.""" -from homeassistant.components.sensor import ( - ATTR_STATE_CLASS, - SensorDeviceClass, - SensorStateClass, -) -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_FRIENDLY_NAME, - ATTR_ICON, - ATTR_UNIT_OF_MEASUREMENT, - PERCENTAGE, - UnitOfLength, - UnitOfPressure, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM - -from . import init_integration - - -async def test_sensors(hass: HomeAssistant) -> None: - """Test creation of the sensors.""" - await init_integration(hass) - - entity_registry = er.async_get(hass) - - # Fuel Remaining Percentage - state = hass.states.get("sensor.my_mazda3_fuel_remaining_percentage") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) - == "My Mazda3 Fuel remaining percentage" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:gas-station" - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "87.0" - entry = entity_registry.async_get("sensor.my_mazda3_fuel_remaining_percentage") - assert entry - assert entry.unique_id == "JM000000000000000_fuel_remaining_percentage" - - # Fuel Distance Remaining - state = hass.states.get("sensor.my_mazda3_fuel_distance_remaining") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Fuel distance remaining" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:gas-station" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.DISTANCE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.KILOMETERS - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "381" - entry = entity_registry.async_get("sensor.my_mazda3_fuel_distance_remaining") - assert entry - assert entry.unique_id == "JM000000000000000_fuel_distance_remaining" - - # Odometer - state = hass.states.get("sensor.my_mazda3_odometer") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Odometer" - assert state.attributes.get(ATTR_ICON) == "mdi:speedometer" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.DISTANCE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.KILOMETERS - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL_INCREASING - assert state.state == "2795" - entry = entity_registry.async_get("sensor.my_mazda3_odometer") - assert entry - assert entry.unique_id == "JM000000000000000_odometer" - - # Front Left Tire Pressure - state = hass.states.get("sensor.my_mazda3_front_left_tire_pressure") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Front left tire pressure" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPressure.KPA - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "241" - entry = entity_registry.async_get("sensor.my_mazda3_front_left_tire_pressure") - assert entry - assert entry.unique_id == "JM000000000000000_front_left_tire_pressure" - - # Front Right Tire Pressure - state = hass.states.get("sensor.my_mazda3_front_right_tire_pressure") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) - == "My Mazda3 Front right tire pressure" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPressure.KPA - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "241" - entry = entity_registry.async_get("sensor.my_mazda3_front_right_tire_pressure") - assert entry - assert entry.unique_id == "JM000000000000000_front_right_tire_pressure" - - # Rear Left Tire Pressure - state = hass.states.get("sensor.my_mazda3_rear_left_tire_pressure") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear left tire pressure" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPressure.KPA - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "228" - entry = entity_registry.async_get("sensor.my_mazda3_rear_left_tire_pressure") - assert entry - assert entry.unique_id == "JM000000000000000_rear_left_tire_pressure" - - # Rear Right Tire Pressure - state = hass.states.get("sensor.my_mazda3_rear_right_tire_pressure") - assert state - assert ( - state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear right tire pressure" - ) - assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPressure.KPA - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "228" - entry = entity_registry.async_get("sensor.my_mazda3_rear_right_tire_pressure") - assert entry - assert entry.unique_id == "JM000000000000000_rear_right_tire_pressure" - - -async def test_sensors_us_customary_units(hass: HomeAssistant) -> None: - """Test that the sensors work properly with US customary units.""" - hass.config.units = US_CUSTOMARY_SYSTEM - - await init_integration(hass) - - # In the US, miles are used for vehicle odometers. - # These tests verify that the unit conversion logic for the distance - # sensor device class automatically converts the unit to miles. - - # Fuel Distance Remaining - state = hass.states.get("sensor.my_mazda3_fuel_distance_remaining") - assert state - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.MILES - assert state.state == "237" - - # Odometer - state = hass.states.get("sensor.my_mazda3_odometer") - assert state - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.MILES - assert state.state == "1737" - - -async def test_electric_vehicle_sensors(hass: HomeAssistant) -> None: - """Test sensors which are specific to electric vehicles.""" - - await init_integration(hass, electric_vehicle=True) - - entity_registry = er.async_get(hass) - - # Fuel Remaining Percentage should not exist for an electric vehicle - entry = entity_registry.async_get("sensor.my_mazda3_fuel_remaining_percentage") - assert entry is None - - # Fuel Distance Remaining should not exist for an electric vehicle - entry = entity_registry.async_get("sensor.my_mazda3_fuel_distance_remaining") - assert entry is None - - # Charge Level - state = hass.states.get("sensor.my_mazda3_charge_level") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Charge level" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.BATTERY - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "80" - entry = entity_registry.async_get("sensor.my_mazda3_charge_level") - assert entry - assert entry.unique_id == "JM000000000000000_ev_charge_level" - - # Remaining Range - state = hass.states.get("sensor.my_mazda3_remaining_range") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Remaining range" - assert state.attributes.get(ATTR_ICON) == "mdi:ev-station" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.DISTANCE - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.KILOMETERS - assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - assert state.state == "218" - entry = entity_registry.async_get("sensor.my_mazda3_remaining_range") - assert entry - assert entry.unique_id == "JM000000000000000_ev_remaining_range" diff --git a/tests/components/mazda/test_switch.py b/tests/components/mazda/test_switch.py deleted file mode 100644 index a2d8ca649f3..00000000000 --- a/tests/components/mazda/test_switch.py +++ /dev/null @@ -1,69 +0,0 @@ -"""The switch tests for the Mazda Connected Services integration.""" -from homeassistant.components.switch import ( - DOMAIN as SWITCH_DOMAIN, - SERVICE_TURN_OFF, - SERVICE_TURN_ON, - STATE_ON, -) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import init_integration - - -async def test_switch_setup(hass: HomeAssistant) -> None: - """Test setup of the switch entity.""" - await init_integration(hass, electric_vehicle=True) - - entity_registry = er.async_get(hass) - entry = entity_registry.async_get("switch.my_mazda3_charging") - assert entry - assert entry.unique_id == "JM000000000000000" - - state = hass.states.get("switch.my_mazda3_charging") - assert state - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Charging" - assert state.attributes.get(ATTR_ICON) == "mdi:ev-station" - - assert state.state == STATE_ON - - -async def test_start_charging(hass: HomeAssistant) -> None: - """Test turning on the charging switch.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - client_mock.reset_mock() - - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.my_mazda3_charging"}, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.start_charging.assert_called_once() - client_mock.refresh_vehicle_status.assert_called_once() - client_mock.get_vehicle_status.assert_called_once() - client_mock.get_ev_vehicle_status.assert_called_once() - - -async def test_stop_charging(hass: HomeAssistant) -> None: - """Test turning off the charging switch.""" - client_mock = await init_integration(hass, electric_vehicle=True) - - client_mock.reset_mock() - - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "switch.my_mazda3_charging"}, - blocking=True, - ) - await hass.async_block_till_done() - - client_mock.stop_charging.assert_called_once() - client_mock.refresh_vehicle_status.assert_called_once() - client_mock.get_vehicle_status.assert_called_once() - client_mock.get_ev_vehicle_status.assert_called_once()