mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 18:27:51 +00:00
Add update platform to Teslemetry (#118145)
* Add update platform * Add tests * updates * update test * Fix support features comment * Add assertion
This commit is contained in:
parent
7bbb33b415
commit
f12f82caac
@ -38,6 +38,7 @@ PLATFORMS: Final = [
|
|||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -454,6 +454,11 @@
|
|||||||
"vehicle_state_valet_mode": {
|
"vehicle_state_valet_mode": {
|
||||||
"name": "Valet mode"
|
"name": "Valet mode"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"vehicle_state_software_update_status": {
|
||||||
|
"name": "[%key:component::update::title%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
105
homeassistant/components/teslemetry/update.py
Normal file
105
homeassistant/components/teslemetry/update.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""Update platform for Teslemetry integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from tesla_fleet_api.const import Scope
|
||||||
|
|
||||||
|
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .entity import TeslemetryVehicleEntity
|
||||||
|
from .models import TeslemetryVehicleData
|
||||||
|
|
||||||
|
AVAILABLE = "available"
|
||||||
|
DOWNLOADING = "downloading"
|
||||||
|
INSTALLING = "installing"
|
||||||
|
WIFI_WAIT = "downloading_wifi_wait"
|
||||||
|
SCHEDULED = "scheduled"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Teslemetry update platform from a config entry."""
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
TeslemetryUpdateEntity(vehicle, entry.runtime_data.scopes)
|
||||||
|
for vehicle in entry.runtime_data.vehicles
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TeslemetryUpdateEntity(TeslemetryVehicleEntity, UpdateEntity):
|
||||||
|
"""Teslemetry Updates entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TeslemetryVehicleData,
|
||||||
|
scopes: list[Scope],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the Update."""
|
||||||
|
self.scoped = Scope.VEHICLE_CMDS in scopes
|
||||||
|
super().__init__(
|
||||||
|
data,
|
||||||
|
"vehicle_state_software_update_status",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update the attributes of the entity."""
|
||||||
|
|
||||||
|
# Supported Features
|
||||||
|
if self.scoped and self._value in (
|
||||||
|
AVAILABLE,
|
||||||
|
SCHEDULED,
|
||||||
|
):
|
||||||
|
# Only allow install when an update has been fully downloaded
|
||||||
|
self._attr_supported_features = (
|
||||||
|
UpdateEntityFeature.PROGRESS | UpdateEntityFeature.INSTALL
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._attr_supported_features = UpdateEntityFeature.PROGRESS
|
||||||
|
|
||||||
|
# Installed Version
|
||||||
|
self._attr_installed_version = self.get("vehicle_state_car_version")
|
||||||
|
if self._attr_installed_version is not None:
|
||||||
|
# Remove build from version
|
||||||
|
self._attr_installed_version = self._attr_installed_version.split(" ")[0]
|
||||||
|
|
||||||
|
# Latest Version
|
||||||
|
if self._value in (
|
||||||
|
AVAILABLE,
|
||||||
|
SCHEDULED,
|
||||||
|
INSTALLING,
|
||||||
|
DOWNLOADING,
|
||||||
|
WIFI_WAIT,
|
||||||
|
):
|
||||||
|
self._attr_latest_version = self.coordinator.data[
|
||||||
|
"vehicle_state_software_update_version"
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self._attr_latest_version = self._attr_installed_version
|
||||||
|
|
||||||
|
# In Progress
|
||||||
|
if self._value in (
|
||||||
|
SCHEDULED,
|
||||||
|
INSTALLING,
|
||||||
|
):
|
||||||
|
self._attr_in_progress = (
|
||||||
|
cast(int, self.get("vehicle_state_software_update_install_perc"))
|
||||||
|
or True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._attr_in_progress = False
|
||||||
|
|
||||||
|
async def async_install(
|
||||||
|
self, version: str | None, backup: bool, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
"""Install an update."""
|
||||||
|
self.raise_for_scope()
|
||||||
|
await self.wake_up_if_asleep()
|
||||||
|
await self.handle_command(self.api.schedule_software_update(offset_sec=60))
|
||||||
|
self._attr_in_progress = True
|
||||||
|
self.async_write_ha_state()
|
@ -237,11 +237,11 @@
|
|||||||
"service_mode": false,
|
"service_mode": false,
|
||||||
"service_mode_plus": false,
|
"service_mode_plus": false,
|
||||||
"software_update": {
|
"software_update": {
|
||||||
"download_perc": 0,
|
"download_perc": 100,
|
||||||
"expected_duration_sec": 2700,
|
"expected_duration_sec": 2700,
|
||||||
"install_perc": 1,
|
"install_perc": 1,
|
||||||
"status": "",
|
"status": "available",
|
||||||
"version": " "
|
"version": "2024.12.0.0"
|
||||||
},
|
},
|
||||||
"speed_limit_mode": {
|
"speed_limit_mode": {
|
||||||
"active": false,
|
"active": false,
|
||||||
|
@ -390,11 +390,11 @@
|
|||||||
'vehicle_state_sentry_mode_available': True,
|
'vehicle_state_sentry_mode_available': True,
|
||||||
'vehicle_state_service_mode': False,
|
'vehicle_state_service_mode': False,
|
||||||
'vehicle_state_service_mode_plus': False,
|
'vehicle_state_service_mode_plus': False,
|
||||||
'vehicle_state_software_update_download_perc': 0,
|
'vehicle_state_software_update_download_perc': 100,
|
||||||
'vehicle_state_software_update_expected_duration_sec': 2700,
|
'vehicle_state_software_update_expected_duration_sec': 2700,
|
||||||
'vehicle_state_software_update_install_perc': 1,
|
'vehicle_state_software_update_install_perc': 1,
|
||||||
'vehicle_state_software_update_status': '',
|
'vehicle_state_software_update_status': 'available',
|
||||||
'vehicle_state_software_update_version': ' ',
|
'vehicle_state_software_update_version': '2024.12.0.0',
|
||||||
'vehicle_state_speed_limit_mode_active': False,
|
'vehicle_state_speed_limit_mode_active': False,
|
||||||
'vehicle_state_speed_limit_mode_current_limit_mph': 69,
|
'vehicle_state_speed_limit_mode_current_limit_mph': 69,
|
||||||
'vehicle_state_speed_limit_mode_max_limit_mph': 120,
|
'vehicle_state_speed_limit_mode_max_limit_mph': 120,
|
||||||
|
113
tests/components/teslemetry/snapshots/test_update.ambr
Normal file
113
tests/components/teslemetry/snapshots/test_update.ambr
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_update[update.test_update-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'update',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'update.test_update',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Update',
|
||||||
|
'platform': 'teslemetry',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': <UpdateEntityFeature: 5>,
|
||||||
|
'translation_key': 'vehicle_state_software_update_status',
|
||||||
|
'unique_id': 'VINVINVIN-vehicle_state_software_update_status',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_update[update.test_update-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'auto_update': False,
|
||||||
|
'entity_picture': 'https://brands.home-assistant.io/_/teslemetry/icon.png',
|
||||||
|
'friendly_name': 'Test Update',
|
||||||
|
'in_progress': False,
|
||||||
|
'installed_version': '2023.44.30.8',
|
||||||
|
'latest_version': '2024.12.0.0',
|
||||||
|
'release_summary': None,
|
||||||
|
'release_url': None,
|
||||||
|
'skipped_version': None,
|
||||||
|
'supported_features': <UpdateEntityFeature: 5>,
|
||||||
|
'title': None,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'update.test_update',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'on',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_update_alt[update.test_update-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'update',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'update.test_update',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Update',
|
||||||
|
'platform': 'teslemetry',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': <UpdateEntityFeature: 4>,
|
||||||
|
'translation_key': 'vehicle_state_software_update_status',
|
||||||
|
'unique_id': 'VINVINVIN-vehicle_state_software_update_status',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_update_alt[update.test_update-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'auto_update': False,
|
||||||
|
'entity_picture': 'https://brands.home-assistant.io/_/teslemetry/icon.png',
|
||||||
|
'friendly_name': 'Test Update',
|
||||||
|
'in_progress': False,
|
||||||
|
'installed_version': '2023.44.30.8',
|
||||||
|
'latest_version': '2023.44.30.8',
|
||||||
|
'release_summary': None,
|
||||||
|
'release_url': None,
|
||||||
|
'skipped_version': None,
|
||||||
|
'supported_features': <UpdateEntityFeature: 4>,
|
||||||
|
'title': None,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'update.test_update',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
89
tests/components/teslemetry/test_update.py
Normal file
89
tests/components/teslemetry/test_update.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"""Test the Teslemetry update platform."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
from tesla_fleet_api.exceptions import VehicleOffline
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
|
||||||
|
from homeassistant.components.teslemetry.update import INSTALLING
|
||||||
|
from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import assert_entities, setup_platform
|
||||||
|
from .const import COMMAND_OK, VEHICLE_DATA, VEHICLE_DATA_ALT
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the update entities are correct."""
|
||||||
|
|
||||||
|
entry = await setup_platform(hass, [Platform.UPDATE])
|
||||||
|
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_alt(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
mock_vehicle_data,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the update entities are correct."""
|
||||||
|
|
||||||
|
mock_vehicle_data.return_value = VEHICLE_DATA_ALT
|
||||||
|
entry = await setup_platform(hass, [Platform.UPDATE])
|
||||||
|
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_offline(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_vehicle_data,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the update entities are correct when offline."""
|
||||||
|
|
||||||
|
mock_vehicle_data.side_effect = VehicleOffline
|
||||||
|
await setup_platform(hass, [Platform.UPDATE])
|
||||||
|
state = hass.states.get("update.test_update")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_services(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_vehicle_data,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the update services work."""
|
||||||
|
|
||||||
|
await setup_platform(hass, [Platform.UPDATE])
|
||||||
|
|
||||||
|
entity_id = "update.test_update"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.VehicleSpecific.schedule_software_update",
|
||||||
|
return_value=COMMAND_OK,
|
||||||
|
) as call:
|
||||||
|
await hass.services.async_call(
|
||||||
|
UPDATE_DOMAIN,
|
||||||
|
SERVICE_INSTALL,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
call.assert_called_once()
|
||||||
|
|
||||||
|
VEHICLE_DATA["response"]["vehicle_state"]["software_update"]["status"] = INSTALLING
|
||||||
|
mock_vehicle_data.return_value = VEHICLE_DATA
|
||||||
|
freezer.tick(VEHICLE_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.attributes["in_progress"] == 1
|
Loading…
x
Reference in New Issue
Block a user