mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +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
|
/tests/components/matrix/ @PaarthShah
|
||||||
/homeassistant/components/matter/ @home-assistant/matter
|
/homeassistant/components/matter/ @home-assistant/matter
|
||||||
/tests/components/matter/ @home-assistant/matter
|
/tests/components/matter/ @home-assistant/matter
|
||||||
/homeassistant/components/mazda/ @bdr99
|
|
||||||
/tests/components/mazda/ @bdr99
|
|
||||||
/homeassistant/components/meater/ @Sotolotl @emontnemery
|
/homeassistant/components/meater/ @Sotolotl @emontnemery
|
||||||
/tests/components/meater/ @Sotolotl @emontnemery
|
/tests/components/meater/ @Sotolotl @emontnemery
|
||||||
/homeassistant/components/medcom_ble/ @elafargue
|
/homeassistant/components/medcom_ble/ @elafargue
|
||||||
|
@ -1,213 +1,26 @@
|
|||||||
"""The Mazda Connected Services integration."""
|
"""The Mazda Connected Services integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from datetime import timedelta
|
from homeassistant.core import HomeAssistant
|
||||||
import logging
|
from homeassistant.helpers import issue_registry as ir
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from pymazda import (
|
DOMAIN = "mazda"
|
||||||
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,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def with_timeout(task, timeout_seconds=30):
|
async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
||||||
"""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:
|
|
||||||
"""Set up Mazda Connected Services from a config entry."""
|
"""Set up Mazda Connected Services from a config entry."""
|
||||||
email = entry.data[CONF_EMAIL]
|
ir.async_create_issue(
|
||||||
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(
|
|
||||||
hass,
|
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,
|
DOMAIN,
|
||||||
"send_poi",
|
DOMAIN,
|
||||||
async_handle_service_call,
|
is_fixable=False,
|
||||||
schema=service_schema_send_poi,
|
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
|
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:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""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
|
return True
|
||||||
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']}"
|
|
||||||
|
@ -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."""
|
"""The Mazda Connected Services integration."""
|
||||||
from collections.abc import Mapping
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import aiohttp
|
from homeassistant.config_entries import ConfigFlow
|
||||||
from pymazda import (
|
|
||||||
Client as MazdaAPI,
|
|
||||||
MazdaAccountLockedException,
|
|
||||||
MazdaAuthenticationException,
|
|
||||||
)
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from . import DOMAIN
|
||||||
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),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class MazdaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Mazda Connected Services."""
|
"""Handle a config flow for Mazda Connected Services."""
|
||||||
|
|
||||||
VERSION = 1
|
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",
|
"domain": "mazda",
|
||||||
"name": "Mazda Connected Services",
|
"name": "Mazda Connected Services",
|
||||||
"codeowners": ["@bdr99"],
|
"codeowners": [],
|
||||||
"config_flow": true,
|
|
||||||
"documentation": "https://www.home-assistant.io/integrations/mazda",
|
"documentation": "https://www.home-assistant.io/integrations/mazda",
|
||||||
|
"integration_type": "system",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pymazda"],
|
"requirements": []
|
||||||
"quality_scale": "platinum",
|
|
||||||
"requirements": ["pymazda==0.3.11"]
|
|
||||||
}
|
}
|
||||||
|
@ -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": {
|
"issues": {
|
||||||
"abort": {
|
"integration_removed": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
"title": "The Mazda integration has been removed",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"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})."
|
||||||
},
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
"lyric",
|
||||||
"mailgun",
|
"mailgun",
|
||||||
"matter",
|
"matter",
|
||||||
"mazda",
|
|
||||||
"meater",
|
"meater",
|
||||||
"medcom_ble",
|
"medcom_ble",
|
||||||
"melcloud",
|
"melcloud",
|
||||||
|
@ -3262,12 +3262,6 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
},
|
},
|
||||||
"mazda": {
|
|
||||||
"name": "Mazda Connected Services",
|
|
||||||
"integration_type": "hub",
|
|
||||||
"config_flow": true,
|
|
||||||
"iot_class": "cloud_polling"
|
|
||||||
},
|
|
||||||
"meater": {
|
"meater": {
|
||||||
"name": "Meater",
|
"name": "Meater",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
|
@ -1847,9 +1847,6 @@ pymailgunner==1.4
|
|||||||
# homeassistant.components.firmata
|
# homeassistant.components.firmata
|
||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
|
||||||
pymazda==0.3.11
|
|
||||||
|
|
||||||
# homeassistant.components.mediaroom
|
# homeassistant.components.mediaroom
|
||||||
pymediaroom==0.6.5.4
|
pymediaroom==0.6.5.4
|
||||||
|
|
||||||
|
@ -1390,9 +1390,6 @@ pymailgunner==1.4
|
|||||||
# homeassistant.components.firmata
|
# homeassistant.components.firmata
|
||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
|
||||||
pymazda==0.3.11
|
|
||||||
|
|
||||||
# homeassistant.components.melcloud
|
# homeassistant.components.melcloud
|
||||||
pymelcloud==2.5.8
|
pymelcloud==2.5.8
|
||||||
|
|
||||||
|
@ -1,80 +1 @@
|
|||||||
"""Tests for the Mazda Connected Services integration."""
|
"""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."""
|
"""Tests for the Mazda Connected Services integration."""
|
||||||
from datetime import timedelta
|
|
||||||
import json
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pymazda import MazdaAuthenticationException, MazdaException
|
from homeassistant.components.mazda import DOMAIN
|
||||||
import pytest
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.mazda.const import DOMAIN
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.helpers import issue_registry as ir
|
||||||
from homeassistant.helpers import device_registry as dr
|
|
||||||
from homeassistant.util import dt as dt_util
|
|
||||||
|
|
||||||
from . import init_integration
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
|
async def test_mazda_repair_issue(
|
||||||
"""Test the Mazda configuration entry not ready."""
|
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||||
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
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test service calls."""
|
"""Test the Mazda configuration entry loading/unloading handles the repair."""
|
||||||
client_mock = await init_integration(hass)
|
config_entry_1 = MockConfigEntry(
|
||||||
|
title="Example 1",
|
||||||
device_registry = dr.async_get(hass)
|
domain=DOMAIN,
|
||||||
reg_device = device_registry.async_get_device(
|
|
||||||
identifiers={(DOMAIN, "JM000000000000000")},
|
|
||||||
)
|
)
|
||||||
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
|
# Add a second one
|
||||||
|
config_entry_2 = MockConfigEntry(
|
||||||
await hass.services.async_call(DOMAIN, service, service_data, blocking=True)
|
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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
api_method = getattr(client_mock, service)
|
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||||
api_method.assert_called_once_with(*expected_args)
|
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||||
|
|
||||||
|
# Remove the first one
|
||||||
async def test_service_invalid_device_id(hass: HomeAssistant) -> None:
|
await hass.config_entries.async_remove(config_entry_1.entry_id)
|
||||||
"""Test service call when the specified device ID is invalid."""
|
|
||||||
await init_integration(hass)
|
|
||||||
|
|
||||||
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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert "Invalid device ID" in str(err.value)
|
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)
|
||||||
|
|
||||||
|
# Remove the second one
|
||||||
async def test_service_device_id_not_mazda_vehicle(hass: HomeAssistant) -> None:
|
await hass.config_entries.async_remove(config_entry_2.entry_id)
|
||||||
"""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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert "Device ID is not a Mazda vehicle" in str(err.value)
|
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
|
||||||
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"
|
|
||||||
|
@ -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