diff --git a/homeassistant/components/teslemetry/manifest.json b/homeassistant/components/teslemetry/manifest.json index 061de63ca6ba..16028cb660e5 100644 --- a/homeassistant/components/teslemetry/manifest.json +++ b/homeassistant/components/teslemetry/manifest.json @@ -12,6 +12,6 @@ "requirements": [ "aiopowerwall==0.4.1", "tesla-fleet-api==1.17.2", - "teslemetry-stream==1.0.2" + "teslemetry-stream==1.2.0" ] } diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index d1b56b766661..a58ce014e519 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -3,7 +3,7 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta -from typing import override +from typing import Any, override from tesla_fleet_api import firmware_at_least from tesla_fleet_api.const import Scope @@ -59,6 +59,12 @@ PARALLEL_UPDATES = 0 # vehicles, identified by this driver-assist capability in the vehicle config. DRIVER_ASSIST_HW4 = "TeslaAP4" + +def _tires_in_warning(warnings: dict[str, Any] | None) -> int | None: + """Count the tires flagged in a per-tire TPMS warning field.""" + return None if warnings is None else sum(bool(v) for v in warnings.values()) + + BMS_STATES = { "Standby": "standby", "Drive": "drive", @@ -1430,7 +1436,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = ( TeslemetryVehicleSensorEntityDescription( key="tpms_hard_warnings", streaming_listener=lambda vehicle, callback: vehicle.listen_TpmsHardWarnings( - callback + lambda value: callback(_tires_in_warning(value)) ), entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, @@ -1438,7 +1444,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = ( TeslemetryVehicleSensorEntityDescription( key="tpms_soft_warnings", streaming_listener=lambda vehicle, callback: vehicle.listen_TpmsSoftWarnings( - callback + lambda value: callback(_tires_in_warning(value)) ), entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, diff --git a/requirements_all.txt b/requirements_all.txt index 8e2f3a095c28..88a81e8706d4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3240,7 +3240,7 @@ tesla-powerwall==0.5.3 tesla-wall-connector==1.2.0 # homeassistant.components.teslemetry -teslemetry-stream==1.0.2 +teslemetry-stream==1.2.0 # homeassistant.components.tessie tessie-api==0.1.3 diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py index 4eb8206358e7..8598025bc470 100644 --- a/tests/components/teslemetry/test_sensor.py +++ b/tests/components/teslemetry/test_sensor.py @@ -50,6 +50,20 @@ ENERGY_HISTORY_ENTITY = "sensor.energy_site_battery_discharged" # or from the event's created_at cannot produce this. SITE_MIDNIGHT = "2024-09-18T00:00:00+10:00" +# Per-tire TPMS warning objects including the Tesla Semi. +TPMS_NO_WARNINGS = { + "frontLeft": False, + "frontRight": False, + "rearLeft": False, + "rearRight": False, + "semiMiddleAxleLeft2": False, + "semiMiddleAxleRight2": False, + "semiRearAxleLeft": False, + "semiRearAxleLeft2": False, + "semiRearAxleRight": False, + "semiRearAxleRight2": False, +} + def _products_with_driver_assist(driver_assist: str) -> dict: """Return a products response with the vehicle's driver-assist capability set.""" @@ -460,6 +474,48 @@ async def test_sensors_streaming_tpms_none_clears_state( assert hass.states.get(entity_id).state == STATE_UNKNOWN +@pytest.mark.parametrize( + ("signal", "entity_id"), + [ + pytest.param( + Signal.TPMS_HARD_WARNINGS, + "sensor.test_tire_pressure_hard_warnings", + id="hard", + ), + pytest.param( + Signal.TPMS_SOFT_WARNINGS, + "sensor.test_tire_pressure_soft_warnings", + id="soft", + ), + ], +) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors_streaming_tpms_warnings( + hass: HomeAssistant, + mock_add_listener: AsyncMock, + signal: Signal, + entity_id: str, +) -> None: + """Test TPMS warning sensors report how many tires are in warning.""" + await setup_platform(hass, [Platform.SENSOR]) + vin = VEHICLE_DATA_ALT["response"]["vin"] + + for warnings, expected_state in ( + ({**TPMS_NO_WARNINGS, "frontRight": True, "rearLeft": True}, "2"), + (TPMS_NO_WARNINGS, "0"), + (None, STATE_UNKNOWN), + ): + mock_add_listener.send( + { + "vin": vin, + "data": {signal: warnings}, + "createdAt": "2024-10-04T10:45:17.537Z", + } + ) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == expected_state + + @pytest.mark.parametrize( "raw_value", [