mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add media player platform to Tessie (#106214)
* Add media platform * Add more props * Fix platform filename * Working * Add a test * Update test and fixture * Refactor media player properties to handle null values * Add comments * add more assertions * Fix test docstring * Use walrus instead. Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Test when media player is idle * Fix tests * Remove None type from volume_level Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Return media position only when a media duration is > 0 * Remove impossible None type * Add snapshot and freezer --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
2c2e6171e2
commit
b4f8fe8d4d
@ -21,6 +21,7 @@ PLATFORMS = [
|
|||||||
Platform.COVER,
|
Platform.COVER,
|
||||||
Platform.DEVICE_TRACKER,
|
Platform.DEVICE_TRACKER,
|
||||||
Platform.LOCK,
|
Platform.LOCK,
|
||||||
|
Platform.MEDIA_PLAYER,
|
||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
109
homeassistant/components/tessie/media_player.py
Normal file
109
homeassistant/components/tessie/media_player.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""Media Player platform for Tessie integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import (
|
||||||
|
MediaPlayerDeviceClass,
|
||||||
|
MediaPlayerEntity,
|
||||||
|
MediaPlayerState,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import TessieDataUpdateCoordinator
|
||||||
|
from .entity import TessieEntity
|
||||||
|
|
||||||
|
STATES = {
|
||||||
|
"Playing": MediaPlayerState.PLAYING,
|
||||||
|
"Paused": MediaPlayerState.PAUSED,
|
||||||
|
"Stopped": MediaPlayerState.IDLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Tessie Media platform from a config entry."""
|
||||||
|
coordinators = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
async_add_entities(TessieMediaEntity(coordinator) for coordinator in coordinators)
|
||||||
|
|
||||||
|
|
||||||
|
class TessieMediaEntity(TessieEntity, MediaPlayerEntity):
|
||||||
|
"""Vehicle Location Media Class."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: TessieDataUpdateCoordinator,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the media player entity."""
|
||||||
|
super().__init__(coordinator, "media")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> MediaPlayerState:
|
||||||
|
"""State of the player."""
|
||||||
|
return STATES.get(
|
||||||
|
self.get("vehicle_state_media_info_media_playback_status"),
|
||||||
|
MediaPlayerState.OFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume_level(self) -> float:
|
||||||
|
"""Volume level of the media player (0..1)."""
|
||||||
|
return self.get("vehicle_state_media_info_audio_volume", 0) / self.get(
|
||||||
|
"vehicle_state_media_info_audio_volume_max", 10.333333
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_duration(self) -> int | None:
|
||||||
|
"""Duration of current playing media in seconds."""
|
||||||
|
if duration := self.get("vehicle_state_media_info_now_playing_duration"):
|
||||||
|
return duration / 1000
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_position(self) -> int | None:
|
||||||
|
"""Position of current playing media in seconds."""
|
||||||
|
# Return media position only when a media duration is > 0
|
||||||
|
if self.get("vehicle_state_media_info_now_playing_duration"):
|
||||||
|
return self.get("vehicle_state_media_info_now_playing_elapsed") / 1000
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_title(self) -> str | None:
|
||||||
|
"""Title of current playing media."""
|
||||||
|
if title := self.get("vehicle_state_media_info_now_playing_title"):
|
||||||
|
return title
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_artist(self) -> str | None:
|
||||||
|
"""Artist of current playing media, music track only."""
|
||||||
|
if artist := self.get("vehicle_state_media_info_now_playing_artist"):
|
||||||
|
return artist
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_album_name(self) -> str | None:
|
||||||
|
"""Album name of current playing media, music track only."""
|
||||||
|
if album := self.get("vehicle_state_media_info_now_playing_album"):
|
||||||
|
return album
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_playlist(self) -> str | None:
|
||||||
|
"""Title of Playlist currently playing."""
|
||||||
|
if playlist := self.get("vehicle_state_media_info_now_playing_station"):
|
||||||
|
return playlist
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def source(self) -> str | None:
|
||||||
|
"""Name of the current input source."""
|
||||||
|
if source := self.get("vehicle_state_media_info_now_playing_source"):
|
||||||
|
return source
|
||||||
|
return None
|
@ -204,14 +204,14 @@
|
|||||||
"audio_volume": 2.3333,
|
"audio_volume": 2.3333,
|
||||||
"audio_volume_increment": 0.333333,
|
"audio_volume_increment": 0.333333,
|
||||||
"audio_volume_max": 10.333333,
|
"audio_volume_max": 10.333333,
|
||||||
"media_playback_status": "Stopped",
|
"media_playback_status": "Playing",
|
||||||
"now_playing_album": "",
|
"now_playing_album": "Album",
|
||||||
"now_playing_artist": "",
|
"now_playing_artist": "Artist",
|
||||||
"now_playing_duration": 0,
|
"now_playing_duration": 60000,
|
||||||
"now_playing_elapsed": 0,
|
"now_playing_elapsed": 30000,
|
||||||
"now_playing_source": "Spotify",
|
"now_playing_source": "Spotify",
|
||||||
"now_playing_station": "",
|
"now_playing_station": "Playlist",
|
||||||
"now_playing_title": ""
|
"now_playing_title": "Song"
|
||||||
},
|
},
|
||||||
"media_state": {
|
"media_state": {
|
||||||
"remote_control_enabled": false
|
"remote_control_enabled": false
|
||||||
|
@ -222,7 +222,7 @@
|
|||||||
"now_playing_artist": "",
|
"now_playing_artist": "",
|
||||||
"now_playing_duration": 0,
|
"now_playing_duration": 0,
|
||||||
"now_playing_elapsed": 0,
|
"now_playing_elapsed": 0,
|
||||||
"now_playing_source": "Spotify",
|
"now_playing_source": "",
|
||||||
"now_playing_station": "",
|
"now_playing_station": "",
|
||||||
"now_playing_title": ""
|
"now_playing_title": ""
|
||||||
},
|
},
|
||||||
|
61
tests/components/tessie/snapshots/test_media_player.ambr
Normal file
61
tests/components/tessie/snapshots/test_media_player.ambr
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_media_player_idle
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'speaker',
|
||||||
|
'friendly_name': 'Test',
|
||||||
|
'supported_features': <MediaPlayerEntityFeature: 0>,
|
||||||
|
'volume_level': 0.22580323309042688,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'media_player.test',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'idle',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_media_player_idle.1
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'speaker',
|
||||||
|
'friendly_name': 'Test',
|
||||||
|
'supported_features': <MediaPlayerEntityFeature: 0>,
|
||||||
|
'volume_level': 0.22580323309042688,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'media_player.test',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'idle',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_media_player_playing
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'speaker',
|
||||||
|
'friendly_name': 'Test',
|
||||||
|
'supported_features': <MediaPlayerEntityFeature: 0>,
|
||||||
|
'volume_level': 0.22580323309042688,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'media_player.test',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'idle',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'speaker',
|
||||||
|
'friendly_name': 'Test',
|
||||||
|
'supported_features': <MediaPlayerEntityFeature: 0>,
|
||||||
|
'volume_level': 0.22580323309042688,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'media_player.test',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'idle',
|
||||||
|
})
|
||||||
|
# ---
|
46
tests/components/tessie/test_media_player.py
Normal file
46
tests/components/tessie/test_media_player.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""Test the Tessie media player platform."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.tessie.coordinator import TESSIE_SYNC_INTERVAL
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
TEST_STATE_OF_ALL_VEHICLES,
|
||||||
|
TEST_VEHICLE_STATE_ONLINE,
|
||||||
|
setup_platform,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
WAIT = timedelta(seconds=TESSIE_SYNC_INTERVAL)
|
||||||
|
|
||||||
|
MEDIA_INFO_1 = TEST_STATE_OF_ALL_VEHICLES["results"][0]["last_state"]["vehicle_state"][
|
||||||
|
"media_info"
|
||||||
|
]
|
||||||
|
MEDIA_INFO_2 = TEST_VEHICLE_STATE_ONLINE["vehicle_state"]["media_info"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player_idle(
|
||||||
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the media player entity is correct when idle."""
|
||||||
|
|
||||||
|
assert len(hass.states.async_all("media_player")) == 0
|
||||||
|
|
||||||
|
await setup_platform(hass)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all("media_player")) == 1
|
||||||
|
|
||||||
|
state = hass.states.get("media_player.test")
|
||||||
|
assert state == snapshot
|
||||||
|
|
||||||
|
# Trigger coordinator refresh since it has a different fixture.
|
||||||
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("media_player.test")
|
||||||
|
assert state == snapshot
|
Loading…
x
Reference in New Issue
Block a user