Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add media player platform to Teslemetry #117394

Merged
merged 13 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/teslemetry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
Platform.CLIMATE,
Platform.COVER,
Platform.LOCK,
Platform.MEDIA_PLAYER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Expand Down
149 changes: 149 additions & 0 deletions homeassistant/components/teslemetry/media_player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Media player platform for Teslemetry integration."""

from __future__ import annotations

from tesla_fleet_api.const import Scope

from homeassistant.components.media_player import (
MediaPlayerDeviceClass,
MediaPlayerEntity,
MediaPlayerEntityFeature,
MediaPlayerState,
)
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

STATES = {
"Playing": MediaPlayerState.PLAYING,
"Paused": MediaPlayerState.PAUSED,
"Stopped": MediaPlayerState.IDLE,
"Off": MediaPlayerState.OFF,
}
VOLUME_MAX = 11.0
VOLUME_STEP = 1.0 / 3


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Teslemetry Media platform from a config entry."""

async_add_entities(
TeslemetryMediaEntity(vehicle, Scope.VEHICLE_CMDS in entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
)


class TeslemetryMediaEntity(TeslemetryVehicleEntity, MediaPlayerEntity):
"""Vehicle media player class."""

_attr_device_class = MediaPlayerDeviceClass.SPEAKER
_attr_supported_features = (
MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.VOLUME_SET
)
_volume_max: float = VOLUME_MAX

def __init__(
self,
data: TeslemetryVehicleData,
scoped: bool,
) -> None:
"""Initialize the media player entity."""
super().__init__(data, "media")
self.scoped = scoped
if not scoped:
self._attr_supported_features = MediaPlayerEntityFeature(0)

def _async_update_attrs(self) -> None:
"""Update entity attributes."""
self._volume_max = (
self.get("vehicle_state_media_info_audio_volume_max") or VOLUME_MAX
)
self._attr_state = STATES.get(
self.get("vehicle_state_media_info_media_playback_status") or "Off",
)
self._attr_volume_step = (
1.0
/ self._volume_max
/ (
self.get("vehicle_state_media_info_audio_volume_increment")
or VOLUME_STEP
)
)

if volume := self.get("vehicle_state_media_info_audio_volume"):
self._attr_volume_level = volume / self._volume_max
else:
self._attr_volume_level = None

if duration := self.get("vehicle_state_media_info_now_playing_duration"):
self._attr_media_duration = duration / 1000
else:
self._attr_media_duration = None

if duration and (
position := self.get("vehicle_state_media_info_now_playing_elapsed")
):
self._attr_media_position = position / 1000
else:
self._attr_media_position = None

self._attr_media_title = self.get("vehicle_state_media_info_now_playing_title")
self._attr_media_artist = self.get(
"vehicle_state_media_info_now_playing_artist"
)
self._attr_media_album_name = self.get(
"vehicle_state_media_info_now_playing_album"
)
self._attr_media_playlist = self.get(
"vehicle_state_media_info_now_playing_station"
)
self._attr_source = self.get("vehicle_state_media_info_now_playing_source")

async def async_set_volume_level(self, volume: float) -> None:
"""Set volume level, range 0..1."""
self.raise_for_scope()
await self.wake_up_if_asleep()
await self.handle_command(
self.api.adjust_volume(int(volume * self._volume_max))
)
self._attr_volume_level = volume
self.async_write_ha_state()

async def async_media_play(self) -> None:
"""Send play command."""
if self.state != MediaPlayerState.PLAYING:
self.raise_for_scope()
await self.wake_up_if_asleep()
await self.handle_command(self.api.media_toggle_playback())
self._attr_state = MediaPlayerState.PLAYING
self.async_write_ha_state()

async def async_media_pause(self) -> None:
"""Send pause command."""
if self.state == MediaPlayerState.PLAYING:
self.raise_for_scope()
await self.wake_up_if_asleep()
await self.handle_command(self.api.media_toggle_playback())
self._attr_state = MediaPlayerState.PAUSED
self.async_write_ha_state()

async def async_media_next_track(self) -> None:
"""Send next track command."""
self.raise_for_scope()
await self.wake_up_if_asleep()
await self.handle_command(self.api.media_next_track())

async def async_media_previous_track(self) -> None:
"""Send previous track command."""
self.raise_for_scope()
await self.wake_up_if_asleep()
await self.handle_command(self.api.media_prev_track())
5 changes: 5 additions & 0 deletions homeassistant/components/teslemetry/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@
}
}
},
"media_player": {
"media": {
"name": "[%key:component::media_player::title%]"
}
},
"cover": {
"charge_state_charge_port_door_open": {
"name": "Charge port door"
Expand Down
19 changes: 10 additions & 9 deletions tests/components/teslemetry/fixtures/vehicle_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,18 @@
"is_user_present": false,
"locked": false,
"media_info": {
"audio_volume": 2.6667,
"a2dp_source_name": "Pixel 8 Pro",
"audio_volume": 1.6667,
"audio_volume_increment": 0.333333,
"audio_volume_max": 10.333333,
"media_playback_status": "Stopped",
"now_playing_album": "",
"now_playing_artist": "",
"now_playing_duration": 0,
"now_playing_elapsed": 0,
"now_playing_source": "Spotify",
"now_playing_station": "",
"now_playing_title": ""
"media_playback_status": "Playing",
"now_playing_album": "Elon Musk",
"now_playing_artist": "Walter Isaacson",
"now_playing_duration": 651000,
"now_playing_elapsed": 1000,
"now_playing_source": "Audible",
"now_playing_station": "Elon Musk",
"now_playing_title": "Chapter 51: Cybertruck: Tesla, 2018–2019"
},
"media_state": {
"remote_control_enabled": true
Expand Down
19 changes: 10 additions & 9 deletions tests/components/teslemetry/snapshots/test_diagnostics.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,18 @@
'vehicle_state_ft': 0,
'vehicle_state_is_user_present': False,
'vehicle_state_locked': False,
'vehicle_state_media_info_audio_volume': 2.6667,
'vehicle_state_media_info_a2dp_source_name': 'Pixel 8 Pro',
'vehicle_state_media_info_audio_volume': 1.6667,
'vehicle_state_media_info_audio_volume_increment': 0.333333,
'vehicle_state_media_info_audio_volume_max': 10.333333,
'vehicle_state_media_info_media_playback_status': 'Stopped',
'vehicle_state_media_info_now_playing_album': '',
'vehicle_state_media_info_now_playing_artist': '',
'vehicle_state_media_info_now_playing_duration': 0,
'vehicle_state_media_info_now_playing_elapsed': 0,
'vehicle_state_media_info_now_playing_source': 'Spotify',
'vehicle_state_media_info_now_playing_station': '',
'vehicle_state_media_info_now_playing_title': '',
'vehicle_state_media_info_media_playback_status': 'Playing',
'vehicle_state_media_info_now_playing_album': 'Elon Musk',
'vehicle_state_media_info_now_playing_artist': 'Walter Isaacson',
'vehicle_state_media_info_now_playing_duration': 651000,
'vehicle_state_media_info_now_playing_elapsed': 1000,
'vehicle_state_media_info_now_playing_source': 'Audible',
'vehicle_state_media_info_now_playing_station': 'Elon Musk',
'vehicle_state_media_info_now_playing_title': 'Chapter 51: Cybertruck: Tesla, 2018–2019',
'vehicle_state_media_state_remote_control_enabled': True,
'vehicle_state_notifications_supported': True,
'vehicle_state_odometer': 6481.019282,
Expand Down
136 changes: 136 additions & 0 deletions tests/components/teslemetry/snapshots/test_media_player.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# serializer version: 1
# name: test_media_player[media_player.test_media_player-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.test_media_player',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': None,
'original_name': 'Media player',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 16437>,
'translation_key': 'media',
'unique_id': 'VINVINVIN-media',
'unit_of_measurement': None,
})
# ---
# name: test_media_player[media_player.test_media_player-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test Media player',
'media_album_name': 'Elon Musk',
'media_artist': 'Walter Isaacson',
'media_duration': 651.0,
'media_playlist': 'Elon Musk',
'media_position': 1.0,
'media_title': 'Chapter 51: Cybertruck: Tesla, 2018–2019',
'source': 'Audible',
'supported_features': <MediaPlayerEntityFeature: 16437>,
'volume_level': 0.16129355359011466,
}),
'context': <ANY>,
'entity_id': 'media_player.test_media_player',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'playing',
})
# ---
# name: test_media_player_alt[media_player.test_media_player-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test Media player',
'media_album_name': '',
'media_artist': '',
'media_playlist': '',
'media_title': '',
'source': 'Spotify',
'supported_features': <MediaPlayerEntityFeature: 16437>,
'volume_level': 0.25806775026025003,
}),
'context': <ANY>,
'entity_id': 'media_player.test_media_player',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_media_player_noscope[media_player.test_media_player-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.test_media_player',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': None,
'original_name': 'Media player',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'media',
'unique_id': 'VINVINVIN-media',
'unit_of_measurement': None,
})
# ---
# name: test_media_player_noscope[media_player.test_media_player-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test Media player',
'media_album_name': 'Elon Musk',
'media_artist': 'Walter Isaacson',
'media_duration': 651.0,
'media_playlist': 'Elon Musk',
'media_position': 1.0,
'media_title': 'Chapter 51: Cybertruck: Tesla, 2018–2019',
'source': 'Audible',
'supported_features': <MediaPlayerEntityFeature: 0>,
'volume_level': 0.16129355359011466,
}),
'context': <ANY>,
'entity_id': 'media_player.test_media_player',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'playing',
})
# ---
Loading