mirror of https://github.com/home-assistant/core
218 lines
7.0 KiB
Python
218 lines
7.0 KiB
Python
"""Support for interacting with and controlling the cmus music player."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from pycmus import exceptions, remote
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.media_player import (
|
|
PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
|
|
MediaPlayerEntity,
|
|
MediaPlayerEntityFeature,
|
|
MediaPlayerState,
|
|
MediaType,
|
|
)
|
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
|
|
from homeassistant.core import HomeAssistant
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DEFAULT_NAME = "cmus"
|
|
DEFAULT_PORT = 3000
|
|
|
|
PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Inclusive(CONF_HOST, "remote"): cv.string,
|
|
vol.Inclusive(CONF_PASSWORD, "remote"): cv.string,
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
}
|
|
)
|
|
|
|
|
|
def setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
add_entities: AddEntitiesCallback,
|
|
discover_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Set up the CMUS platform."""
|
|
|
|
host = config.get(CONF_HOST)
|
|
password = config.get(CONF_PASSWORD)
|
|
port = config[CONF_PORT]
|
|
name = config[CONF_NAME]
|
|
|
|
cmus_remote = CmusRemote(server=host, port=port, password=password)
|
|
cmus_remote.connect()
|
|
|
|
if cmus_remote.cmus is None:
|
|
return
|
|
|
|
add_entities([CmusDevice(device=cmus_remote, name=name, server=host)], True)
|
|
|
|
|
|
class CmusRemote:
|
|
"""Representation of a cmus connection."""
|
|
|
|
def __init__(self, server, port, password):
|
|
"""Initialize the cmus remote."""
|
|
|
|
self._server = server
|
|
self._port = port
|
|
self._password = password
|
|
self.cmus = None
|
|
|
|
def connect(self):
|
|
"""Connect to the cmus server."""
|
|
|
|
try:
|
|
self.cmus = remote.PyCmus(
|
|
server=self._server, port=self._port, password=self._password
|
|
)
|
|
except exceptions.InvalidPassword:
|
|
_LOGGER.error("The provided password was rejected by cmus")
|
|
|
|
|
|
class CmusDevice(MediaPlayerEntity):
|
|
"""Representation of a running cmus."""
|
|
|
|
_attr_media_content_type = MediaType.MUSIC
|
|
_attr_supported_features = (
|
|
MediaPlayerEntityFeature.PAUSE
|
|
| MediaPlayerEntityFeature.VOLUME_SET
|
|
| MediaPlayerEntityFeature.TURN_OFF
|
|
| MediaPlayerEntityFeature.TURN_ON
|
|
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
|
| MediaPlayerEntityFeature.NEXT_TRACK
|
|
| MediaPlayerEntityFeature.PLAY_MEDIA
|
|
| MediaPlayerEntityFeature.SEEK
|
|
| MediaPlayerEntityFeature.PLAY
|
|
)
|
|
|
|
def __init__(self, device, name, server):
|
|
"""Initialize the CMUS device."""
|
|
|
|
self._remote = device
|
|
if server:
|
|
auto_name = f"cmus-{server}"
|
|
else:
|
|
auto_name = "cmus-local"
|
|
self._attr_name = name or auto_name
|
|
self.status = {}
|
|
|
|
def update(self) -> None:
|
|
"""Get the latest data and update the state."""
|
|
try:
|
|
status = self._remote.cmus.get_status_dict()
|
|
except BrokenPipeError:
|
|
self._remote.connect()
|
|
except exceptions.ConfigurationError:
|
|
_LOGGER.warning("A configuration error occurred")
|
|
self._remote.connect()
|
|
else:
|
|
self.status = status
|
|
if self.status.get("status") == "playing":
|
|
self._attr_state = MediaPlayerState.PLAYING
|
|
elif self.status.get("status") == "paused":
|
|
self._attr_state = MediaPlayerState.PAUSED
|
|
else:
|
|
self._attr_state = MediaPlayerState.OFF
|
|
self._attr_media_content_id = self.status.get("file")
|
|
self._attr_media_duration = self.status.get("duration")
|
|
self._attr_media_title = self.status["tag"].get("title")
|
|
self._attr_media_artist = self.status["tag"].get("artist")
|
|
self._attr_media_track = self.status["tag"].get("tracknumber")
|
|
self._attr_media_album_name = self.status["tag"].get("album")
|
|
self._attr_media_album_artist = self.status["tag"].get("albumartist")
|
|
left = self.status["set"].get("vol_left")[0]
|
|
right = self.status["set"].get("vol_right")[0]
|
|
if left != right:
|
|
volume = float(left + right) / 2
|
|
else:
|
|
volume = left
|
|
self._attr_volume_level = int(volume) / 100
|
|
return
|
|
|
|
_LOGGER.warning("Received no status from cmus")
|
|
|
|
def turn_off(self) -> None:
|
|
"""Service to send the CMUS the command to stop playing."""
|
|
self._remote.cmus.player_stop()
|
|
|
|
def turn_on(self) -> None:
|
|
"""Service to send the CMUS the command to start playing."""
|
|
self._remote.cmus.player_play()
|
|
|
|
def set_volume_level(self, volume: float) -> None:
|
|
"""Set volume level, range 0..1."""
|
|
self._remote.cmus.set_volume(int(volume * 100))
|
|
|
|
def volume_up(self) -> None:
|
|
"""Set the volume up."""
|
|
left = self.status["set"].get("vol_left")
|
|
right = self.status["set"].get("vol_right")
|
|
if left != right:
|
|
current_volume = float(left + right) / 2
|
|
else:
|
|
current_volume = left
|
|
|
|
if current_volume <= 100:
|
|
self._remote.cmus.set_volume(int(current_volume) + 5)
|
|
|
|
def volume_down(self) -> None:
|
|
"""Set the volume down."""
|
|
left = self.status["set"].get("vol_left")
|
|
right = self.status["set"].get("vol_right")
|
|
if left != right:
|
|
current_volume = float(left + right) / 2
|
|
else:
|
|
current_volume = left
|
|
|
|
if current_volume <= 100:
|
|
self._remote.cmus.set_volume(int(current_volume) - 5)
|
|
|
|
def play_media(
|
|
self, media_type: MediaType | str, media_id: str, **kwargs: Any
|
|
) -> None:
|
|
"""Send the play command."""
|
|
if media_type in {MediaType.MUSIC, MediaType.PLAYLIST}:
|
|
self._remote.cmus.player_play_file(media_id)
|
|
else:
|
|
_LOGGER.error(
|
|
"Invalid media type %s. Only %s and %s are supported",
|
|
media_type,
|
|
MediaType.MUSIC,
|
|
MediaType.PLAYLIST,
|
|
)
|
|
|
|
def media_pause(self) -> None:
|
|
"""Send the pause command."""
|
|
self._remote.cmus.player_pause()
|
|
|
|
def media_next_track(self) -> None:
|
|
"""Send next track command."""
|
|
self._remote.cmus.player_next()
|
|
|
|
def media_previous_track(self) -> None:
|
|
"""Send next track command."""
|
|
self._remote.cmus.player_prev()
|
|
|
|
def media_seek(self, position: float) -> None:
|
|
"""Send seek command."""
|
|
self._remote.cmus.seek(position)
|
|
|
|
def media_play(self) -> None:
|
|
"""Send the play command."""
|
|
self._remote.cmus.player_play()
|
|
|
|
def media_stop(self) -> None:
|
|
"""Send the stop command."""
|
|
self._remote.cmus.stop()
|