mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Add electric vehicle sensors to Mazda integration (#64099)
This commit is contained in:
parent
9d0b73bd99
commit
bc17616720
@ -155,6 +155,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
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"])
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_VEHICLES] = vehicles
|
||||
|
||||
return vehicles
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Mazda Connected Services",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/mazda",
|
||||
"requirements": ["pymazda==0.2.2"],
|
||||
"requirements": ["pymazda==0.3.0"],
|
||||
"codeowners": ["@bdr99"],
|
||||
"quality_scale": "platinum",
|
||||
"iot_class": "cloud_polling"
|
||||
|
@ -4,7 +4,12 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_UNIT_SYSTEM_IMPERIAL,
|
||||
@ -54,6 +59,20 @@ def _get_distance_unit(unit_system):
|
||||
return LENGTH_KILOMETERS
|
||||
|
||||
|
||||
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
|
||||
@ -74,6 +93,22 @@ def _rear_right_tire_pressure_supported(data):
|
||||
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, unit_system):
|
||||
"""Get the fuel distance remaining value."""
|
||||
return round(
|
||||
@ -106,13 +141,28 @@ def _rear_right_tire_pressure_value(data, unit_system):
|
||||
return round(data["status"]["tirePressure"]["rearRightTirePressurePsi"])
|
||||
|
||||
|
||||
def _ev_charge_level_value(data, unit_system):
|
||||
"""Get the charge level value."""
|
||||
return round(data["evStatus"]["chargeInfo"]["batteryLevelPercentage"])
|
||||
|
||||
|
||||
def _ev_remaining_range_value(data, unit_system):
|
||||
"""Get the remaining range value."""
|
||||
return round(
|
||||
unit_system.length(
|
||||
data["evStatus"]["chargeInfo"]["drivingRangeKm"], LENGTH_KILOMETERS
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
SENSOR_ENTITIES = [
|
||||
MazdaSensorEntityDescription(
|
||||
key="fuel_remaining_percentage",
|
||||
name_suffix="Fuel Remaining Percentage",
|
||||
icon="mdi:gas-station",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
is_supported=lambda data: data["status"]["fuelRemainingPercent"] is not None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_fuel_remaining_percentage_supported,
|
||||
value=lambda data, unit_system: data["status"]["fuelRemainingPercent"],
|
||||
),
|
||||
MazdaSensorEntityDescription(
|
||||
@ -120,7 +170,8 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Fuel Distance Remaining",
|
||||
icon="mdi:gas-station",
|
||||
unit=_get_distance_unit,
|
||||
is_supported=lambda data: data["status"]["fuelDistanceRemainingKm"] is not None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_fuel_distance_remaining_supported,
|
||||
value=_fuel_distance_remaining_value,
|
||||
),
|
||||
MazdaSensorEntityDescription(
|
||||
@ -128,6 +179,7 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Odometer",
|
||||
icon="mdi:speedometer",
|
||||
unit=_get_distance_unit,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
is_supported=lambda data: data["status"]["odometerKm"] is not None,
|
||||
value=_odometer_value,
|
||||
),
|
||||
@ -136,6 +188,7 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Front Left Tire Pressure",
|
||||
icon="mdi:car-tire-alert",
|
||||
native_unit_of_measurement=PRESSURE_PSI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_front_left_tire_pressure_supported,
|
||||
value=_front_left_tire_pressure_value,
|
||||
),
|
||||
@ -144,6 +197,7 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Front Right Tire Pressure",
|
||||
icon="mdi:car-tire-alert",
|
||||
native_unit_of_measurement=PRESSURE_PSI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_front_right_tire_pressure_supported,
|
||||
value=_front_right_tire_pressure_value,
|
||||
),
|
||||
@ -152,6 +206,7 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Rear Left Tire Pressure",
|
||||
icon="mdi:car-tire-alert",
|
||||
native_unit_of_measurement=PRESSURE_PSI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_rear_left_tire_pressure_supported,
|
||||
value=_rear_left_tire_pressure_value,
|
||||
),
|
||||
@ -160,9 +215,28 @@ SENSOR_ENTITIES = [
|
||||
name_suffix="Rear Right Tire Pressure",
|
||||
icon="mdi:car-tire-alert",
|
||||
native_unit_of_measurement=PRESSURE_PSI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_rear_right_tire_pressure_supported,
|
||||
value=_rear_right_tire_pressure_value,
|
||||
),
|
||||
MazdaSensorEntityDescription(
|
||||
key="ev_charge_level",
|
||||
name_suffix="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",
|
||||
name_suffix="Remaining Range",
|
||||
icon="mdi:ev-station",
|
||||
unit=_get_distance_unit,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
is_supported=_ev_remaining_range_supported,
|
||||
value=_ev_remaining_range_value,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -1654,7 +1654,7 @@ pymailgunner==1.4
|
||||
pymata-express==1.19
|
||||
|
||||
# homeassistant.components.mazda
|
||||
pymazda==0.2.2
|
||||
pymazda==0.3.0
|
||||
|
||||
# homeassistant.components.mediaroom
|
||||
pymediaroom==0.6.4.1
|
||||
|
@ -1032,7 +1032,7 @@ pymailgunner==1.4
|
||||
pymata-express==1.19
|
||||
|
||||
# homeassistant.components.mazda
|
||||
pymazda==0.2.2
|
||||
pymazda==0.3.0
|
||||
|
||||
# homeassistant.components.melcloud
|
||||
pymelcloud==2.5.6
|
||||
|
@ -19,15 +19,22 @@ FIXTURE_USER_INPUT = {
|
||||
}
|
||||
|
||||
|
||||
async def init_integration(hass: HomeAssistant, use_nickname=True) -> MockConfigEntry:
|
||||
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")
|
||||
)
|
||||
|
||||
config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT)
|
||||
config_entry.add_to_hass(hass)
|
||||
@ -42,6 +49,9 @@ async def init_integration(hass: HomeAssistant, use_nickname=True) -> MockConfig
|
||||
)
|
||||
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()
|
||||
|
19
tests/components/mazda/fixtures/get_ev_vehicle_status.json
Normal file
19
tests/components/mazda/fixtures/get_ev_vehicle_status.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"chargeInfo": {
|
||||
"lastUpdatedTimestamp": "20210807083956",
|
||||
"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
|
||||
}
|
||||
}
|
@ -34,4 +34,4 @@
|
||||
"rearLeftTirePressurePsi": 33.0,
|
||||
"rearRightTirePressurePsi": 33.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
"interiorColorCode": "BY3",
|
||||
"interiorColorName": "BLACK",
|
||||
"exteriorColorCode": "42M",
|
||||
"exteriorColorName": "DEEP CRYSTAL BLUE MICA"
|
||||
"exteriorColorName": "DEEP CRYSTAL BLUE MICA",
|
||||
"isElectric": false
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -158,6 +158,19 @@ async def test_unload_config_entry(hass: HomeAssistant) -> None:
|
||||
assert entries[0].state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_init_electric_vehicle(hass):
|
||||
"""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):
|
||||
"""Test creation of the device when vehicle has a nickname."""
|
||||
await init_integration(hass, use_nickname=True)
|
||||
|
@ -1,6 +1,12 @@
|
||||
"""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,
|
||||
@ -30,6 +36,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
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
|
||||
@ -43,6 +50,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:gas-station"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_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
|
||||
@ -54,6 +62,7 @@ async def test_sensors(hass):
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Odometer"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:speedometer"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_KILOMETERS
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL_INCREASING
|
||||
assert state.state == "2796"
|
||||
entry = entity_registry.async_get("sensor.my_mazda3_odometer")
|
||||
assert entry
|
||||
@ -67,6 +76,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
assert state.state == "35"
|
||||
entry = entity_registry.async_get("sensor.my_mazda3_front_left_tire_pressure")
|
||||
assert entry
|
||||
@ -81,6 +91,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
assert state.state == "35"
|
||||
entry = entity_registry.async_get("sensor.my_mazda3_front_right_tire_pressure")
|
||||
assert entry
|
||||
@ -94,6 +105,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
assert state.state == "33"
|
||||
entry = entity_registry.async_get("sensor.my_mazda3_rear_left_tire_pressure")
|
||||
assert entry
|
||||
@ -107,6 +119,7 @@ async def test_sensors(hass):
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
assert state.state == "33"
|
||||
entry = entity_registry.async_get("sensor.my_mazda3_rear_right_tire_pressure")
|
||||
assert entry
|
||||
@ -130,3 +143,43 @@ async def test_sensors_imperial_units(hass):
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_MILES
|
||||
assert state.state == "1737"
|
||||
|
||||
|
||||
async def test_electric_vehicle_sensors(hass):
|
||||
"""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_UNIT_OF_MEASUREMENT) == LENGTH_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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user