mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Remove Mazda integration (#101849)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
b70e2f7749
commit
91cf719588
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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
|
||||
)
|
@ -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()
|
@ -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()
|
||||
|
@ -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"}
|
@ -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"]
|
@ -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
|
@ -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()
|
@ -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": []
|
||||
}
|
||||
|
@ -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)
|
@ -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:
|
@ -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})."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
@ -271,7 +271,6 @@ FLOWS = {
|
||||
"lyric",
|
||||
"mailgun",
|
||||
"matter",
|
||||
"mazda",
|
||||
"meater",
|
||||
"medcom_ble",
|
||||
"melcloud",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"temperature": 20,
|
||||
"temperatureUnit": "C",
|
||||
"frontDefroster": true,
|
||||
"rearDefroster": false
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
@ -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"
|
@ -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"
|
@ -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
|
||||
)
|
@ -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"
|
@ -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"
|
@ -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)
|
@ -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
|
||||
|
@ -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()
|
@ -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"
|
@ -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()
|
Loading…
x
Reference in New Issue
Block a user